ntvdmx64 icon indicating copy to clipboard operation
ntvdmx64 copied to clipboard

Extreme Paintbrawl crash and DxWnd APC injection

Open BEENNath58 opened this issue 2 years ago • 67 comments

This game is hilarious, from a gameplay POV. On the technical side of things it is a mixture of Windows and DOS. On Windows 11 it crashes too just like XP. Can you look into getting this game run? On XP this appears: extremepaintbrawlcrash

I am not sure if that's the situation with NTVDM too, but it'll be cool if you can the game to work. Since the game scaled to my entire desktop resolution, I actually had to use DxWnd to run the game. I posted my .dxw profile too pblaunch.zip (You may want too add the 640x400 resolution to your driver, or use the Run in Window setting of DxWnd)

BEENNath58 avatar Mar 28 '22 15:03 BEENNath58

Not really sure, how that one works. Tried your dxwnd profile, then said "Single Player" and then it shows an overview screen, where I can i.e. click "Practice" or "Exit game", but whatever I try, it doesn't react to any click.

So I tried starting game.exe in engine subdirectory, with NTVDMx64 it starts up normally, but of course the performance is inferior, completely unusable (not really surprising, as NTVDMx64 is not really useful for games due to the performance of the emulated CCPU and with VT-x accelleration on the other hand, you would have a good CPU performance but suffer from the slow video performance, which is technically impossible to speed up due to the VGA architecture). image

Anyway, to me - at least in my version - it looks like this is just some DOS game that has a Windows GUI for setup, so why not directly run it in DosBox? Or do I have the wrong version? If so, can you provide me with a download link to the proper version?

My guess is that this should be some hybrid with a Windows graphics where just the game engine is on DOS, like PowerChess? If so, how do I get this damn thing working?

leecher1337 avatar Mar 28 '22 17:03 leecher1337

The game is based on the Build engine, popular for Duke Nukem 3D. Thus it's engine is clearly DOS, but it's initial setup of buying and customizing things is in Win32, so that's a problem for dosbox.

Regarding clicks, did you get a white screen? If so you might want to run it in a window. The game should run in a very low resolution, so if the game for some reason decides to render at a very high resolution, a problem like this should occur.

Since the game works here from the Win32 "setup", I can buy weapons easily, access other options but when I am about to launch a mission, it just crashes without any type of error!

And I got the game from old games ru. Probably you have the same version, it has an InstallShield that displays a "headgames" video at the start.

If I try to run "game.exe" I get "Cannot execute D:......\ENGINE\game.exe"

so why not directly run it in DosBox?

I tried running game.exe from there, all I got was a revolving "Extreme Paintbrawl" logo.

Below, you can see how the game looks on my PC, the Win32 part: paintbrawlingame

BEENNath58 avatar Mar 29 '22 05:03 BEENNath58

I got this version https://www.myabandonware.com/game/extreme-paintbrawl-cl3 and game.exe also runs fine in DOSBox.

My Win32 part looks the same (at least with your DxWnd profile), when I go over the various elements of the picture with the mouse, I also get the correct descriptive text, but when I click on one of these items, nothing happens.

leecher1337 avatar Mar 29 '22 19:03 leecher1337

https://www.old-games.ru/game/download/5644.html This RIP also works fine with dosbox

leecher1337 avatar Mar 29 '22 19:03 leecher1337

Ok, I managed to find a point where I can click to "Practice" on this Overview map, it's a tiny spot but I found it. What it basically does is to modify the config files and the run the game via GO.BAT Good thing is that you can rewrite Engine\GO.BAT and i.e. let the gam run in DosBox. One annoying bug is a use-after-free bug. It unloads some DLLs but leaves pointers to functions in memory leading to a crash upon launching the target executable, see here:

image

image

This is upon handling the Window-Procedure request WM_COMMAND for pushing the "Play" button:

image

leecher1337 avatar Mar 29 '22 20:03 leecher1337

FreeLibrary(hDirectDraw):

image

But DirectDraw still on callstack, so crashes on return to nonexisting address:

image

leecher1337 avatar Mar 29 '22 20:03 leecher1337

Even tough this seems to be a bug in Extreme Paintbrawl launcher, it was enough for me to edit go.bat and lat it launch via dosbox:

ECHO OFF
"c:\Program Files (x86)\DOSBox-0.74-3\dosbox.exe" -c "mount C '%CD%'" -C C: -c game.exe -c exit
CD ..
PAINTBRAWL

leecher1337 avatar Mar 29 '22 21:03 leecher1337

Forgot that I circumvented dxwnd when testing this, so there also is a bug in dxwnd preventing you to run GO.BAT:

dll\createproc.cpp:

	res=(*pCreateProcessA)(
		lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, false, 
		dwCreationFlags|CREATE_SUSPENDED, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
	if (!res){
		OutTraceE("%s(CREATE_SUSPENDED) ERROR: err=%d\n", ApiRef, GetLastError());
		res=(*pCreateProcessA)(NULL, lpCommandLine, 0, 0, false, dwCreationFlags, NULL, lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
		if(!res){ 
			OutTraceE("%s ERROR: err=%d\n", ApiRef, GetLastError());
		}
		return res;
	}

	while(TRUE){ // fake loop
		bKillProcess = TRUE;

		// locate the entry point
		TargetHandle = OpenProcess(
			PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_SUSPEND_RESUME, 
			FALSE, 
			lpProcessInformation->dwProcessId);

		fExe = fopen(lpExePath, "rb");
		if(fExe == NULL){
			OutTraceE("%s: ERROR fopen(\"%s\") err=%d\n", ApiRef, lpExePath, GetLastError());
			break;
		}

		bKillProcess = !InjectSon(ApiRef, fExe, lpProcessInformation, TargetHandle);
		break; // exit fake loop
	}

	// cleanup ....
	if(fExe) fclose(fExe);
	if(TargetHandle) CloseHandle(TargetHandle);
	// terminate the newly spawned process
	if(bKillProcess){
		OutTraceDW("%s: Kill son process hproc=%#x pid=%#x\n", ApiRef, lpProcessInformation->hProcess, lpProcessInformation->dwProcessId);
		if(!TerminateProcess( lpProcessInformation->hProcess, -1 )){
			OutTraceE("%s: failed to kill hproc=%#x err=%d\n", ApiRef, lpProcessInformation->hProcess, GetLastError());
		}
	}

When launching GO.BAT, this is a .BAT file, thus it launches cmd.exe /C GO.BAT cmd.exe is a console process. However, it opens the lpExePath with fopen, so it opens the BAT file, but in InjectSon, it tries to parse the .bat file as .exe, which of course fails and thus, it kills the process instead of leaving it alone. Why it reads the exe from disk and not in memory is onknown to me, this doesn't make sense. I don't know why it just kills the target process when it's unable to inject, this is plain stupid behaviour to me.

So in order to run it properly, you need 3 things:

  1. Fix dxwnd to launch GO.BAT
  2. Modify GO.BAT like mentioned above to run it in DosBox
  3. Maybe optionally patch the use-after-free bug in PaintBrawl.exe

I can take a look at 3), if necessary, but first dxwnd author needs to fix his bug.

leecher1337 avatar Mar 29 '22 21:03 leecher1337

I changed above line

	if(bKillProcess){

to:

if(bKillProcess && ResumeThread(lpProcessInformation->hThread)==(DWORD)-1) {

and recompiled dxwnd.dll (which was a real pain). Find attached an updated dxwnd.dll: dxwnd.zip

So:

  1. Copy updated dxwnd.dll to dxwnd directory
  2. Modify GO.BAT as described above
  3. Profit

Hope that helps

leecher1337 avatar Mar 30 '22 02:03 leecher1337

Ok I got some time and tested it. I tested the change in DxWnd and now it no longer crashes directly. But still executing go.bat through NTVDMx64 doesn't run anything. As soon as I execute it, it goes back to the game GUI with errors (that apparently has to be fixed by DxWnd, the game loses the hooked task). But NTVDMx64 never launches the game here.

So I took the DOSBox route, in fact I tried both DOSBox as well as DOSBox-X but all I get it: paintbrawl rotating

I tried this game in PCem Win98 and the rotating icon stops spinning to launch the game.

BEENNath58 avatar Mar 30 '22 09:03 BEENNath58

A weird situation here. While testing this game I had PCem on. The strange thing was DOSBox ran the game some times, but when it ran, PCem Win98 received a BSOD. It is like if there's no BSOD on PCem Win98, the mission doesn't launch on DOSBox

BEENNath58 avatar Mar 30 '22 09:03 BEENNath58

Hi, thank for your help. The reason for killing the son process is that the procedure is supposed to inject code in the son process, so it first overwrites a few bytes of the suspended process with an infinite loop, then injects the hooking code and finally resumes the bytes at the start address to let the program go. If anything fails in this operation, you would have a process looping forever and overheating your CPU, so it is wise to kill it after the timeout. The procedure is intended for Win32 executable only, you should be able to run a .BAT file by picking a DxWnd different son hooking mode (the default one) but of course this way you won't have the automatic "extended hook" mode. Please, let me know if my reasoning makes sense, I'll be happy to apply the fix to improve DxWnd if I was wrong.

ghotik avatar Mar 30 '22 10:03 ghotik

Hi, thank for your help. The reason for killing the son process is that the procedure is supposed to inject code in the son process, so it first overwrites a few bytes of the suspended process with an infinite loop, then injects the hooking code and finally resumes the bytes at the start address to let the program go. If anything fails in this operation, you would have a process looping forever and overheating your CPU, so it is wise to kill it after the timeout.

So you can either return a different return value for non-PE executables (your PE-header parser will notice it) then and let these applications resume instead of killing it, then it should work with your injector too (as the checked PE-header is not used anywhere, I guess the intention of it is to just check if it's a PE file). Your injector may even work fine, because a .bat is just a cmd.exe /c process, so it's an .exe, but you have to take HMODULE load addres for checking then and not open the file on disk (That's what I found odd, you have it mapped in memory already including PE-Header and everything, but you do a seperate read and check if ondisk. Is there a reason for this?)

You can also use APC injection, as NtTestAlert() should fire just in time. Btw., if you are interested in different injection methods, you can have a look at my [injector32.c] (https://github.com/leecher1337/ntvdmx64/blob/master/ntvdmpatch/src/ldntvdm/ldntvdm/injector32.c) so that your loader can offer even more hooking methods, just in case.

The procedure is intended for Win32 executable only, you should be able to run a .BAT file by picking a DxWnd different son hooking mode (the default one) but of course this way you won't have the automatic "extended hook" mode. Please, let me know if my reasoning makes sense, I'll be happy to apply the fix to improve DxWnd if I was wrong.

I tried different combinations before writing (and trying to compile the source myself), because I thought, maybe this is enough, but I didn't get the nworking, most of them just crashed the original .exe. Btw.: How is the compilation supposed to work? I opened the dll project in VS2019 and got thousands of errors on compiling, because there are some MinGW headers in the "include" directory. I had to remove libloaderapi.h and apiset.h to be able to compile it and even then, I had to do some fixes (which may be due to using VS2019 instead of VS2008). There also was a declaration with C++ auto somewhere, which the VS2019 C++ compiler didn't understand.

leecher1337 avatar Mar 30 '22 11:03 leecher1337

Thank you for the reply. It's quite a lot of stuff to make my brains boiling, so I'm replying now just to some easy questions, though I assure you that I'll consider all your suggestions very carefully. DxWnd is easily compiled with VS2008. This was not really a choice, the original Japanese code was compiled this way and, during so many years, I never found a very good reason for changing compiler, so I presume that I could say the culprit was my laziness. In addition, DxWnd is targeted to old Win32 programs, and in some cases it was possible to find leaked include files that were better fitting to the old tool rather than the modern ones. Finally, I found quite annoying some Microsoft claim to release free versions of Visual Studio just to discover after some time that I needed to register someplace or do other unpleasant stuff. I like my freedom. In any case, it's something I could try to change in near future. I will appreciate comments and suggestions about the best, free available tool.

You said that your compilations crashed the original exe. Did you mean the DxWnd.exe GUI? That sounds reasonable: DxWnd.exe and dxwnd.dll use shared memory sections with C structures inside, so it is necessary to make sure they both compile the struct objects with the same spacing. Probably some #pragma pack directive here and there could fix the problem, as well as using the same compiler.

I will surely read and take advantage of your injector32.c source. I was happy enough with mine, but I must admit that in some cases the process shows some flaw, like failures on first attempt or unreliable results. Usually with a little patience and some retry the problems are bypassed, but I agree there could be something that doesn't work properly in there.

Well, that's even too much for now. I hope to hear you again with more intriguing questions. And congrats for your wonderful work!

ghotik avatar Mar 30 '22 13:03 ghotik

Thank you for the reply. It's quite a lot of stuff to make my brains boiling, so I'm replying now just to some easy questions, though I assure you that I'll consider all your suggestions very carefully. DxWnd is easily compiled with VS2008. This was not really a choice, the original Japanese code was compiled this way and, during so many years, I

Hehe, I still prefer Visual C 6, that's my IDE, it has a small memory footprint and links everything to msvcrt.dll, so no additional runtime includes, so I fully understand your Compiler choice. I was just in doubt that it would compile with VS 2008, because of these 2 strange header files I mentioned. They include i.e. an inexistant _mingw.h, so I thought that this also cannot work with VS 2008, i.e. because of conflicts with Windows original headers. When I removed these 2 header files, then the other errors can be attributed to the VS version. But what I found funny is that a newer compiler doesn't know the "auto" keyword, but an older one would be OK with it, so that got me even more confused. As a matter of fact, I have a few different VS Versions installed, but VS 2008 is the one I'm missing. Bad luck for me, I guess ;-)

You said that your compilations crashed the original exe. Did you mean the DxWnd.exe GUI? That sounds reasonable: DxWnd.exe and dxwnd.dll use shared memory sections with C structures inside, so it is necessary to make sure they both compile the struct objects with the same spacing. Probably some #pragma pack directive here and there could fix the problem, as well as using the same compiler.

Oh, no, that's a misunderstanding. Before I even tried to recompile the source, I first checked if I cannot fix the problem with the .bat file by choosing different hooking methods in the "advanced" GUI, but a lot of combinations mostly crashed the target executable. That's why I gave up on that and finally patched dxwnd. Maybe I was missing a working combination. You may want to try it out yourself with the "RIP" version from oldgames.ru, it is only a few MB in size.

I will surely read and take advantage of your injector32.c source. I was happy enough with mine, but I must admit that in some cases the process shows some flaw, like failures on first attempt or unreliable results. Usually with a little patience and some retry the problems are bypassed, but I agree there could be something that doesn't work properly in there.

Some of the methods in injector32.c are a bit unreliable too depending on the target, but as you have this nice selection dialog, the user can try out different methods. As I went through and fixed some common problems when writing them, you may profit from it.

Well, that's even too much for now. I hope to hear you again with more intriguing questions. And congrats for your wonderful work!

Thank you :)

leecher1337 avatar Mar 30 '22 15:03 leecher1337

Ok I got some time and tested it. I tested the change in DxWnd and now it no longer crashes directly. But still executing go.bat through NTVDMx64 doesn't run anything. As soon as I execute it, it goes back to the game GUI with errors (that apparently has to be fixed by DxWnd, the game loses the hooked task). But NTVDMx64 never launches the game here.

Forget about NTVDMx64 for that game, performance is inferior, so it is completely unusable.

So I took the DOSBox route, in fact I tried both DOSBox as well as DOSBox-X but all I get it:

Which version of DOSbox? With latest DOSBox 0.74-3 directly from the homepage, I don't have any problems so far. Here is my PaintB.cfg in case it is a problem with your settings: PaintB.cfg

leecher1337 avatar Mar 30 '22 16:03 leecher1337

Ok I got some time and tested it. I tested the change in DxWnd and now it no longer crashes directly. But still executing go.bat through NTVDMx64 doesn't run anything. As soon as I execute it, it goes back to the game GUI with errors (that apparently has to be fixed by DxWnd, the game loses the hooked task). But NTVDMx64 never launches the game here.

Forget about NTVDMx64 for that game, performance is inferior, so it is completely unusable.

So I took the DOSBox route, in fact I tried both DOSBox as well as DOSBox-X but all I get it:

Which version of DOSbox? With latest DOSBox 0.74-3 directly from the homepage, I don't have any problems so far. Here is my PaintB.cfg in case it is a problem with your settings: PaintB.cfg

I tried your configuration as well and it doesn't seem to help. It's still a infinite rotating "Extreme Paintbrwal" logo and I no longer to achieve what I describe here.

Can you describe what kind of problem I am facing with launching with NTVDM because as I said it can't execute the file (according to cmd output).

BEENNath58 avatar Mar 31 '22 14:03 BEENNath58

Check DebugView output, ensure that 32bit loader is injected properly. No problems with starting it in NTVDMx64 via GO.BAT here. You are running it on Win11 as host OS?

leecher1337 avatar Mar 31 '22 15:03 leecher1337

Check DebugView output, ensure that 32bit loader is injected properly. No problems with starting it in NTVDMx64 via GO.BAT here. You are running it on Win11 as host OS?

Yes I am using Windows 11. And here's the log: extremepaintbrwal.LOG

BEENNath58 avatar Mar 31 '22 15:03 BEENNath58

Looks like NTVDM is starting up normally as it should:

00000016	0.07511840	[5716] LDNTVDM is running inside ntvdm.exe	

So you don't see any window with the game in it?

leecher1337 avatar Mar 31 '22 16:03 leecher1337

Looks like NTVDM is starting up normally as it should:

00000016	0.07511840	[5716] LDNTVDM is running inside ntvdm.exe	

So you don't see any window with the game in it?

No as i double click on game exe it starts and crashes

BEENNath58 avatar Mar 31 '22 17:03 BEENNath58

Your game.exe is 993.885 bytes dated 02.11.1998 ? paintb.grp is 20.704.495 bytes dated 02.11.1998 ?

As your game.exe also crashes in DosBox and other emulators, I suspect that you may have a damaged version? Here is works fine in DosBox, ntvdmx64 (altough unusable due to slowness) and other emulators without any issues.

leecher1337 avatar Mar 31 '22 18:03 leecher1337

Your game.exe is 993.885 bytes dated 02.11.1998 ? paintb.grp is 20.704.495 bytes dated 02.11.1998 ?

Size: 970 KB (9,93,855 bytes) ‎Modified: 02 ‎November ‎1998, ‏‎12:11:22

As your game.exe also crashes in DosBox and other emulators, I suspect that you may have a damaged version? Here is works fine in DosBox, ntvdmx64 (altough unusable due to slowness) and other emulators without any issues.

The same game, from same media installs and run on Windows 98SE

BEENNath58 avatar Mar 31 '22 18:03 BEENNath58

I tried that RIP from oldgames.ru which indeed does not work, but only leaves you with a black screen. As said, I used this ISO and installed it: https://www.myabandonware.com/game/extreme-paintbrawl-cl3 and it works fine. File size of your version seems to match my version (I think I installed Extreme_Paintbrawl_11_patch.rar , but it shouldn't matter), but maybe something different is damaged? So did you use the ISO from myabandonware? Tested on Win 7 x64 and Windows 11 x64, no issues.

image

But for god's sake, don't run this with NTVDMx64, it's not designed to run any games that use graphics. Better check with DosBox authors why your version crashes within DosBox.

leecher1337 avatar Mar 31 '22 19:03 leecher1337

As said, I used this ISO and installed it: https://www.myabandonware.com/game/extreme-paintbrawl-cl3 and it works fine. File size of your version seems to match my version (I think I installed Extreme_Paintbrawl_11_patch.rar , but it shouldn't matter), but maybe something different is damaged? So did you use the ISO from myabandonware? Tested on Win 7 x64 and Windows 11 x64, no issues.

The ISO from myabandonware and oldgames ru are identical.

But for god's sake, don't run this with NTVDMx64, it's not designed to run any games that use graphics. Better check with DosBox authors why your version crashes within DosBox.

Since DOSBox X didn't help then I came here and while your DOSBox worked, I gave it a go and it didn't work here. Looks like there isn't anything else to try, is it? The most frustrating thing is it doesn't work on Windows 7/10 VM either.

BEENNath58 avatar Mar 31 '22 19:03 BEENNath58

Did you try DOS32A DOS etender as a DOS4GW replacement? http://www.r-t-c-m.com/knowledge-base/downloads-rtcm/general-tools-dosxp/ At least for native Windows XP, that worked.

leecher1337 avatar Mar 31 '22 20:03 leecher1337

Did you try DOS32A DOS etender as a DOS4GW replacement? http://www.r-t-c-m.com/knowledge-base/downloads-rtcm/general-tools-dosxp/ At least for native Windows XP, that worked.

There's a lot of complications. Even with this the game doesn't work on Windows XP. BUT...

Your NTVDM started working, only after I removed (x86) from the path. It seems NTVDM doesn't like special characters on my PC. Can you check this?

Regarding DOSBox IDK what's wrong, it's something else I'll have to figure out!

BEENNath58 avatar Apr 01 '22 14:04 BEENNath58

I can run game.exe with NTVDMx64 from "c:\Program Files (x86)\HeadGames\PaintBrawl\engine" without any issues.

leecher1337 avatar Apr 01 '22 18:04 leecher1337

I can run game.exe with NTVDMx64 from "c:\Program Files (x86)\HeadGames\PaintBrawl\engine" without any issues.

I now installed it on "D:\adfhuiahednf\dsafhjmnok e8qu9rhna[\aaaaaaaaaaaaaaaaaaaaaaaaaaaaa(x86)\PaintBrawl" and it runs without issues. Program Files (x86) and Program Files are definitely the culprit! I can't understand why.

BEENNath58 avatar Apr 01 '22 18:04 BEENNath58

Maybe it has to do with your file system, I don't know. Not reproducable here.

You can enable YODA, start vdmdebug, set registry key so that NTVDM breaks on startup into YODA, start the applicationn, attach a debugger (x32dbg) to ntvdm.exe, grab ntvdm.pdb symbols, continue execution in yoda and step through ntvdm.exe to see where it quits.

leecher1337 avatar Apr 01 '22 20:04 leecher1337

Hi, leecher1337 I was intrigued by your suggestions and started to put them in practice, but I fear I may need some help. As you wrote, I started trying to implement a Inject schema of APC type that seemed quite promising. Instead of using it in the target client/son relationship, I started directly using APC injection between DxWnd.exe and the direct target. If I understood correctly, this goes through several steps (here summarized):

  1. DxWnd creates a process in a suspended state with CreateProcess(... CREATE_SUSPENDED, ..., &pinfo);
  2. then gets the injected dll full path this way: GetFullPathNameW(L"dxwnd.dll", MAX_PATH, dllpath, NULL);
  3. then calls that injects injectLdrLoadDLL(pinfo.hProcess, pinfo.hThread, dllpath);
  4. in turn, injectLdrLoadDLL calls inject_via_apcthread that should do the trick
  5. finally, on successful completion, DxWnd should resume the process with ResumeThread(pinfo.hThread);

The procedure is finished, it compiles and runs with no errors, but it doesn't seem able to perform an early hook how it is supposed to. Please, consider that dxwnd.dll performs a double hook activity: at program startup if run with code injection and later, at the first window creation, through a SetWindowsHook callback, so it's not so easy to understand whether the procedures were run through injection. It takes some target that needs injection to tell the difference, as a game that checks for the OS version before creating its first window.

All this said, I have a few doubts: a) DxWnd work start by the invocation of the DLLMain() entry, which should happen automatically when loading the dll. The APC injection seems to require (if I understood correctly) to define the address of the procedure to be called, but I didn't find trace of that in the code. Is APC injection starting DLLMain too? b) did I create the process with the correct capabilities for APC injection? (see my attached code) c) Somewhere in the web I read that the injected thread must be put in a alertable suspended state to allow APC to run the injected thread first and this is easily made with a Sleep() call. I saw no Sleep calls in your code (at least, in the tiny part that I grabbed) and I didn't put a Sleep in mine. Maybe is this the flaw in my code? d) more in general, how comes that I got no errors whatsoever (the return codes of your calls were always TRUE) and nothing happens?

Please, forgive my laziness. Surely if I downloaded and studied all your project I would (more later than sooner) get some answer, but it's really quite a lot of coding! Here in attach the procedure that I am now using instead of the DxWnd InjectSuspended for testing (should I upload also the source code of InjectSuspended for a comparison?). It can't be compiled alone, but I think it should clear enough to follow the logic by reading the code. I hope you'll be so kind to have a look at it and help me finding what's possibly wrong.

InjectAPC.zip

ghotik avatar Apr 02 '22 14:04 ghotik

A late addition: browsing the web I found an interesting set of articles about code injection in cocomelonc.github.io . For instance, the code injection used currently in DxWnd maps this example: [Classic DLL injection into the process](https://cocomelonc.github.io/tutorial/2021/09/20/malware-injection-2.html). Is the suggested APC injection method described in some of the related pages, like (perhaps) [APC injection via NtTestAlert](https://cocomelonc.github.io/tutorial/2021/11/20/malware-injection-4.html) ? There seems to be so many different ways, I could get lost ...

ghotik avatar Apr 02 '22 18:04 ghotik

Hi ghotik,

I tested your code and here it works fine. Fixed some obvious errors that prevented proper testing: MessageBoxEx -> MessageBoxExA CreateProcess -> CreateProcessA Added CloseHandle() for the event

Then I wrote test code to launch notepad.exe and inject dxwnd.dll into it:

int main(int argc, char** argv)
{
	InjectAPC("C:\\WINDOWS\\SYSTEM32\\notepad.exe", NULL, TRUE, TRUE);
}

Then verified with a debugger: image

LdrLoadDLL success: image

DLL loaded: image

The trick why it works is that on starting a task, Windows does NtTestAlert() to start the main thread of the target application, as this is of course also just an APC. As your APC for injection is also queued, it gets executed as soon as you resume the main thread.

As for your questions: a) You pass an address (in the target thread, of course) of a routine that gets executed on APC. That's the Shellcode we inject (apc_stub_x86). The APC stub then loads the read loading routine (APCProcx86) which does the LdrLoadDLL() of your call. OrigEIP parameter is not used in our case, as you said, loading the DLL is enough to execute DllMain(). b) Yes, it works fine here c) True, but as you suspended the process, NtTestAlert() will get executed as soon as you resume the main thread executing your APC. d) Here it works, DLL is injected in target process with test program.

Maybe you also want to execute it as standalone like I did in my tests and check if it also works for you. You can do the same as me with the debugger in order to check possible problems.

leecher1337 avatar Apr 02 '22 18:04 leecher1337

You are right, the code works! I got another prove by disabling the SetWindowsHookEx hook in DxWnd so that the APC injection works alone, and it works. So now, hoping not to abuse of your patience, I'll explain what is my trouble. DxWnd should intercept system calls and inject some wrapper code to ensure compatibility, windowing, time stretching etc., pretty much like Microsoft shims do. But unlike shims, in some case timing is critical and DxWnd hooks partially fail. For instance, a game may start and check the OS release as first thing, just to tell that the OS is not supported and terminate. Another nasty case is when the game first commands a video screen resolution change to set 8bit color depth. So, it is vital for DxWnd to hook some system calls before they gets executed, and it seems that most injection schemas may run in parallel with the game main thread, so that the hook may work or not depending on timing. With this goal in mind, the current "Classic DLL injection" used in DxWnd works apparently a little better that the "APC Injection" because I tested the two modes with a game that calls GetVersionExA to detect the OS and the APC injection failed in most cases. I say "in most cases" because repeating the same test more times, sometimes the APC hook was fast enough to intercept the call, but this happened only a few times. I suppose that this means that the injected dll and the game were running in parallel and the fastest won! So, I will add APC injection to the DxWnd set of hook modes, you never know that it may have better performances in specific cases, but it doesn't seem to fix my fundamental concern. I whish to ask you if in your opinion there is some stronger method (I see you implemented many) that could ensure that ALL the injection code is executed BEFORE the program threads are freed and let to run. I add that there is a very hard case that so far had no solutions in DxWnd, that is when the system call to be intercepted is within the DLLMain section of some linked library. In this case, the code is executed before that the hooking dll is run, so there seems to be no hope, but I was intrigued by the source code of some leaked Microsoft shims that showed the possibility to inject some code in the DLLMain section before it was executed. So, to summarize, would you suggest some stronger hook method to fully execute the injected dll before that the target program starts its execution?

p.s. I would have many more questions for you. Should I open a dedicated thread for this?

ghotik avatar Apr 03 '22 11:04 ghotik

I think this thread is fine, it has something to do with DxWnd at least ;-)

I was facing the very same problem with ldntvdm that it needs to get executed prior to all other calls, otherwise some fixes may just be too late. Especially for processes like cmd.exe, that don't have an alertable Wait in their main routine, this was always a problem. The "strongest" that ldntvdm now uses is METHOD_HOOKLDR, I came up with it when disassembling the loader desperately searching for a point where I can inject before main DLL code gets executed. As described in the header file, this is the same as hooking NtCurrentPeb()->PostProcessInitRoutine pointer, but unfortunately, user32.dll sets this pointer to NULL, so I have to write my METHOD_HOOKLDR which just hooks the private loader routine LdrpInitializeProcess. The disadvantage of METHOD_HOOKLDR is that you need debug symbols for lookup of LdrpInitializeProcesswhich may not be desirable for your case, but NTVDMx64 just has the availability of MS Symbol server as a requirement anyway, so we can hook private symbols, as quite some private functions need to get intercepted to fix bugs in them (there is at least 1 different bug in every Windows version, even Windows 11 lol Does M$ even do some testing before putting out a new Windows version..? ).

You said that Shims are too "slow", I don't really know why there should be a performance issue when writing an AppCompat-Shim. can you elaborate on that? There is an example for writing custom shims: https://github.com/Fleex255/CustomShim I also wrote my own shim engine DLL, beacuse I didn't like the example being in C++ and wrote a plain C example. If you are interested, I can upload the source for the Demo-Shim. But unfortunately there can only be 1 custom shim DLL on the system making it hard to place multiple different Shim-DLLs on the same machine without kicking out each other due to the naming restrictions for the DLLs that get accepted as Shim DLLs.

leecher1337 avatar Apr 03 '22 15:04 leecher1337

Thank you again. Well, no, I didn't mean that shims are "slow", quite the opposite: shims are surely executed before anything else, if you define a shim to wrap a system call there is no risk that the call could be run unwrapped and with no shim execution. Probably this is because shims are coordinated by the loader, after all M$ wrote both the loader and the shims engine, and Bill G. can do whatever it likes in its own OS. The problem instead is that my DxWnd wrappers are hooked while some part of the program is in execution, so if the hooking process is not fast enough it is possible that some calls could run unwrapped.

If the METHOD_HOOKLDR is more reliable, it could be worth adding it to DxWnd hooking set even if it could work only on a limited number of cases, you never know that it could become handy. I'll have some more interesting reading in your source code.

The idea of writing a custom shim is interesting. Since shims have a good control of operation timing, I was wondering if it was possible to write a shim that, on its execution, would load dxwnd.dll to do the rest of the job while the process is stopped. That would be the final weapon, the DxWnd "shim injection" mode. Am I too optimistic?

Good, that's all for now, I'll have to read and get prepared for next round!

P.s. as in all other contributions, I'd like to add a reference for yours in DxWnd thanks list. Would that be ok for you? Is the nick "Leecher1337" good for you or would you propose some other name?

ghotik avatar Apr 03 '22 21:04 ghotik

Shims - as far as I know - are designed to hook certain functions that you also have to define in the sdb so that it gets loaded, but you could try to use the SN_STATIC_DLLS_INITIALIZED notification hook of the NotifyShims callback for your purposes. Here was some coding attempt by me to write a shim for the pchess program, for instance, may be interesting for reference purposes: pchessshim.zip

Regarding APC injection, I suppose the timing problem is due to the fact that the Shellcode just does a CreateProcess in the target thread, but does not do LoadLibrary directly. I wrote some simple code that does LoadLibrary directly, you can try it directly if you also have timing problems with this piece of code:

// Needed on newer Windows versions (>Vista) to initialize Activation Context 
static __declspec(naked) __declspec(code_seg(".text$1")) void  shellcode(ULONG_PTR parameter)
{
	_asm {
		xor ecx, ecx						; zero ECX
		mov eax, dword ptr fs:[24]			; Get the current TEB
		cmp dword ptr [eax+424], ecx		; Is the TEB ActivationContextStackPointer pointer NULL ?
		jne cont							; If there already is an ActivationContext structure setup, just continue
		call goon                           ; Push current addr on stack
		goon:
		pop ebx								; get current address 
		lea edx, dword ptr [ebx+16]			; calculate the address of our dummy ActivationContext
		mov dword ptr [eax+424], edx		; and set the address of our dummy ActivationContext in the current TEB
		cont:
		push 0xAAAAAAAA						; Set LoadLibraryW address
		ret
		ActCtx:								; Empty activation context 
		_emit 0x00 
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
	}
}

#define SHELLCODE_SIZE  0x3D
#define SHELLCODE_LOADLIB_OFFS 0x20

#pragma warning(disable : 4996)
void InjectAPC(char* exepath, char* dirpath, BOOL bSuspended, BOOL bCommitPage)
{
	STARTUPINFO sinfo;
	PROCESS_INFORMATION pinfo;
	char DebugMessage[1024];
	BOOL bKillProcess = FALSE;
	HANDLE hEvent;
	char* eventName = "DxWndInjectCompleted";
	DWORD t0 = GetTickCount();
	LPSTR lpCommandLine = NULL;
	BOOL APCret;

	OutTrace("InjectAPC: exe=\"%s\" dir=\"%s\" commit=%x\n", exepath, dirpath, bCommitPage);

	hEvent = CreateEvent(NULL, FALSE, FALSE, eventName);
	if (!hEvent) {
		if (ERROR_ALREADY_EXISTS == GetLastError()) {
			hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, eventName);
		}
		if (!hEvent) {
			OutTrace("CreateEvent %s failed: err=%d\n", eventName, GetLastError());
		}
	}

	ZeroMemory(&sinfo, sizeof(sinfo));
	sinfo.cb = sizeof(sinfo);
	// attempt to load the specified target
	// v2.05.21: setlpCommandLine together with lpApplicationName for games that requires it,
	// for instance "Star Wars Rogue Squadron 3D"
	// v2.05.23: made this configurable with SETCMDLINE flag
	// v2.05.53: made configurable by field copy and fixed argument order!!
#if 0
	if (pm->cmdline[0])
		lpCommandLine = pm->cmdline;
#endif

	if (!CreateProcessA(exepath, lpCommandLine, 0, 0, FALSE, CREATE_SUSPENDED, NULL, dirpath, &sinfo, &pinfo)) {
		int err = GetLastError();
		char* errmsg;
		switch (err) {
		case ERROR_DIRECTORY:
			errmsg = "(invalid directory name)";
			break;
		case ERROR_FILE_NOT_FOUND:
			errmsg = "(file not found)";
			break;
		default:
			errmsg = "";
			break;
		}
		sprintf(DebugMessage, "CreateProcess \"%s\" \nerror=%d %s", exepath, err, errmsg);
		MessageBoxExA(0, DebugMessage, "Injection", MB_ICONEXCLAMATION | MB_OK, NULL);
		OutTrace("%s\n", DebugMessage);
		CloseHandle(hEvent);
		return;
	}

	// Injection code start
	WCHAR dllpath[MAX_PATH];
	LPVOID lpTgtDllName;
	DWORD dwLenDll = GetFullPathNameW(L"dxwnd.dll", MAX_PATH, dllpath, NULL);
	DWORD dwVersion;
	BOOL fSuccess = FALSE;
	if (dwLenDll)
	{
		HMODULE hKrnl32 = GetModuleHandleA("kernel32.dll");
		LPVOID lpLoadLibrary = GetProcAddress(hKrnl32, "LoadLibraryW");
		DWORD dwReserve, dwPerm = PAGE_READWRITE;

		dwReserve = dwLenDll = (dwLenDll + 1) * sizeof(WCHAR);
		dwVersion = GetVersion();
		if (LOBYTE(LOWORD(dwVersion)) >= 6)
		{
			dwReserve += SHELLCODE_SIZE;
			dwPerm = PAGE_EXECUTE_READWRITE;
		}
		if (lpTgtDllName = VirtualAllocEx(pinfo.hProcess, NULL, dwReserve,
			MEM_RESERVE | MEM_COMMIT, dwPerm))
		{
			if (WriteProcessMemory(pinfo.hProcess, lpTgtDllName, dllpath, dwLenDll, NULL))
			{
				if (LOBYTE(LOWORD(dwVersion)) >= 6)
				{
					ULONG_PTR ulAddrShellcode = (ULONG_PTR)lpTgtDllName + dwLenDll;
					if (WriteProcessMemory(pinfo.hProcess, (PVOID)ulAddrShellcode, &shellcode,
						SHELLCODE_SIZE, NULL) &&
						WriteProcessMemory(pinfo.hProcess, 
							(PVOID)((ULONG_PTR)ulAddrShellcode + SHELLCODE_LOADLIB_OFFS),
							&lpLoadLibrary, sizeof(DWORD), NULL))
					{
						fSuccess = QueueUserAPC(ulAddrShellcode, pinfo.hThread, lpTgtDllName);
					}
				}
				else fSuccess = QueueUserAPC(lpLoadLibrary, pinfo.hThread, lpTgtDllName);
			}
			if (!fSuccess) VirtualFreeEx(pinfo.hProcess, lpTgtDllName, dwReserve, MEM_RELEASE);
		}
	}
	// Injection code end

	if (ResumeThread(pinfo.hThread) == (DWORD)-1) {
		sprintf(DebugMessage, "ResumeThread error=%d at:%d", GetLastError(), __LINE__);
		MessageBoxExA(0, DebugMessage, "Injection", MB_ICONEXCLAMATION | MB_OK, NULL);
		OutTrace("%s\n", DebugMessage);
	}

	CloseHandle(pinfo.hThread);
	CloseHandle(pinfo.hProcess);

	CloseHandle(hEvent);
}

Adding the nick leecher1337 to the list of contributors is fine, as I also go by this nick as author for NTVDMx64 ;-)

leecher1337 avatar Apr 04 '22 22:04 leecher1337

Some quick and short comments ...

  1. I tried to compile and replace the current InjectAPC code with this new interesting release, but I got two problems: on VS2008 the statement __declspec(code_seg(".text$1")) is not recognized and without it the procedure doesn't work. The effect is that the target program seems blocked. I don't know if the missing code_seq is the culprit, maybe not because I was on Win7, so in an earlier OS than Vista. Anyway, it's really too early to draw conclusions.

  2. the first InjectAPC is working quite well, so far we found only an interesting case where (sometimes) the original injection was better. The old game "South Park" (a really bad beast!) fails at startup with APC because it fails when trying to allocate a memory segment at 0x10010000 that is probably taken by the APC stub. I was just wondering if it could be possible to free the stub memory before loading the dxwnd.dll that, in turn, will fire DllMain with the hooking routines.

If you are curious, you can read more at https://sourceforge.net/p/dxwnd/discussion/general/thread/678da4be84/#983a

update on issue 1) - it was necessary to uncomment these lines if (pm->cmdline[0]) lpCommandLine = pm->cmdline; this way (and with deleted __declspec(code_seg) some target works on Win7, but many others stay locked for a while and then terminate. This should mean that the logic is sound, but probably there's some event not handled properly. I'll try to find what.

ghotik avatar Apr 05 '22 13:04 ghotik

Without the code_seg declaration, it could happen that it copies some crap to the taget intead of the real code. I suggest you replace the shellcode() procedure with:

static BYTE shellcode[] = {
	0x33,0xc9,0x64,0xa1,0x18,0x00,0x00,0x00,
	0x39,0x88,0xa8,0x01,0x00,0x00,0x75,0x0f,
	0xe8,0x00,0x00,0x00,0x00,0x5b,0x8d,0x53,
	0x10,0x89,0x90,0xa8,0x01,0x00,0x00,0x68,
	0xaa,0xaa,0xaa,0xaa,0xc3,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00 };

leecher1337 avatar Apr 05 '22 15:04 leecher1337

Regarding the proposal to free the memory block, I fear this isn't possible, because it could be that the thread's activation context lies within the allocated memory block and would become an invalid pointer once you free the allocated memory block (which is a challenge on its own). However you can control the address of the allocated memory block in the target process (second parameter of VirtualAllocEx), so you could make it configurable to ensure that you are not taking an address that the game expects to allocate. The Method I presented in the previous post has the advantage that it only allocates one memory block and not multiple blocks, so lowers the risk of collissions.

leecher1337 avatar Apr 05 '22 15:04 leecher1337

changing the shellcode array doesn't seem to give any benefit (even a task that was working before is now failing). I was wondering if it was possible to change the segment attributes on the fly after it was allocated, making a programmatic "code_seg" equivalent. Anyway, all these are details, the original schema works better than expected!

ghotik avatar Apr 05 '22 15:04 ghotik

You mean VirtualProtectEx ?

Aha, interesting, do you have any simple test cases where the code above with direct loadlibrary call fails? I'm just curious.

leecher1337 avatar Apr 05 '22 15:04 leecher1337

Sure, as a matter of fact I got a single case where the new procedure works (Soccer Manager '97) so I think you can run any program (or video game) with no risk to miss the trouble. Here in attach the InjectACP.cpp file modified by me. I did the following:

  1. #if-ed the old working code to have all available just changing one #if 0 int #if 1 statement
  2. recovered the pm and tm arguments, necessary for DxWnd interface. You can revert this change for your tests
  3. added a VirtualProtectEx to get PAGE_EXECUTE_READWRITE attributes (was that right? The call throws no error, but ...)
  4. added some redundant error messages, just in case. For standalone test you can delete them of #define OutTrace as printf

But I'd like also to ask your opinion about another "thick" problem. Some games are obfuscated, that is they show a stripped PE table with minimal entries that, once run, unzip themselves and populate the actual PE table before running the code. In this case, hooking by PE editing fails and I have to use what I call a "hot patching" method by patching a jump/call in the target function' prologue (using MinHook). The drawback is that this way I have to blindly patch every call without knowing if it was referenced or not. Have you ever had such a problem? Is there some way to tell when the code deflating is finished to defer the PE analysis until when the scene is complete?

InjectAPC (2).zip

ghotik avatar Apr 05 '22 20:04 ghotik

I don't really understand the use of VirtualProtect in your modified code:

		DWORD dwReserve, dwPerm = PAGE_READWRITE;

So I default to PAGE_READWRITE on Windows XP and below, because there is no ActivationContext to fix there, so we don't need the shellcode and can simply inject LoadLibraryW as APC

		if (LOBYTE(LOWORD(dwVersion)) >= 6)
		{
			dwReserve += SHELLCODE_SIZE;
			dwPerm = PAGE_EXECUTE_READWRITE;
		}

On Vista and above, we need the shellcode, so the page doesn't just contain the DLL name (where READWRITE would be enough), but also the shellcode, therefore the page permission is changes to EXECUTE_READWRITE.

Now the page gets allocated with the given permissions:

		if (lpTgtDllName = VirtualAllocEx(pinfo.hProcess, NULL, dwReserve,
			MEM_RESERVE | MEM_COMMIT, dwPerm))
		{

And now, I don't understand this. The page is already allocated with the necessary permissions, why do you change them afterwards (most likely to the same permissions the page already has)? I don't get it...

			DWORD OldProtect;
			//if(!VirtualProtectEx(pinfo.hProcess, lpTgtDllName, SHELLCODE_SIZE, PAGE_EXECUTE_READ, &OldProtect)){
			if(!VirtualProtectEx(pinfo.hProcess, lpTgtDllName, dwReserve, PAGE_EXECUTE_READWRITE, &OldProtect)){
				OutTrace("InjectACP: VirtualProtectEx ERROR err=%d\n", GetLastError());
			}

Can you explain the reason why you do this?

leecher1337 avatar Apr 05 '22 21:04 leecher1337

Hm, I tried the copy of your code, added a starter to it and ran it as a standalone starter for various applications (i.e.:

test.exe c:\windows\system32\calc.exe test.exe c:\windows\system32\notepad.exe test.exe c:\windows\system32\taskmgr.exe

and dxwnd.dll from same directory was always inected on my Windows 7 machine: test.zip

So providing an example (preferably something that doesn't need to get installed) would still be interesting.

leecher1337 avatar Apr 05 '22 21:04 leecher1337

Regarding OEP detection of packed/crypted eexecutables, I guess that inline hooking (that you described as "hotpatching") isn't a bad idea, as various packers have a different workflow, so it's not really clear on how to detect when OEP is reached. There are various OEP finders out there that can be of help, but imho it's too specific for the used exe packer / crypter. You can try to rely on seomething that happens in the entry point, i.e. entering an alertable wait state so that a queued APC would get executed then, or using WaitForInputIdle() works so that hook fires on first GetMessage() call, but that may be too late for you and not every application does it. An unpacker most likely will not enter an alertable wait state, as it is busy with unpacking, I'd assume. But I don't think that there is a reliable generic way to find the OEP.

leecher1337 avatar Apr 05 '22 22:04 leecher1337

About my improper use of VirtualProtectEx, my bad, it was a wrong attempt to make a programmatic "code_seg" directive, though I must admit that it's not clear to me what is the effect of that __declspec mode and how would it be possible to implement it by code. Please, forget it. About a test case, I'll try to suggest something, but today is going to be another busy day (sic!) ... Probably some game RIP (like those in old-games.ru) should let you control both size and registry activities.

About OEP detection of packed/crypted executables, I feared that and you just confirm my suspects.

ghotik avatar Apr 06 '22 08:04 ghotik

The code_seg directive is just because, at least in debug builds, it sometimes copies bullshit when specifying the address of the function to copy, so keeping it in a seperate segment of the executable remedys this problem. Of course, if you just use the shellcode as BYTE[] array, this is not necessary at all.

leecher1337 avatar Apr 06 '22 15:04 leecher1337

Oh, my! All of the sudden your code started to work .... after this small fix:

	//dwReserve = dwLenDll = (dwLenDll + 1) * sizeof(WCHAR);
	dwLenDll = (dwLenDll + 1) * sizeof(WCHAR);
	dwReserve = dwLenDll;

I think the reason is clear: your compiler interprets the statements right to left, while my vs2008 probably does it left to right. This way dwReserve is assigned to dwLen before than dwLen is incremented and doubled, so the valued space is not enough to hold everything. Separating the statements in two lines of code makes it unambiguous.

Now it is possible to compare the features of the two APC methods. This last method was proposed as a method able to hook at an earlier stage which makes it quite interesting. But it seems that it doesn't work with all games, there are many of them that don't get run as if the procedure could crash the game in certain circumstances. I am planning to modify the Dxwnd GUI so that it will be easier to add or remove hook procedures, something like the current renderer selection.

ghotik avatar Apr 06 '22 20:04 ghotik

Ah, interesting! I didn't check K&R about that, but I guess, behaviour is indeed undefined in this case, so compiler implementation dependent. Good to know that such constructs should be avoided then.

For testing on why games crash on startup, you could use the launcher code, remove "ResumeThread", attach x32dbg to the target game, and then resume thread from the debugger to check why it crashes there (or what bad things it does). I restricted ldntvdm to only link with kernel32.dll and ntdll.dll, as they both should be already loaded at the time of injection, in order to not pull in any other DLL dependencies at this early stage of injection. Not sure if this really helps against potential problems, but I thought that it couldn't hurt. Anyway, I think the debugger will help identifying potential problems with early injection.

leecher1337 avatar Apr 06 '22 23:04 leecher1337

Another bit of the mystery is revealed: this morning I started moving the new, working code on Win7 with some GUI update to make testing more comfortable. With my despair, nothing was working, not even the old procedures for the few games that seemed to accept APC injection. Then, a bit of insight: I turned the AV off and things started to work again. So, it seems that APC injection is so alike viral code that the AV stops it. I was used to the AV setting DxWnd.exe as unsafe and to add exceptions, but apparently the exception doesn't prevent Avast to make some run-time checks and kill the suspicious activity. This is quite an annoyance, because making tests with the AV protections turned off is not healthy, considering that together with DxWnd code we're going to execute old crapped games hacked by who knows who!

ghotik avatar Apr 07 '22 09:04 ghotik

Honestly, I consider all this Antivirus-Crap just snakeoil that doesn't have any real use. It slows down the machine with all its realtime scans, it gives loads of false positives (my personal best was a simple C program that I wrote to enumerate network shares and reconnect them, as Windows often suffers from a problem that network drives are not reconnected on startup, only on first use, so it was just a useful simple utility - 50% (!!) of all AV-scanners on virustotal.com flagged it as malware - unbelievable!), it wrongly flags cracks and keygens as malware with partly obscure names (Kaspersky doesn't do that kind of crap, but i.e. Avira is notorious for making intentionally wrong signatures), partly has buggy filter drivers that cause system hangs, etc. I'm OK with offline signature based scanners, but all this realtime protection crap is just a burden that can and should be avoided imho. Just my personal opinion.

leecher1337 avatar Apr 07 '22 10:04 leecher1337

If you are curious, here https://sourceforge.net/p/dxwnd/discussion/general/thread/678da4be84/#a262 I uploaded a DxWnd upgrade (it should work also with these two files alone) with the new inject modes that, with little fantasy, I called "Inject ACP" and "Inject ACP2". Today (I say today because I'm now used to see test results changing in all possible ways ...) on Win7 the inject ACP works pretty well with the AV turned off, while the inject ACP2 doesn't work at all (despite the fact that yesterday, on Win11 and the code fix it seemed to work in most cases!). I will attach here also the source code of the two procedures. As you may see, I didn't change very much. Question: could the use of libc calls (like fopen , sprintf used to write logs) inhibit the loading of dxwnd.dll in ACP injection? I am skeptical, because the random behavior, if that was a problem why should the ACP2 work on Win11? InjectAPC2.zip

ghotik avatar Apr 07 '22 12:04 ghotik

Some better (and less bugged) files here: https://sourceforge.net/p/dxwnd/discussion/general/thread/678da4be84/?page=1#7fcb But maybe it's better that we stabilize the situation a little better, I fear that because of some bugs of mine I made some mess here.

I got a few doubts and I humbly ask your opinion: I see that the ACP2 shellcode block uses some registers (ecx, eax and others) but it doesn't preserve their value before the shellcode execution with some push & pop instructions. Is it possible that depending on the OS or the target program there registers could be vital? You overwrite the DWORD 0xAAAAAAAA in the shellcode with the address of the LoadLibraryW system call that you retrieve in the DxWnd.exe address space. But the shellcode is going to be executed in the target address space, so are we sure that the LoadLibraryW address so retrieved is valid in the new context?

ghotik avatar Apr 07 '22 19:04 ghotik

Usually, a task hooked with ACP2 dies immediately, so it is impossible to attach it to a debugger (I use OllyDBG) but launching it repeatedly you can get multiple instances locking each other that can be inspected. This happens now on my Win7 computer. I hope that the peculiar test condition don't invalidate some possible conclusions. This is what I saw attaching one of them: acp2dump2 Here it is possible to see what you would expect, the WIDECHAR full path of dxwnd.dll and the shellcode with 0xaaaaaaaa replaced by the address 0x76D548F3. That is supposed to be the address of kernel32 LoadLibraryW, but trying to follow the pointer I get into a unqualified procedure that leads to a bad address. The memory dump shows that there is no kernel32 text segment (yet). BTW (unseen) the debugger declares a unhandled exception accessing address 0x80000008, which is in the kernel memory segment. acp2dump3 Could it be that the problem for this early hook is that it just runs too early?

ghotik avatar Apr 08 '22 07:04 ghotik

Ah, good analysis, so I'd say you are right with your conclusion that at the time the code runs, there is no kernel32.dll yet. So maybe the APC fires in the loader at a position where DLLs are not bound yet (call stack would be useful to see that). In APC1, we create a thread so the code will run after next task switch, so probability that it runs too early may be lower, so that may be the reason why it is more reliable. The crash can easily be fixed by using NTDLL's LdrLoad... function, but it wouldn't be of much use for your usecase, I guess, because DXWND.DLL should run AFTER imports were bound by the loader, otherwise you wouldn't find what you are looking for. Maybe it could be rewritten so that it checks the current state of progress of the loader and requeue the APC if it runs too early, but I guess I'm lacking reliable test cases for that, because it may be random when the APC gets executed.

Another method that could work pretty reliably is registering yourself as a debugger and launch the process with debug flags, then you get called on application entry point reliably. I didn't use this method for ldntvdm, because it would possibly prevent a real debugger from working, but for your usecase, it may be a good method, because you only inject into 1 target. Should I try to write some demo code for that method?

leecher1337 avatar Apr 08 '22 10:04 leecher1337

I wrote something that resembles what you proposed, a debugger skeleton just to inject the dxwnd.dll code. It works, but it is a little less reliable that the other methods, maybe because I didn't handle all the many events that could happen during a debug session. If you want, you may start from this file belonging to DxWnd project already, maybe you can see some flaw here ... InjectDebug.zip

ghotik avatar Apr 08 '22 10:04 ghotik

Ah, I see, I must have overlooked it that this is already implemented. You put an infinite loop at EIP and then check when target has reached it. Just out of couriosity: Is there an advantage over placing an INT 3 there? With other methods, INT 3 would have undesirable effects, but as a debugger, you would get notified about it, right? But generally, acting as a debugger should be pretty robust that makes me wonder why it is a bit unreliable. There is no timing issue as it can happen with APCs as you always reach a pre-defined point as a debugger. Just makes me wonder in which cases this can fail.

leecher1337 avatar Apr 08 '22 10:04 leecher1337

Well, now on Win7 it seems pretty good in this release, but after a few attempts with a dozen different games I found a problem in "Premier Manager 97". The game gets hooked, but some desktop resolution setting operation escapes my hooks and the video mode changes, while this doesn't happen with the ISP injection, nor with your ACP method. This is the DxWnd GUI log about two attempts, the first with ISP and the second with ACP

isp vs debug.txt

As you can see there's a lot of events to process. The dxwnd.dll should happen (unlogged) after the process creation, you may notice a second load of dxwnd.dll later at [00000:718], but this should be the redundant window hook that gets executed when it's too late.

Update: I repeated the tests disabling the redundant window hook and - surprise - the debug hook is practically useless, it seems to always fail doing its job. The only difference with ACP2 is that it doesn't crash the program, so you don't notice. Ach!

ghotik avatar Apr 08 '22 11:04 ghotik

Uhm... I repeated the test with window hook disabled and the load dll message about dxwnd.dll disappeared. But there should be one right after the process creation, I wonder why it's not there. It seems that the target program never loaded this lib! debug_alone.txt Sadly, one eerie characteristic of this method is that you can't have two debuggers attached to the same process, so if you inject via debug mode you can't also debug to see what's going on :(

ghotik avatar Apr 08 '22 11:04 ghotik

I got it! The problem was in the Inject function within dxwnd.dll where I freed (with VirtualFreeEx) the allocated buffer where the dxwnd.dll path was copied. The operation was deferred by 500mSec that is enough or not depending on the case, so I decided to swipe that call away and everything works much better. The inject procedure is used by many hook modes, so I revised quickly some results. With the fixed dxwnd.dll:

  • ACP2 still doesn't work (it was to be expected)
  • debug mode generally works
  • debug mode with "Premier Manager 97" works partially: the game gets hooked, but the video mode changes

In the new log yu can see that now dxwnd.dll is effectively loaded, but the method can't force the loading sequence, so it is possible that one of the previously loaded dlls has a DllMain procedure that sets a video mode and the toy is broken. In effect I got a suspect that proved to be right: in this case the culprit and unwanted code comes from a Microsoft shim 640x480 applied without being asked for! I renamed the executable filename (with the DxWnd noshim flag, but it doesn't matter how) and the debug mode worked also in this case. Well, it's a difficult world. pm97_alone.txt

ghotik avatar Apr 08 '22 12:04 ghotik

If you are still interested in injection problems and difficult cases, I just found one with "Jane's Fleet Command" (discussion also here: https://sourceforge.net/p/dxwnd/discussion/general/thread/678da4be84/?page=1#c0e4 ). The game seems to load preferably glide2x.dll for rendering, withdrawing on ddraw.dll if a 3Dfx card (or a glide emulator) can't be found. DxWnd has a flag to mimic a failure when trying to make a LoadLibrary("glide2x.dll") operation, but the game apparently succeeds loading it because the loading happens before that dxwnd.dll is injected and run. Since no static linkage can be found, I believe this is because a LoadLibrary in buried within the DllMain procedure of some of its linked dlls. Running the game with Debugger injection, in effect you can see that the glide2x.dll module was loaded before dxwnd.dll, and I can't think of a way to prevent this to happen! dxwnd.gui.fleet.log

ghotik avatar Apr 09 '22 21:04 ghotik

Another curiosity about ACP injection: in my Win11 the difficulties to make an early hook seem increased, also turning the AV off I got games that refused the hook. Then, by chance, I put a MessageBox in the middle of the ACP injection steps and it worked. Maybe the MessageBox provided the necessary suspended state that is necessary for the injected thread to be scheduled. Since the MessageBox call is based on a timed-out inner routine MessageBoxTimeout it could be possible to increase the effectiveness of ACP injection by simply adding a timed-out message like "Wait 1 second ..." and then going on.

update: tested & working. This is the code added just before the ResumeThread call in the ACP injection routine. I set a 500 mSec timeout, but I saw it working also with smaller delays. HMODULE hUser32 = GetModuleHandle("user32.dll"); if (hUser32){ typedef int (WINAPI *MessageBoxTimeoutA_Type)(HWND, LPCSTR, LPCSTR, UINT, WORD, DWORD); MessageBoxTimeoutA_Type pMessageBoxTimeoutA; pMessageBoxTimeoutA = (MessageBoxTimeoutA_Type)GetProcAddress(hUser32, "MessageBoxTimeoutA"); if(pMessageBoxTimeoutA) (*pMessageBoxTimeoutA)(NULL, "wait ...", "DxWnd", 0, 0, 500); }

update 2: after writing this code I felt a little stupid and thought: "if all we need is a small delay, why don't do that silently with a Sleep(500) call?". Well, as a matter o fact there is a good reason: the Sleep doesn't fix the problem. Probably it is some obscure reason about context switching or entering kernel mode, anyway MessageBoxTimeoutA worked and Sleep didn't!

ghotik avatar Apr 12 '22 16:04 ghotik

Hmm, SleepEx just does a NtDelayExecution and indeed would trigger a Task switch. Maybe it's GetMessage inside MsgBox that helps? Just as a stupid idea: Did you try with PeekMessage / GetMessage ?

leecher1337 avatar Apr 14 '22 07:04 leecher1337

I tried right now with this code replacing the timed message-box:

	MSG msg;
	GetMessage(&msg, 0, 0, 0);
	DWORD tick0 = GetTickCount();
	while((GetTickCount() - tick0) < 500)  GetMessage(&msg, 0, 0, 0);

The result is puzzling: depending on who knows what, the trick works or doesn't work. The feeling is that it works better at first game start, but as soon as you feel you got a schema, next time the behavior is completely different. The thing doesn't change if I turn the AV off or I increase the 500 mSec timeout.

ghotik avatar Apr 14 '22 20:04 ghotik

Maybe it has to do with your file system, I don't know. Not reproducable here.

You can enable YODA, start vdmdebug, set registry key so that NTVDM breaks on startup into YODA, start the applicationn, attach a debugger (x32dbg) to ntvdm.exe, grab ntvdm.pdb symbols, continue execution in yoda and step through ntvdm.exe to see where it quits.

Hi @leecher1337 it's been some time and I didn't want to interrupt this discussion of ACP. I tried different applications in this time and it seems no DOS application work in Program Files folder in any partition. I want to follow your instructions but I didn't manage to figure out how to do them. Can you please specify better?

BEENNath58 avatar Apr 29 '22 07:04 BEENNath58

First of all, you can check for potential file access errors using Sysinternals ProcessMonitor and setting process to ntvdm.exe, maybe its results lead you to some wrong path access or something like that.

Otherwise, to debug NTVDM:

  1. Ensure that you installed checked build
  2. Run inst-sym.cmd from release so that debug symbols get installed.
  3. There is a YODA.reg file in reg\ folder of the release. Merge it into your registry. From now on, NTVDM will not start up normally but break into YODA debugger. If you check the contents of the registry file or the docs, this has to do with setting YODA=1, so as soon as you don't want YODA to break anymore, you have to remove the key YODA=1 from CpuEnv in registry again! Also be aware that if no vdmdbg is running, ntvdm will be cought in an endless loop with 99%, if that happen (spamming debugvie output with Yoda> prompts), so you have to kill ntvdm.exe via task manager.
  4. Startup vdmdebug.exe
  5. Start your target application You will see a Yoda> prompt in vdmdebug window.
  6. Attach a debugger to ntvdm.exe, i.e. x32dbg
  7. Now you can continue in Yoda by entering c command
  8. If x32dbg breaks with some notifications like STATUS_SEGMENT_NOTIFICATION, you can skip them with SHIFT+F9
  9. if ntvdm.exe crashes, you will end up in x32dbg at the point of crash. If it just silently quits, you may have to set various breakpoints in ntvdm.exe in order to check the flow. This would require you to study NTVDM sourcecode in order to know where you cna place breakpoints to catch certain error conditions. There is no generic hint I can give you, unfortunately. Maybe you can also spot something in the YODA window. Generally: If you want to debug code INSIDE the NTVDM, so DOS-code, you have to get used to YODA and use it for that. If you want to debug NTVDM, use x32dbg. As it's a path issue, you may want to set breakpoints at KERNE32.CreateFile API, but it's just a guess.

leecher1337 avatar Apr 29 '22 09:04 leecher1337