---------------------------------------------------------------------------- | DLL Patches - How To | | By Mario "HCl" Brito | ---------------------------------------------------------------------------- - Introduction - When you've reached the limit of game modding by more "normal" means (hex editing supporting files), the next step is to start researching the game EXE. By tweaking Assembly code, we can add several new functionality to the game not possible before, but these modifications can be limited: with the lack of space of most segments there may not be much space to put your assembly code, and the simple fact that you're punching in assembly opcodes into the EXE may be discouraging enough to prevent you from working on a more complex patch. Well, there's a solution for this: DLL Patches. A DLL patch consists on a small DLL which, when loaded by the game, will modify the EXE in memory, re-directing EXE code to functions inside the DLL, where the actual modifications will be made. Since the DLL may be written in C, C++ or any other language (away with the assembly! well for the most part anyway ;) ), it'll be possible to program faster and without space constrains for your new code. Also, fans not too familiar with assembly will be able to expand your work. - Making the game load your DLL - Before we can use a DLL to patch Wing Commander games (i'll give the example of Wing Commander Secret Ops, and the Unknown Enemy patches), we'll need to modify the game to load one of our DLLs. There may be several approaches for this problem, but i choose to patch the EXE exactly at it's entry point. There we jump to a small routine that loads the DLL into memory, restore the entry point to it's original state, and jump to it, leaving the EXE run as it should from this point on. So we must determine two things: where's the EXE entry point (any tool such as IDA, W32Dasm or SoftICE will do) and locate a nice suitable place to insert the opcodes of the code that will load our DLL (the only patch that will need to actually be placed on the EXE). In the example in hand (Secret Ops GOLD EXE, also used by Unknown Enemy's EXE wcue.exe), the entry point is at 4C1CD0. There are several good places to store our small DLL loader routine, but i've choosen 4D66F0 (.rdata segment). So, the first step is to make a jump to this new location. I prefer to use 2 opcodes, push and ret, instead of a single jump, since the value used by a jump opcode is relative where using the push/ret combination i can simply hex-edit 68
c3 immediately :) So that's exactly what we put at this location: push 4D66F0, ret. We now move our attention to the address 4D66F0: here we'll simply make a small loader routine. It's the shortest routine possible: we call LoadLibraryA to load our DLL and restore the bytes we overwritten when placing the 2 instructions to jump here. Then we jump to the entry point to execute the original EXE code. push 4D66E0 (location of the DLL name, it must be hex edited on the EXE 16 bytes before the code, since the code is at 4D66F0) call dword ptr [4D1158] (location of the address of LoadLibraryA) mov ebx, 4C1CD0 (entry point address) mov dword ptr [ebx], 6AEC8B55 (restore the entry point opcodes) mov word ptr [ebx+4], 68FF push 4C1CD0 (jump to the restored entry point and let the game run) ret (the Unknown Enemy EXE had a few modifications to load a 2nd DLL, but the the routine is otherwise the same). Only one detail remains: if you ran the EXE now, the game would crash with a General Protection Fault. This happens because we're trying to execute in a read only data segment (.rdata) and write to an executable segment (.text), which you don't have permission to. To fix this, simply give whatever segments you need the permission value E0000060, which will give the segment read, write and execute permissions. I modified the permissions of .text and .rdata, as well as .data segments, as the need to modify these from the DLL may arise. We're just loading the DLL with no concern on unloading it... but fortunately Windows is clever enough to unload our DLL when the process ends. Now the game loads our DLL. Nothing more happens, since the job of hooking functions from the EXE is entirely up to the DLL... Read on :) - Using the DLL to intercept a piece of code - - (and make other modifications) - Now that you've modified the EXE to load your DLL, you'll never have to hex- -edit the EXE again :) You can now fire up your C/C++ compiler of choice (i use Visual C++ 6.0, but any other will do) and start coding. First off... since you only load the DLL and don't call any of it's methods, there's only one place where you can place the code to intercept functions, and make any modifications you wish, and since this is only done once, it's very adequate: the function DllMain, which is called whenever the DLL is loaded (or unloaded, but this doesn't concern us). So let's turn our attention to DllMain for a bit. (I won't include an entire DLL patch on this document, only the most relevant parts, but you can find the source code of several DLL patches on the same section of my site where this document is.) Tiny patches, such like modifying small values can be coded in only one line of C code. It will be easier to understand and you won't have to modify the EXE directly. As an example, to increase the capship rendering distance i had to modify a float at address 4D194C. This is where the value of the distance where capships start disappearing is stored (far clipping). This distance is set to 50000, which is very noticeable. Machines today can handle a larger distance, so let's improve this and put in a nicer value: 500000 :) To make the change it's enough to write the following line: *(float *)0x4d194c = 500000.0f; This line should be placed on DllMain, or in a function called from DllMain. However if this is all you'd need to do, there wouldn't be the need to DLL patches in the first place ;) The real usefulness is the ability to intercept a piece of EXE code, re-direct it to a DLL function and write a piece of code that will to get an intended result. As an example, i'll present below a patch that allowed Unknown Enemy, as well as other projects under development (B5Commander and Standoff), to load their own TRE archives. The first step is to determine the location where the TREs are being loaded. After looking around a bit, it's clear that the code from 43AC4E until 43ACA8 loads the game's TRE archives, and that the function at 4166C0, which loads the TREs, takes 2 parameters, the TRE name and an integer (the priority value of the TRE in question). The strategy i choose was to do away with the code that loads the TREs, redirect the code to the DLL at address 43AC4E, do whatever i want on the DLL (which is to load the usual TREs, as well as fan-project TREs declared on the TXT config file) and resume normal execution at 43ACA8. To do this, i placed on DllMain the following piece of code: void (*temp)(void) = tre; char treBridge[] = {0x90, 0x68, 0xa8, 0xac,0x43,0, 0x68, 0,0,0,0,0xc3}; memcpy((void *)0x43ac4e, treBridge, 12); memcpy((void *)0x43ac55, &temp, 4); In short, this copies to 43AC4E the instructions... push 43ACA8 push ret ... which simulate a call to the tre() function on the DLL (again, i don't use a call instruction because the value used by the call opcode is relative). Executing push followed by ret jumps to the tre() function, Meanwhile, 43ACA8 becomes the return address for the tre() function when it returns. All is good: we "hijacked" the code to our function on the DLL :) There we must load the TREs that the EXE was supposed to load (we skipped that deliberately a while back), and open the TXT config file to load whatever TREs are declared there. I wrote the following code: (code edited for clarity, the original function is present on the Unknown Enemy source code) // TRE patch void tre(void) { void (*LoadTre)(char*,int) = (void(*)(char*, int)) 0x4166c0; int * selectedRes = (int *) 0x43d3c6; char func[20]; char treName[20]; int number; func[0] = 0; LoadTre("mission.tre", 2); LoadTre("data.tre", 1); LoadTre("speech.tre", 1); LoadTre("ifcomms.tre", 1); LoadTre("silence.tre", 2); LoadTre("music.tre", 1); FILE * f = fopen ("ue_tre.cfg", "rt"); if (f) { while (1) { fscanf (f, "%s %s %d\n", func, treName, &number); if (!strcmp(func, "end")) break; else if (!strcmp(func, "all")||(atoi(func) == *selectedRes)) LoadTre (treName, number); } fclose(f); } } In short, we can call directly functions on the EXE whenever needed as long as we declare it (void (*LoadTre)(char*,int) = ...) and keep any object reference that may be necessary (in case we're calling a method on a object, for example, but that's not the case here). Also, should we need to have access to registers or stack, to have a more direct control over what's going on, it's useful to use functions with naked context. Naked functions don't set up their own context (push ebp, mov ebp, esp, etc), so the compiler only generates the code you put there. Using assembly at this point may be inevitable, but you can always call C functions from there :) A good example of this, is the leech_mci_hook function on the UE DLL: void __declspec (naked) leech_mci_hook() { __asm cmp eax, 0xab369204 // LEECH_Code(int) __asm jnz fail_first __asm mov eax, dword ptr[edi+4] // get parameter (int) __asm push eax __asm mov ecx, ebx __asm call get_register_value __asm mov weapon_index, eax // store Leech gun index __asm mov n_leech_table, 0 // reset leech table __asm jmp success fail_first: __asm cmp eax, 0x7fea304 // LEECH_Get(obj) (snip...) } - Conclusions - DLL patches are, IMHO, a very powerful method of hacking a game, probably the most powerful one you can use when you don't have the source code of the game. The most difficult task, when creating patches this way, is perhaps to determine what does what on the EXE, since adding your own code becomes less of a problem (just recompile the DLL and you're done). With some work documenting the EXE, it'll be possible to do pretty much any kind of hack with DLL patches :) An interesting aplication of DLL Patches is source code reconstruction: intercept the original routine on the EXE, call your reconstructed routine instead and you'll be able to debug your code based on watching the game run. I used this when reconstructing the movie decoders for my WC Movie Player;) -EOF