Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extreme Paintbrawl crash and DxWnd APC injection #182

Open
BEENNath58 opened this issue Mar 28, 2022 · 67 comments
Open

Extreme Paintbrawl crash and DxWnd APC injection #182

BEENNath58 opened this issue Mar 28, 2022 · 67 comments

Comments

@BEENNath58
Copy link

BEENNath58 commented Mar 28, 2022

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)

@leecher1337
Copy link
Owner

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?

@BEENNath58
Copy link
Author

BEENNath58 commented Mar 29, 2022

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

@leecher1337
Copy link
Owner

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
Copy link
Owner

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

@leecher1337
Copy link
Owner

leecher1337 commented Mar 29, 2022

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
Copy link
Owner

FreeLibrary(hDirectDraw):

image

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

image

@leecher1337
Copy link
Owner

leecher1337 commented Mar 29, 2022

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
Copy link
Owner

leecher1337 commented Mar 29, 2022

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
Copy link
Owner

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

@BEENNath58
Copy link
Author

BEENNath58 commented Mar 30, 2022

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
Copy link
Author

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

@ghotik
Copy link

ghotik commented Mar 30, 2022

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.

@leecher1337
Copy link
Owner

leecher1337 commented Mar 30, 2022

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.

@ghotik
Copy link

ghotik commented Mar 30, 2022

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!

@leecher1337
Copy link
Owner

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
Copy link
Owner

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

@BEENNath58
Copy link
Author

BEENNath58 commented Mar 31, 2022

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).

@leecher1337
Copy link
Owner

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?

@BEENNath58
Copy link
Author

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

@leecher1337
Copy link
Owner

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?

@BEENNath58
Copy link
Author

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

@leecher1337
Copy link
Owner

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.

@BEENNath58
Copy link
Author

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

@leecher1337
Copy link
Owner

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.

@BEENNath58
Copy link
Author

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.

@leecher1337
Copy link
Owner

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.

@BEENNath58
Copy link
Author

BEENNath58 commented Apr 1, 2022

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!

@leecher1337
Copy link
Owner

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

@BEENNath58
Copy link
Author

BEENNath58 commented Apr 1, 2022

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.

@leecher1337
Copy link
Owner

leecher1337 commented Apr 1, 2022

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.

@ghotik
Copy link

ghotik commented Apr 5, 2022

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.

@leecher1337
Copy link
Owner

leecher1337 commented Apr 5, 2022

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
Copy link
Owner

leecher1337 commented Apr 5, 2022

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.

@ghotik
Copy link

ghotik commented Apr 5, 2022

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!

@leecher1337
Copy link
Owner

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.

@ghotik
Copy link

ghotik commented Apr 5, 2022

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

@leecher1337
Copy link
Owner

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
Copy link
Owner

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
Copy link
Owner

leecher1337 commented Apr 5, 2022

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.

@ghotik
Copy link

ghotik commented Apr 6, 2022

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.

@leecher1337
Copy link
Owner

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.

@ghotik
Copy link

ghotik commented Apr 6, 2022

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.

@leecher1337
Copy link
Owner

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.

@ghotik
Copy link

ghotik commented Apr 7, 2022

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!

@leecher1337
Copy link
Owner

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.

@ghotik
Copy link

ghotik commented Apr 7, 2022

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
Copy link

ghotik commented Apr 7, 2022

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
Copy link

ghotik commented Apr 8, 2022

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?

@leecher1337
Copy link
Owner

leecher1337 commented Apr 8, 2022

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?

@ghotik
Copy link

ghotik commented Apr 8, 2022

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

@leecher1337
Copy link
Owner

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.

@ghotik
Copy link

ghotik commented Apr 8, 2022

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
Copy link

ghotik commented Apr 8, 2022

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
Copy link

ghotik commented Apr 8, 2022

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
Copy link

ghotik commented Apr 9, 2022

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
Copy link

ghotik commented Apr 12, 2022

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!

@leecher1337
Copy link
Owner

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 ?

@ghotik
Copy link

ghotik commented Apr 14, 2022

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.

@BEENNath58
Copy link
Author

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?

@leecher1337
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants