Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
uhbooh

EXE hacking

Recommended Posts

Ok, this has got to be the coolest, most bad-ass post I've seen in a long time! I'm dying to check out what your patcher is doing, and how you're getting everything to work, despite the awful run-time relocation stuff to deal with.

 

Great stuff - I'm massively impressed! Cacoaward!!

 

Share this post


Link to post

The call/pop trick was actually originally mentioned by budko (the prboom+ coder, also the doom+ series creator) on #doom-tech or somewhere else on IRC I was lurking years ago, though I think at some point the realization would have dawned upon me regardless*, and you could alternatively also modify the fixup table to make the program loader write the relocated addresses of the start of CS and DS somewhere your code can grab them. (granted this isn't as easy as it could be because the LE format seems to be overall really shitty and stupidly complex, just look at it and compare to eg. PE which has also always had relocation support for DLLs and nowadays ASLR enabled executables, but at least I managed to write that tool earlier to help a bit with this)

*(even if I'm not a particulary expert or experienced programmer or even good or a professional in this field, really I just hack on old games as a hobby sometimes, maybe a bit like some people do puzzles and riddles stuff like that)

 

When you know where the code and data sections of the game binary are in memory at runtime, it's already easy to start doing small changes with some memory reads and writes. You can try this yourself with that newdhack.exe file I just posted inside that zip, just use NASM or something to assemble a stage2.bin which does some movs. When the code in stage2.bin starts, EDX contains the relocation offset for game code and EBX contains it for game data, and in EAX you have the address where the code in stage2.bin was loaded. At the end of the file you just put a RET to go back to the 1st stage loader (which is in the EXE, I overwrite an unused function with it and then overwrote part of the date + password check code on early startup with a call to there). You don't really even need to save/restore any registers yourself because the loader I patched into the game exe does pusha/popa already and the call to stage2.bin is the last instruction before the popa and ret back to original game code.

 

So for example, if you wanted to change plat speed to match the release version (0x40000 vs. 0x10000) you could write this:

BITS 32

org 0

mov [edx + 0x2eb60], byte 0x04
ret

and assemble it with NASM (raw output) to a file you name stage2.bin and put in the same directory with newdhack.exe before you run it in DOSBox.

 

If you look at newdoom.exe in any disassembler that shows you addresses as they would appear in memory without relocation*, you see that this overwrites a byte from the immediate value included in this instruction inside EV_DoPlat:

cseg01:0002EB5B                 mov     dword ptr [eax+10h], 10000h

*(note that the preferred load address without relocation specified in the LE headers for the code section is 0x10000 and not 0, and for the data section it's 0x50000, this is why I already renamed the vars in the C code I posted on last page to csrelo/dsrelo from csbase/dsbase because what stage1 loader really gives you is the relocation offsets = difference vs preferred address, not relocated start of the section, that would be the difference+0x10000 for code or difference+0x50000 for data)

 

That's not a change that would be hindered by DOS4GW rewriting all non-relative address references on startup though, because it's just an immediate value inside an instruction (and the memory write it does is to an address relative to a register but even if it wasn't the relocation record would still hit just the address part of the instruction here) so let's try something else next:

BITS 32

org 0

lea ecx, [edx + 0x248d4]
mov [ebx + 0x5221c], dword ecx
ret

This changes a function pointer inside the menu definitions in data section so that the first selection in the main menu ("Demo Map 1") now activates the previously disabled but hidden in the code options menu. This is something you'd normally need to know to change in the relocation table instead because the function pointer inside the menu defs needs to be rewritten obviously when the game's relocated in memory (it's not a relative reference like jmps/calls back and forth inside the code section).

 

For anybody not even superficially familiar with x86 assembly: note that the lea instruction does not do any real memory access despite the square brackets, it's just used for calculations (it's intended for memory address calculations but can be used for any calculations really, and sometimes it's more optimal to calculations that way, though here you could just do add edx 0x248d4 and use edx as the source in the next instruction if you didn't care about losing the value in edx which is the CS relocation offset).

 

(beware the screen size control in the options menu btw., you can play around it with but if you expand the game view to fullscreen then something bad happens when you quit the game, it's probably overwriting something in memory just before the screen buffer because the view is drawn a bit too high with a few pixels of the status bar still showing in the bottom, I needed to go to a virtual console with ctrl+alt+f2 to kill dosbox because after quitting the game it went into a state where it made X totally unresponsive. maybe it doesn't break on Windows like that but I don't really use Windows anymore because Win10 is such a disaster, so it's difficult for me to say anything for sure about that)

 

None of this really helps with making more significant changes to the game code any less tedious work (where you have to keep track of the byte position of all memory address references in your code all the time) though, unless you can somehow automate the process of always applying address fixups to the correct locations in the replacement code. Because the LE format is annoying to work with I think it's easier to accomplish this via a runtime loader approach than something which would generate fixup records for LE headers automatically.

 

Now I'm really not some secret closet master wizard programmer, as unfortunate as that is (I've completed the first page of puzzles in TIS-100 once but that's it, and even those were often very unoptimal and sometimes silly solutions though they worked), so I gave up on programming a rebasing loader for code chunks of any kind (even for a real simple format I'd "design" myself on the fly) in assembly almost right away and started looking at the possibility to use C for this. Using compiled code for patching Watcom-made game binaries is something I had really wanted to make work earlier already but I gave up on it because I had only been looking at compiler and not linker output, and that format looked just disgusting.

 

Well, now I finally looked at the linker output instead and in the end it turned out it isn't difficult at all to make this work at least on the level seen in the previous post. It's really fortunate that Open Watcom can make PE DLLs if you select the win95 or nt target, it's such a nice and easy format to parse compared to OMF32 or LE/LX, and then I just had to figure out ways to work around the fact that I don't have any kind of runtime dynamic rebasing loader yet in the C code. I cannot really replace ingame functions from compiled code yet though, because how would I pass the pointers to the vars/funcs structs to some code that isn't called from the start function without resorting to doing something silly, like storing them to some fixed address in low DOS memory, video memory or elsewhere where I could maybe get away with it, for later access?

 

edit: well, a fews mins after posting this I realized that I could of course go into the stack in my new function when it's called by the game get some pointers to known locations, and proceed from there... lol, maybe I'll go code that next

 

What I have already is enough for writing a rebasing loader much easier than using pure asm though, maybe for the simple format first and DLLs later, or maybe I'll just attempt DLLs right away.

 

Edited by xttl

Share this post


Link to post

I'm not sure if I ever posted it, but several months ago I was playing around with trying to disassemble / poke at the original EXE. Here's what I have from as far as I got.

 

Quote

address=0x3D830; length=84; funcname="P_GunShot"; exe=~/Desktop/doom2/DOOM2.EXE; printf -v offset "%d" $((address + 0x32014)); xxd -s $offset -o-0x32014 -l $length -g 1 -u $exe; dd bs=1 skip=$offset count=$length if=$exe > ${funcname}.bin; ndisasm -u -o $address ${funcname}.bin > ${funcname}.asm

 

address=0x3D830; length=84; funcname="P_GunShot"; exe=~/Desktop/doom2/DOOM2.EXE; printf -v offset "%d" $((address + 0x32014)); printf "USE32\nCPU 386\n" > ${funcname}.asm; ndisasm -u -o $address ${funcname}.bin | tr A-Z a-z | gsed -r 's/^[0-9a-f]{3}([0-9a-f]{5}). ([^ ]*) *(.*)/l_0x\1:\t\3\t; \2/' | gsed -r 's/j[a-z]{1,2} /&l_/' | gsed -r 's/(^.*call).*; \w{2}(\w{2})(\w{2})(\w{2})(\w{2}).*/\1 \$+5+0x\5\4\3\2/' >> ${funcname}.asm; nasm ${funcname}.asm -w-number-overflow -O3 -o tempfile; if [ "$(md5 -q ${funcname}.bin)" == "$(md5 -q tempfile)" ]; then echo "Recompiled binary is identical to the original"; else echo "Recompiled binary is not the same"; fi; rm tempfile;

 

address=0x3D830; length=84; funcname="P_GunShot"; exe=~/DOOMS/DOOM2.EXE; cp $exe ~/DOOMS/; nasm ${funcname}.asm -O3 -o ${funcname}.bin; dd if=${funcname}.bin of=$exe bs=1 count=$length seek=$offset conv=notrunc

 

Share this post


Link to post

@xttl Let me see if I can understand your difficulties and your system:

 

  1. Pre-patching the exe if difficult, except for tiny patches: Since you don't know where the OS will relocate the code, you cannot use long jumps - all jumps must be relative, and therefore, nearby, making it impossible to add large functions, or subroutines called absolutely.
  2. Patching at runtime via Stage2.bin give you absolute addresses, but you still must figure out where the loader put the Doom code. If you can get the Doom code to call your patch, you can pop the return address off the stack, and determine where the caller is, but, to get the caller to call your patch, you need to know where the caller is. Catch-22!

You said that when stage2.bin starts, it has the location of the Doom code - how do you accomplish this? You also said that upon stage2.bin running,

EDX contains the relocation offset for game code

EBX contains it for game data

 

Can all game code be directly referenced as an offset of EDX? I thought there were lots of relocation entries, perhaps one for every Doom module. Can EDX be used to find all game code? If so, that's beautiful, and it makes much more sense to patch post-relocation.

 

So, basically, you patched the exe to get it to load stage2.bin, then you pop (and then re-push) the return address off the stack and place the address in EDX, and, finally you call stage2.bin which returns to the Doom code you patched. Is this what you're doing? If so, sounds like a nice system, indeed!

Share this post


Link to post
13 hours ago, kb1 said:

@xttl Let me see if I can understand your difficulties and your system:

 

  1. Pre-patching the exe if difficult, except for tiny patches: Since you don't know where the OS will relocate the code, you cannot use long jumps - all jumps must be relative, and therefore, nearby, making it impossible to add large functions, or subroutines called absolutely.
  2. Patching at runtime via Stage2.bin give you absolute addresses, but you still must figure out where the loader put the Doom code. If you can get the Doom code to call your patch, you can pop the return address off the stack, and determine where the caller is, but, to get the caller to call your patch, you need to know where the caller is. Catch-22!

You said that when stage2.bin starts, it has the location of the Doom code - how do you accomplish this? You also said that upon stage2.bin running,

EDX contains the relocation offset for game code

EBX contains it for game data

 

Can all game code be directly referenced as an offset of EDX? I thought there were lots of relocation entries, perhaps one for every Doom module. Can EDX be used to find all game code? If so, that's beautiful, and it makes much more sense to patch post-relocation.

 

So, basically, you patched the exe to get it to load stage2.bin, then you pop (and then re-push) the return address off the stack and place the address in EDX, and, finally you call stage2.bin which returns to the Doom code you patched. Is this what you're doing? If so, sounds like a nice system, indeed!

 

Relative call/jmp works with addresses up to +/- 2GB, so regular function calls are not a problem at all. Function pointers need to use absolute addressing though, as do all references to the data section.

 

The sources posted on the previous page should explain everything pretty well, but here's a summary:

  • exe is prepatched with some new code (138 bytes, asm source is on previous page) and a call to there on startup, the call replaces part of the press beta's date&password check code and the rest i just skip over, so no need for fakedate.com and those batch files anymore.
  • this code overwrites an unused function in the beta exe (D_TimingLoop which does a thing that's very similar to the timerefresh console command in quake if you know it, rotates the view 360 degrees once and then drops to dos with some timing info displayed)
  • i modified the LE fixup table to remove* any fixup records (3 total) which would otherwise cause dos4gw to overwrite parts of this code, because D_TimingLoop references some global vars and a string from the data section
  • prepatched new code uses open/read/close/filelength/malloc code already present in the original exe, no problems here because the relative call instruction works with addresses up to +/- 2GB like mentioned previously. loads a file called "stage2.bin" from the same directory on disk to a malloced buffer, and does an absolute call to the buffer's address
  • call $-5 calls the next instruction right after it (wherever it happens to be in memory), so if you write pop <register> after the call you can get the current EIP value in that register no matter where the code is currently located in memory, no need to peek into the stack deeper than that (nothing I posted on previous page peeks into the stack to get pointers)
  • then you can subtract from that value the address which the pop instruction following the call would have been at if it weren't relocated, and now you have the code section relocation offset, you can use this knowledge to address anything located in the code section
  • read a known data pointer value from somewhere in the code section, subtract from it the expected unrelocated memory address of that data, and now you also know the data section's relocation offset
  • you know what address malloc just gave you for the buffer you loaded stage2.into, that's the base address for all code and data loaded from that external file.
  • then just put or keep these values in the registers before directing execution flow into the buffer (btw. I originally chose ESI = cs offset, EDI = ds offset, EDX = stage 2 base, but changed these after I started experimenting with compiled code because EAX, EDX and EBX are the register watcom's calling convention uses)

 

"Can all game code be directly referenced as an offset of EDX?" <- yes, just add the address you see in IDA or some other disassembler or listing which shows addresses the same way. For example, if the first byte of the code section was at 0x10000 (it's preferred load address), then exit and printf from libc would be at:

cseg01:000436CE exit_
cseg01:00042D40 printf_ 

If the first byte of the data section was at 0x50000 (preferred), then this sound debug string from the original game code would be at:

dseg02:000583EC aSomethingSFuck db 'Something',27h,'s fucked.  Distance to sound < 0.',0

So you could write a stage2.bin which uses printf from the game's libc to print that string from the game, a string from stage2.bin itself and then the libc exit function like this:

BITS 32
org 0

lea ecx, [edx + 0x42d40] ; printf
lea esi, [eax + text]    ; text from this binary
push esi                 ; string pointer for printf
call ecx                 ;
add esp, 4               ;

lea esi, [ebx + 0x583ec] ; "Something's fucked. Distance to sound < 0."
push esi                 ;
call ecx                 ; still points to printf
add esp, 4               ;

lea ecx, [edx + 0x436ce]          ; exit
mov eax, -1                       ; program exit code
jmp ecx                           ; never retuns

text: db "hello from stage2",0x0a,0

(why exit? well I_Quit / I_Error try to do some deinitialization IIRC which is not a good idea at this point in startup)

 

Relocation is done per-section, (not per-page or anything crazy like that luckily, that wouldn't work very well with code anyway because it's better to use relative jumps/calls whenever possible of course you can always rewrite relative pointers too, I was already doing that to patch in calls in the loader when I wrote that but I still keep forgetting it... see, this is why I don't do this as a job :D) so because there are only 2 relevant sections in the executable you really just need 2 offsets to address everything.

 

* really I just made them all clones of another fixup record which fix up addresses nearby, I didn't want to use the tool I made which always regenerates the whole fixup table because that would cause a huge bindiff while manually hex edited this way it's only a 9 byte difference to the original file

 

I am too tired right now, but I'll write more tomorrow. I almost just got replacing a function in the game with compiled C code from the loader working, but there's some strange problems with it and I can't figure out why it's not working at least until I sleep.

Edited by xttl : a couple of small fixes/additions

Share this post


Link to post

@xttl: Thanks! For some reason, your last post really explains it in a way I can more easily understand. I know writing that post (and the posts before it) was a lot of work, and I appreciate it.

 

This is a really neat system. I really want to write a program that breaks down the structure of these types of EXEs, in a way that easily explains all of the runtime relocations, and maybe provides a way to streamline hacks like the work that you're doing. Your process provides a method to bypass the need for most of that, but, as you said, the EXE format is over-complicated, and I'd really want a better understanding of it.

 

Speaking of which, I am going to need to study what you've written and done a lot more, before I can claim to really understand it on a deep level, but, from what I've read so far, this is very slick, man. Who says you aren't a Hacking Wizard? :)

 

@xttl and @Quasar :

Your posts are always interesting, and you're doing very cool stuff! Quasar deserves some credit here too. The beta is a really neat piece of history, and the work you guys have done are really turning it into a new, fully playable game, which is fascinating! Thank you both.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×