Archive

Archive for the ‘Game Hacking’ Category

Bypassing Product Key Authentication (1/2)

July 21st, 2017 1 comment

This post will cover the topic of product authentication in applications and how it can be bypassed. It aims to serve as a detailed walkthrough of how to locate these functions in a target application and methods in which an application can be modified to allow it to accept invalid product keys. The post will focus on a concrete application and will involve reverse engineering the code which is responsible for performing authentication. At this time, only the calling code will be investigated — this will not be a post about reverse engineering the actual algorithm itself, although that may come at a later date.

Tools

Not much is needed here outside of the standard tools. Below is what was used when creating this post:

  • Cheat Engine for memory scanning
  • x64dbg for dynamic analysis
  • Installer executable (SETUP.EXE) SHA1 Hash: AC9241F632FFB0D845E404FC06C3A204D2EE1B99 that comes with the Age of Mythology CD

The Target

The target for this post will be Age of Mythology. The game requires a valid product key as part of the installation process. The verification is done entirely within the executable itself; there is no online activation required, which would greatly complicate the process.

The installation process requires the input of a valid 25-character product key that is located on the physical CD case. Failure to provide this product key results in an error dialog saying that the product key is invalid and prevents the user from continuing the installation process.

The goal then is to bypass this process and be able to install the game without having a valid product key. This will involve finding the code responsible for calling the authentication function(s), reverse engineering it to understand how it works, and then finding a way to modify it so that it is possible to proceed in the installation process without having a valid key.

Finding the Function

As mentioned above, the natural starting point is to find where the product key is being verified. This can be accomplished in multiple ways, each one having its own benefits and drawbacks. For this example, the approach I took involved finding the key in memory and seeing where it was accessed. This was done by inputting a key into the box and then searching for the string in the process memory using Cheat Engine.

Doing this resulted in one address. Finding out what writes to this address provided additional information to investigate

At this point it is time to attach a debugger and begin stepping through some of this code. Starting at the top of the list of addresses, the ones in the 0x757621… range looked interesting. Given the high address, it can be concluded that these likely reside in a Windows core DLL. Navigating to the first address in the debugger reveals that it is part of the lstrcpyA function in kernel32.dll.

757621B0 | 6A 08                    | push 8
757621B2 | 68 B8 F5 7C 75           | push kernel32.757CF5B8
757621B7 | E8 E0 85 00 00           | call kernel32.7576A79C
757621BC | 83 65 FC 00              | and dword ptr ss:[ebp-4], 0
757621C0 | 8B 55 0C                 | mov edx, dword ptr ss:[ebp+C]
757621C3 | 8B 45 08                 | mov eax, dword ptr ss:[ebp+8]
757621C6 | 8B F0                    | mov esi, eax
757621C8 | 2B F2                    | sub esi, edx
757621CA | 8A 0A                    | mov cl, byte ptr ds:[edx]
757621CC | 88 0C 16                 | mov byte ptr ds:[esi+edx], cl
757621CF | 42                       | inc edx
757621D0 | 84 C9                    | test cl, cl
757621D2 | 75 F6                    | jne kernel32.757621CA
757621D4 | C7 45 FC FE FF FF FF     | mov dword ptr ss:[ebp-4], FFFFFFFE
757621DB | E8 01 86 00 00           | call kernel32.7576A7E1
757621E0 | C2 08 00                 | ret 8

There’s nothing surprising here, the two arguments are passed in [EBP+0x8] and [EBP+0xC]. The contents of the source argument are copied, one byte at a time, into the destination argument in a loop which terminates when a null terminator is found in the source parameter. Setting a breakpoint on this function shows that it is being hit multiple times. It is initially hit five times for the five different parts of the key. Afterwards it is hit with the entire key. For the first five parts, the call stack shows the call coming from the following:

0040CD06 | 50                       | push eax                                         |
0040CD07 | 68 D0 D8 47 00           | push ebud71f.47D8D0                              | 47D8D0:"11111"
0040CD0C | FF D6                    | call esi                                         | esi:lstrcpyA
0040CD0E | 8B 0D A0 CC 47 00        | mov ecx,dword ptr ds:[47CCA0]                    |
0040CD14 | 8B 11                    | mov edx,dword ptr ds:[ecx]                       |
0040CD16 | FF 92 90 00 00 00        | call dword ptr ds:[edx+90]                       |
0040CD1C | 50                       | push eax                                         |
0040CD1D | 68 D4 D9 47 00           | push ebud71f.47D9D4                              | 47D9D4:"22222"
0040CD22 | FF D6                    | call esi                                         | esi:lstrcpyA
0040CD24 | 8B 0D A4 CC 47 00        | mov ecx,dword ptr ds:[47CCA4]                    |
0040CD2A | 8B 01                    | mov eax,dword ptr ds:[ecx]                       |
0040CD2C | FF 90 90 00 00 00        | call dword ptr ds:[eax+90]                       |
0040CD32 | 50                       | push eax                                         |
0040CD33 | 68 D8 DA 47 00           | push ebud71f.47DAD8                              | 47DAD8:"33333"
0040CD38 | FF D6                    | call esi                                         | esi:lstrcpyA
0040CD3A | 8B 0D A8 CC 47 00        | mov ecx,dword ptr ds:[47CCA8]                    |
0040CD40 | 8B 11                    | mov edx,dword ptr ds:[ecx]                       |
0040CD42 | FF 92 90 00 00 00        | call dword ptr ds:[edx+90]                       |
0040CD48 | 50                       | push eax                                         |
0040CD49 | 68 DC DB 47 00           | push ebud71f.47DBDC                              | 47DBDC:"44444"
0040CD4E | FF D6                    | call esi                                         | esi:lstrcpyA
0040CD50 | 8B 0D AC CC 47 00        | mov ecx,dword ptr ds:[47CCAC]                    |
0040CD56 | 8B 01                    | mov eax,dword ptr ds:[ecx]                       |
0040CD58 | FF 90 90 00 00 00        | call dword ptr ds:[eax+90]                       |
0040CD5E | 50                       | push eax                                         |
0040CD5F | 68 E0 DC 47 00           | push ebud71f.47DCE0                              | 47DCE0:"55555"
0040CD64 | FF D6                    | call esi                                         | esi:lstrcpyA

Navigating around the code a bit doesn’t show immediately useful being done with the results of these calls. The next hit gives the following call stack:

Moving down the frame to the return to 0x0040D035 brings us to a rather large function starting at 0x0040CFD0. This will be the target of investigation.

Analyzing the Function

The function starts out with the following code:

0040CFE0 | 8D 44 24 2C                | lea eax,dword ptr ss:[esp+2C]                    | [esp+2C]:lstrcpyA
0040CFE4 | 6A 06                      | push 6                                           |
0040CFE6 | 50                         | push eax                                         |
0040CFE7 | BE 01 00 00 00             | mov esi,1                                        |
0040CFEC | 33 DB                      | xor ebx,ebx                                      |
0040CFEE | 68 39 01 00 00             | push 139                                         |
0040CFF3 | 51                         | push ecx                                         |
0040CFF4 | 89 74 24 20                | mov dword ptr ss:[esp+20],esi                    | [esp+20]:WaitForSingleObject
0040CFF8 | 89 5C 24 30                | mov dword ptr ss:[esp+30],ebx                    |
0040CFFC | 89 5C 24 2C                | mov dword ptr ss:[esp+2C],ebx                    | [esp+2C]:lstrcpyA
0040D000 | 89 5C 24 38                | mov dword ptr ss:[esp+38],ebx                    |
0040D004 | E8 37 3D 00 00             | call ebu9e7d.410D40                              |
0040D009 | A1 04 D1 47 00             | mov dword ptr ds:[47D104]                        |
0040D00E | 8D 54 24 44                | lea edx,dword ptr ss:[esp+44]                    |
0040D012 | 6A 0A                      | push A                                           |
0040D014 | 52                         | push edx                                         |
0040D015 | 68 83 01 00 00             | push 183                                         |
0040D01A | 50                         | push eax                                         |
0040D01B | E8 20 3D 00 00             | call ebu9e7d.410D40                              |

Stepping into the function at 0x004100D40 this shows that it is responsible for loading some resources from the executable.

00410D45 | 55                         | push ebp                                         |
00410D46 | 8B 6C 24 18                | mov ebp,dword ptr ss:[esp+18]                    |
...
00410D4C | 8D 44 2D 00                | lea eax,dword ptr ss:[ebp+ebp]                   |
00410D50 | 33 FF                      | xor edi,edi                                      |
00410D52 | 50                         | push eax                                         |
00410D53 | C6 03 00                   | mov byte ptr ds:[ebx],0                          |
00410D56 | E8 EF 36 04 00             | call ebu9e7d.45444A                              | malloc
00410D5B | 8B F0                      | mov esi,eax                                      |
...
00410D77 | 8B 4C 24 18                | mov ecx,dword ptr ss:[esp+18]                    | [esp+18]:lstrcpyA
00410D7B | 8B 54 24 14                | mov edx,dword ptr ss:[esp+14]                    |
00410D7F | 55                         | push ebp                                         |
00410D80 | 56                         | push esi                                         |
00410D81 | 51                         | push ecx                                         |
00410D82 | 52                         | push edx                                         |
00410D83 | FF 15 5C 64 46 00          | call dword ptr ds:[<&LoadStringA>]               |

The definition of LoadStringA shows that the function takes in the HINSTANCE of the executable to load the resource from, a resource identifier, an output buffer to receive the resource data, and the length of the output buffer. Three of the four parameters are those that are passed into 0x004100D40. The parameter that isn’t passed in is the output buffer parameter. This buffer parameter is created via a call to a malloc wrapper that creates a buffer of (2 * nBufferMax) size. After the resource is successfully loaded (the call succeeds), the loaded resource is copied into the third parameter of 0x004100D40 and is null-terminated.

00410D8D | 8B CD                      | mov ecx, ebp
00410D8F | 8B FB                      | mov edi, ebx
00410D91 | 8B C1                      | mov eax, ecx
00410D93 | C1 E9 02                   | shr ecx, 2
00410D96 | F3 A5                      | rep movsd dword ptr es:[edi], dword ptr ds:[esi]
00410D98 | 8B C8                      | mov ecx, eax
00410D9A | 83 E1 03                   | and ecx, 3
00410D9D | F3 A4                      | rep movsb byte ptr es:[edi], byte ptr ds:[esi]
00410D9F | C6 44 2B FF 00             | mov byte ptr ds:[ebx+ebp-1], 0
00410DA4 | 8A 03                      | mov al, byte ptr ds:[ebx]
00410DA6 | 84 C0                      | test al, al
00410DA8 | 8B F3                      | mov esi, ebx

This is done through some pretty clever assembly involving some shifts and rep moves. The function continues on perform some checks for code pages, presumably because the resource data can have an ANSI code page or DBCS code page. The function wraps up by calling two other functions at 0x00410200 and 0x00454361, which at a quick glance are responsible for verifying some information about the resource. The function wraps up by returning the length of the loaded resource.

Stepping through these calls in the original function at 0x0040D035 reveals that the loaded resources are:

  • “69405” at resource index 0x139
  • “Z09-00001” at resource index 0x183
  • A missing resource at index 0x185

Continuing on, the function then proceeds to try to load a DLL and get the address of a function. The executed code is colored below:

0040D07B | 68 A8 D3 46 00             | push ebu9e7d.46D3A8                              | 46D3A8:"%SETUPEXEDIR"
0040D080 | F3 AB                      | rep stosd dword ptr es:[edi],eax                 | edi:"\\PidGen.dll"
0040D082 | 8D 4C 24 64                | lea ecx,dword ptr ss:[esp+64]                    |
0040D086 | 88 5C 24 44                | mov byte ptr ss:[esp+44],bl                      |
0040D08A | 51                         | push ecx                                         |
0040D08B | C7 84 24 70 02 00 00 00 01 | mov dword ptr ss:[esp+270],100                   |
0040D096 | 66 AB                      | stosw word ptr es:[edi],ax                       | edi:"\\PidGen.dll"
0040D098 | FF D5                      | call ebp                                         | ebp:lstrcpyA
0040D09A | 8D 54 24 60                | lea edx,dword ptr ss:[esp+60]                    |
0040D09E | 68 04 01 00 00             | push 104                                         |
0040D0A3 | 52                         | push edx                                         |
0040D0A4 | E8 57 31 00 00             | call ebu9e7d.410200                              |
0040D0A9 | 8D 44 24 68                | lea eax,dword ptr ss:[esp+68]                    |
0040D0AD | 50                         | push eax                                         |
0040D0AE | E8 FD 4E 00 00             | call ebu9e7d.411FB0                              |
0040D0B3 | 8B 35 20 63 46 00          | mov esi,dword ptr ds:[<&lstrcat>]                |
0040D0B9 | 83 C4 0C                   | add esp,C                                        |
0040D0BC | 8D 4C 24 60                | lea ecx,dword ptr ss:[esp+60]                    |
0040D0C0 | 68 2C D8 46 00             | push ebu9e7d.46D82C                              | 46D82C:"PidGen.dll"
0040D0C5 | 51                         | push ecx                                         |
0040D0C6 | FF D6                      | call esi                                         |
0040D0C8 | 8D 54 24 60                | lea edx,dword ptr ss:[esp+60]                    |
0040D0CC | 52                         | push edx                                         |
0040D0CD | E8 AE 25 00 00             | call ebu9e7d.40F680                              |
0040D0D2 | 83 C4 04                   | add esp,4                                        |
0040D0D5 | 85 C0                      | test eax,eax                                     |
0040D0D7 | 75 46                      | jne ebu9e7d.40D11F                               |
0040D0D9 | 8D 44 24 60                | lea eax,dword ptr ss:[esp+60]                    |
0040D0DD | 50                         | push eax                                         |
0040D0DE | 68 04 01 00 00             | push 104                                         |
0040D0E3 | FF 15 00 63 46 00          | call dword ptr ds:[<&GetTempPathA>]              |
0040D0E9 | 8D 4C 24 60                | lea ecx,dword ptr ss:[esp+60]                    |
0040D0ED | 51                         | push ecx                                         |
0040D0EE | E8 BD 4E 00 00             | call ebu9e7d.411FB0                              |
0040D0F3 | 83 C4 04                   | add esp,4                                        |
0040D0F6 | 8D 54 24 60                | lea edx,dword ptr ss:[esp+60]                    |
0040D0FA | 68 2C D8 46 00             | push ebu9e7d.46D82C                              | 46D82C:"PidGen.dll"
0040D0FF | 52                         | push edx                                         |
0040D100 | FF D6                      | call esi                                         |
0040D102 | 8D 44 24 60                | lea eax,dword ptr ss:[esp+60]                    |
0040D106 | 50                         | push eax                                         |
0040D107 | E8 74 25 00 00             | call ebu9e7d.40F680                              |
0040D10C | 83 C4 04                   | add esp,4                                        |
0040D10F | 85 C0                      | test eax,eax                                     |
0040D111 | 75 0C                      | jne ebu9e7d.40D11F                               |
0040D113 | 8D 4C 24 60                | lea ecx,dword ptr ss:[esp+60]                    |
0040D117 | 68 20 BB 47 00             | push ebu9e7d.47BB20                              |
0040D11C | 51                         | push ecx                                         |
0040D11D | FF D5                      | call ebp                                         | ebp:lstrcpyA
0040D11F | 38 5C 24 60                | cmp byte ptr ss:[esp+60],bl                      |
0040D123 | 0F 84 51 01 00 00          | je ebu9e7d.40D27A                                |
0040D129 | 8D 54 24 60                | lea edx,dword ptr ss:[esp+60]                    |
0040D12D | 52                         | push edx                                         |
0040D12E | FF 15 98 61 46 00          | call dword ptr ds:[<&LoadLibraryA>]              |
0040D134 | 8B F0                      | mov esi,eax                                      |
0040D136 | 3B F3                      | cmp esi,ebx                                      |
0040D138 | 89 74 24 24                | mov dword ptr ss:[esp+24],esi                    |
0040D13C | 0F 84 38 01 00 00          | je ebu9e7d.40D27A                                |
0040D142 | 68 20 D8 46 00             | push ebu9e7d.46D820                              | 46D820:"PIDGenSimpA"
0040D147 | 56                         | push esi                                         |
0040D148 | FF 15 E4 62 46 00          | call dword ptr ds:[4662E4]                       |
0040D14E | 3B C3                      | cmp eax,ebx                                      |

The first part in orange is responsible for building the path string of the PidGen.dll file. There are other functions that are called above which are responsible for making sure that the file exists and that it has the correct file attributes. Once the path string is built, the PidGen.dll file is loaded with a call to LoadLibrary and the address of the PIDGenSimpA function is retrieved via a call to GetProcAddress (teal).

The Verification Call

The call to PIDGenSimpA shows that it takes nine parameters

0040D156 | 8D 4C 24 28                | lea ecx,dword ptr ss:[esp+28]                    |
0040D15A | 8D 54 24 20                | lea edx,dword ptr ss:[esp+20]                    |
0040D15E | 51                         | push ecx                                         |
0040D15F | 8D 8C 24 6C 02 00 00       | lea ecx,dword ptr ss:[esp+26C]                   |
0040D166 | 52                         | push edx                                         |
0040D167 | 51                         | push ecx                                         |
0040D168 | 8B 4C 24 28                | mov ecx,dword ptr ss:[esp+28]                    |
0040D16C | 8D 54 24 4C                | lea edx,dword ptr ss:[esp+4C]                    |
0040D170 | 52                         | push edx                                         |
0040D171 | 51                         | push ecx                                         |
0040D172 | 8D 54 24 28                | lea edx,dword ptr ss:[esp+28]                    |
0040D176 | 8D 4C 24 48                | lea ecx,dword ptr ss:[esp+48]                    |
0040D17A | 52                         | push edx                                         |
0040D17B | 51                         | push ecx                                         |
0040D17C | 8B 8C 24 88 03 00 00       | mov ecx,dword ptr ss:[esp+388]                   |
0040D183 | 8D 54 24 48                | lea edx,dword ptr ss:[esp+48]                    |
0040D187 | 52                         | push edx                                         |
0040D188 | 51                         | push ecx                                         |
0040D189 | FF D0                      | call eax                                         |

At the time of the call, the stack looks like the following:

0496F23C  0496F5E0  "1111122222333334444455555"
0496F240  0496F28C  "69405"
0496F244  0496F294  "Z09-00001"
0496F248  0496F274  
0496F24C  00000000  
0496F250  0496F2A0  
0496F254  0496F4C8  
0496F258  0496F280  
0496F25C  0496F288  

The first three parameters are plainly obvious; they are the input product key, and the two resources that were loaded from the executable earlier in the function. The next six are a bit harder to pin down however. Stepping back through the function and looking at the origin of these addresses shows that:

  • 0x0496F274 comes from the third string resource at index 0x185 that is attempted to be loaded. Since the resource does not exist, this string remains empty.
  • The value of 0 is initialized at the start of the function. It is possible to have it set to 1 if the call to 0x004112D0 returns 1, which it doesn’t in this case.
  • 0x0496F2A0 is not initialized anywhere, so it is likely an optional or output parameter
  • 0x0496F4C8 is written into at 0x0040D08B, which writes in 0x100 (256). Following 0x0496F4C8 in the memory dump shows that it contains the bytes 00 01 00 00, which is the little endian representation of 0x100 (256).
  • 0x0496F280 is not initialized anywhere, so it is likely an optional or output parameter
  • 0x0496F228 is not initialized anywhere, so it is likely an optional or output parameter

Since an invalid key is being entered, it will be useful to study the failure case and see what conditions bring up the “Invalid Product Key” popup. The call to PIDGenSimpA returns with a value of 1 when an invalid product key is entered. The following instructions are then executed:

0040D18B | 3B C3                      | cmp eax,ebx                                      |
0040D18D | 75 64                      | jne ebu9e7d.40D1F3                               |
...
0040D1F3 | 8B 0D 04 D1 47 00          | mov ecx,dword ptr ds:[47D104]                    |
0040D1F9 | 8D 84 24 64 01 00 00       | lea eax,dword ptr ss:[esp+164]                   |
0040D200 | 68 04 01 00 00             | push 104                                         |
0040D205 | 50                         | push eax                                         | eax:"Invalid Product Key"
0040D206 | 68 A3 00 00 00             | push A3                                          |
0040D20B | 51                         | push ecx                                         |
0040D20C | E8 2F 3B 00 00             | call ebu9e7d.410D40                              |
0040D211 | A1 0C D1 47 00             | mov dword ptr ds:[47D10C]                        |
0040D216 | 83 C4 10                   | add esp,10                                       |
0040D219 | 8D 94 24 64 01 00 00       | lea edx,dword ptr ss:[esp+164]                   |
0040D220 | 52                         | push edx                                         | edx:"Invalid Product Key"
0040D221 | 6A 30                      | push 30                                          |
0040D223 | 50                         | push eax                                         |
0040D224 | EB 32                      | jmp ebu9e7d.40D258                               |
...
0040D258 | E8 23 26 00 00             | call ebu9e7d.40F880                              |

The “Invalid Product Key” popup comes after stepping over the call at 0x0040D258, which calls 0x0040F8880. This concludes the analysis of the error-case. The next part will cover what happens in the success case and how to properly get the application to respond to the success case with an invalid product key.

Thanks for reading and follow on Twitter for more updates.

Game Hacking (3/3): Putting Everything Together

March 11th, 2017 No comments

The last two posts discussed how to develop an Age of Mythology map hack. This was done by finding and reverse engineering the parts of the game responsible for toggling the map state (black overlay, fog of war, full reveal) and invoking those functions through a DLL that is injected into the game process. This short post will complete the series by providing the source code for an injector that will inject the newly developed hack DLL into the Age of Mythology process. The hack will work in multiplayer, and in the original game as well as the extended edition.

The code can be found here and is generally pretty self-explanatory. The map hack DLL exports a KeyboardProc callback, which handles the logic of toggling the map state depending on what keys the user enters (7, 8, 9, 0 keys). The injector installs a keyboard hook on the game process, which effectively injects the hack DLL into the game process and makes the KeyboardProc callback active. At this point, all keystrokes going to the game will be intercepted and checked against the four special toggle keys. If a toggle key is hit, then the corresponding function to set the map state will be invoked.

Missed the earlier posts?

Game Hacking (1/3): The Hard Way

Game Hacking (2/3): The Easy Way

Thanks for reading and follow on Twitter for more updates.

Game Hacking (2/3): The Easy Way

February 25th, 2017 9 comments

The previous post detailed how to develop a map hack by taking advantage of existing functionality in the game. The technique relied on being able to toggle the map to a hidden/revealed state, and then using this functionality to methodically step through the assembly code. This eventually led to logic that was specific to hiding and revealing the map, where it was then possible to write a hack that invokes this functionality at will. The technique presented in this post is much easier than that, and is only made possible due to useful strings that were found in the binary.

The tool used in this post will be x64dbg, which is a really great debugger and disassembler, and what I consider to be the successor to the now-ancient OllyDbg. Unfortunately, it won’t really be used too much in this post since there won’t be much need of live analysis (this post is titled “the easy way” after all). Assembly snippets will be pasted from IDA Pro, since I find their copy+paste format to be the most readable.

Starting off by attaching to the process and doing a string dump (Right click -> Search for -> Current Module -> String references) for the main executable yielded 25817 strings for me — plenty to search through.

Filtering on the string “map” yields a much more manageable set. Looking through, there are a few strings that seem like they might lead somewhere interesting:

"trSetFogAndBlackmap(<true/false> <true/false>): turn fog and black map on/off."
"trRevealEntireMap -- shows whole map, similar to how revealed mode works"
"trPlayerResetBlackMap(: Resets the black map for a given HUMAN player."
"map visibility"
"blackmap([integerState]) : toggles or sets unexplored black map rendering."

The two most promising places seem to be the ones highlighted in orange. The strings give a clear description of what the function does and even provides parameter arguments. The “trX” functions appear to be related to the triggers system that is available in the game and allows map makers to add effects and conditions to their custom maps. Looking at references to the first string goes to the following:

...
.text:008B2B76 loc_8B2B76:                             ; CODE XREF: sub_8AE4A0+46CDj
.text:008B2B76                 mov     ecx, esi
.text:008B2B78                 call    sub_59C270
.text:008B2B7D                 push    1
.text:008B2B7F                 push    offset loc_8AAEE0
.text:008B2B84                 push    offset aTrsetfogandbla ; "trSetFogAndBlackmap"
.text:008B2B89                 mov     ecx, esi
.text:008B2B8B                 call    sub_59BE80
.text:008B2B90                 test    al, al
.text:008B2B92                 jnz     short loc_8B2BAE
.text:008B2B94                 push    offset aTrsetfogandbla ; "trSetFogAndBlackmap"
.text:008B2B99                 push    offset aSyscallConfigE ; "Syscall config error - Unable to add th"...
.text:008B2B9E                 push    esi             ; int
.text:008B2B9F                 call    sub_59DBC0
...

The code here begins by passing in the string, a pointer to a function, and a constant (1) as arguments to another function (teal). The return value of this call is checked for 0, which is an error condition (blue). From looking at the what is happening in a disassembler, this pattern is found throughout everywhere. This code, and all of the surrounding code, is attempting to register triggers and is providing the trigger name, a callback to where the trigger code lives, and a yet unknown constant of 1. Given that, the real place to look would be in the callback.

Following through to the callback leads to the following section of code:

.text:008AAEE0 loc_8AAEE0:                             ; DATA XREF: sub_8AE4A0+46DFo
.text:008AAEE0                 mov     eax, dword_A9D244
.text:008AAEE5                 mov     ecx, [eax+140h]
.text:008AAEEB                 test    ecx, ecx
.text:008AAEED                 jz      short locret_8AAF13
.text:008AAEEF                 mov     edx, [esp+4]
.text:008AAEF3                 push    0
.text:008AAEF5                 push    edx
.text:008AAEF6                 call    sub_5316B0
.text:008AAEFB                 mov     eax, [esp+8]
.text:008AAEFF                 mov     ecx, dword_A9D244
.text:008AAF05                 mov     ecx, [ecx+140h]
.text:008AAF0B                 push    0
.text:008AAF0D                 push    eax
.text:008AAF0E                 call    sub_5316D0
.text:008AAF13
.text:008AAF13 locret_8AAF13:                          ; CODE XREF: .text:008AAEEDj
.text:008AAF13                 retn

The two calls here (green) should be familiar if you have read the first part of this series recently. These are the two functions that were eventually found to control revealing and hiding the map to the player. Each function takes in a “this” pointer, which we can see here is loaded from a constant address and is likely the class for the main player, along with a true/false value which describes what should happen to the map. There’s also a third constant parameter of 0 here, which is different from the constant parameter of 1 at the other call site from the previous post, possibly indicating whether the map state is being changed via player interaction or a trigger.

Knowing this, the hack from the previous post can be made a bit better. With the old hack, there was an issue of having to provide a fake “this” pointer which needed to have a field written into, and there was only a true/false toggle option. Going from the documentation provided by the string dump, this function takes in two booleans — presumably to control the black overlap and the fog of war which obscures areas that the player has already explored but does not have vision of anymore.

The new (and still hacky) code is below:

#include <Windows.h>
 
using pToggleMapFnc = void (__cdecl *)(bool bEnableBlackOverlay, bool bEnableFogOfWar);
 
int APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
    switch (dwReason)
    {
    case DLL_PROCESS_ATTACH:
    {
        (void)DisableThreadLibraryCalls(hModule);
 
        pToggleMapFnc ToggleMap = (pToggleMapFnc)0x008AAEE0;
 
        while (!GetAsyncKeyState('0'))
        {
            if (GetAsyncKeyState('6'))
            {
                ToggleMap(true,  true);
            }
            else if (GetAsyncKeyState('7'))
            {
                ToggleMap(true, false);
            }
            else if (GetAsyncKeyState('8'))
            {
                ToggleMap(false, true);
            }
            else if (GetAsyncKeyState('9'))
            {
                ToggleMap(false, false);
            }
 
            Sleep(10);
        }
 
        break;
    }
 
    case DLL_PROCESS_DETACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    }
 
    return TRUE;
}

Calling the function with the various parameter combinations reveals the following behavior:
True/True – Black map overlay with fog of war
True/False – No black map overlay, fog of war is still present. Features are missing from the map.
False/True – Black map overlay without fog of war. Areas that were explored will always have line of sight
False/False – No black map overlay, no fog of war. Everything is visible.

Screenshots of the mini-map is shown for these four states below:

The hack becomes a bit cleaner since now it is just making a direct call to a function and doesn’t require passing anything unknown. Hopefully it is obvious why this is considered the “easy way” versus the previous post, which required a large amount of debugging and tracing.

The next, and last, part of this series will cover how to clean this hack up a bit more and make it more professional. Additionally, it will cover what is involved in porting this hack to the newer Extended Edition version of the game.

Thanks for reading and follow on Twitter for more updates.

Game Hacking (1/3): The Hard Way

February 19th, 2017 3 comments

Introduction

Age of Mythology is a real-time strategy game in which a player hopes to build up their civilization and ultimately conquer his or her enemies. The standard mode of play has the player starting off in a map that is covered in black, signifying unknown and unexplored territory.


As the game progresses, the player explores the map and explored regions show signs of terrain, resources, enemy buildings, and are overlaid with a “fog of war” that signifies explored territory that the player does not have vision of anymore.


The goal of this post will be to develop a hack that provides a significant advantage to the player by revealing the entire map. This allows the player to see what enemies on the map are doing as well as where and when it is best to attack. This hack will be developed for the original version and then later parts of this article series will show how it applies to the newer extended edition which is currently found on Steam.

Here are the hashes for the main executable that will be reversed in this set of articles:

CRC32: 7F1AF498
MD5: 09876F130D02AE760A6B06CE6A9C92DB
SHA-1: AAAC9CD38B51BEB3D29930D13A87C191ABF9CAD4

Starting off

The goal is to develop something that reveals everything on the map to the player, giving the player full knowledge of what is going on in the game at all times. The good news is revealing and hiding the map is built-in functionality of the game. The game supports playing back recorded games, and the option to reveal and hide the map is part of this UI.

Clicking the “Fog of War” button toggles fully revealing the map and setting it back to the normal state, which only shows what the player has vision of or has explored. The plan is to find out where the handler for this button is and trace it through to where the logic for revealing the map is. Once that is found, it will simply be a matter of injecting a DLL into the game process to call the function for revealing the map. The tool most appropriate for something like this is Cheat Engine, which is a useful tool for browsing and manipulating memory, debugging, disassembling, and much more, with a specific focus on game hacking. This post will not go over usage of the tool — there are hundreds of resources out there that can cover usage targeted at various skill levels.

After starting Cheat Engine and attaching, it becomes a matter of finding out where the code that interacts with the button is located. The easiest way to find this is to assume some normal programming practices, specifically that an active button will have a value of 1 in memory somewhere and unactive will be 0. Then it becomes a matter of testing and patience. Searching in the process memory for a value of “1” (when the button was active) returned 337,597 results for me. If you’re following along at home then don’t expect identical numbers.

This is far too many to enumerate. Clicking the button again to set it in an unactive state and searching for 0 then returned 376 results — still too many.

Repeating this process a few more times eventually narrows it down to a much more manageable 21 addresses. 20 of these 21 were very close in range. 0x08FC71A4 seemed the odd address of the bunch. Inspecting it closer and forcing its value to 0 did in fact toggle successfully toggle the button. At this point the correct address was found and the other 20 could safely be ignored. The next step is to see what is writing to it.

At this point Cheat Engine will attach a debugger and monitor all writes to 0x08FC71A4. Clicking the button a few times gets the following instructions to pop up. These were the instructions that were found to have written to 0x08FC71A4.

The next step is to inspect these and begin setting breakpoints in order to better understand what is going on around these writes. Setting a breakpoint on the write instructions

and messing around with the game a bit reveals that this function is called for every button. Here, ECX is the pointer to the button, and presumably +0x1A4 holds an IsToggled property that gets set accordingly. This set happens on the second write instruction, where EDX will either be 0 for disabled or 1 for active. The code might look a bit complicated, but it is basically ensuring that the toggled state is a valid state and then setting the IsToggled property before calling a function and returning.

The destination address +0x14B670 is still code that is specific to all buttons. The goal is to begin slowly stepping through everything and finding areas of code that might be specific to the “Fog of War” button. There are a lot of different approaches here, but what I usually look for is:

  • Call destinations that are calculated via a register. This can signify a callback to be invoked after the state of the button has changed, something like an OnChanged/OnEnabled/OnDisabled or similar function.
  • Function parameters that are pointers to functions.
  • Function calls that take in arguments of 1 or 0.

Stepping into +0x14B670 gives the following (partial) assembly listing listed below. The assembly listings will have an absolute address instead of module base + offset address since they were a lot easier to copy from IDA than from Cheat Engine.

.text:0054B670                 mov     eax, large fs:0
.text:0054B676                 push    0FFFFFFFFh
.text:0054B678                 push    offset SEH_54B670
.text:0054B67D                 push    eax
.text:0054B67E                 mov     large fs:0, esp
.text:0054B685                 sub     esp, 8
.text:0054B688                 push    esi
.text:0054B689                 mov     esi, ecx
.text:0054B68B                 mov     eax, [esi+148h]
.text:0054B691                 push    edi
.text:0054B692                 mov     edi, [esi]
.text:0054B694                 push    eax
.text:0054B695                 push    esi
.text:0054B696                 lea     ecx, [esp+24h+var_10]
.text:0054B69A                 call    sub_4D7470
.text:0054B69F                 mov     ecx, [eax]
.text:0054B6A1                 push    ecx
.text:0054B6A2                 push    1
.text:0054B6A4                 mov     ecx, esi
.text:0054B6A6                 call    dword ptr [edi+54h]
.text:0054B6A9                 cmp     [esp+1Ch+arg_0], 0Dh
.text:0054B6AE                 jnz     loc_54B769
.text:0054B6B4                 lea     edi, [esi+154h]
...

The call to 0x004D7470 (red) was stepped into and found to return relatively quickly so it will not be shown here. The next call (blue) at +0x14B6A6 makes a call via a register. This is a good candidate to step into and observe closely. This function has two possible destinations that it can call:

...
.text:0054BF98                 push    0Ch
.text:0054BF9A                 call    dword ptr [eax+0CCh]
.text:0054BFA0
.text:0054BFA0 loc_54BFA0:                             ; CODE XREF: sub_54BF80+Fj
.text:0054BFA0                                         ; sub_54BF80+14j
.text:0054BFA0                 mov     ecx, [esp+0Ch+arg_8]
.text:0054BFA4                 push    ecx
.text:0054BFA5                 push    edi
.text:0054BFA6                 push    ebx
.text:0054BFA7                 mov     ecx, esi
.text:0054BFA9                 call    sub_4D4EF0
.text:0054BFAE                 pop     edi
...

The instruction at +0x14BF9A (red) never gets invoked when debugging and stepping through, so there’s no point in looking at it. That just leaves the next call at +0x14BFA9 (blue) to step into and look at. This functions turn out to be very large in size and has a lot of branching and potential call sites. With the aid of debugging, a lot of this logic can be skipped. After only tracing the code that is executed when the “Fog of War” button is toggled to reveal the map, there are only three call sites left.

...
.text:004D504C                 cmp     esi, dword_A9D068
.text:004D5052                 jz      short loc_4D5087
.text:004D5054                 push    esi
.text:004D5055                 call    sub_424750
.text:004D505A                 mov     edi, eax
.text:004D505C                 add     esp, 4
.text:004D505F                 test    edi, edi
.text:004D5061                 jz      short loc_4D5070
.text:004D5063                 push    esi
.text:004D5064                 call    sub_4D58B0
.text:004D5069                 add     esp, 4
.text:004D506C                 test    edi, edi
.text:004D506E                 jnz     short loc_4D5079
.text:004D5070
.text:004D5070 loc_4D5070:                             ; CODE XREF: sub_4D4EF0+171j
.text:004D5070                 pop     edi
.text:004D5071                 pop     esi
.text:004D5072                 pop     ebp
.text:004D5073                 xor     al, al
.text:004D5075                 pop     ebx
.text:004D5076                 retn    0Ch
.text:004D5079 ; ---------------------------------------------------------------------------
.text:004D5079
.text:004D5079 loc_4D5079:                             ; CODE XREF: sub_4D4EF0+17Ej
.text:004D5079                 mov     eax, [esp+10h+arg_4]
.text:004D507D                 mov     edx, [edi]
.text:004D507F                 push    ebp
.text:004D5080                 push    eax
.text:004D5081                 push    ebx
.text:004D5082                 mov     ecx, edi
.text:004D5084                 call    dword ptr [edx+54h]
.text:004D5087
.text:004D5087 loc_4D5087:                             ; CODE XREF: sub_4D4EF0+157j
.text:004D5087                                         ; sub_4D4EF0+162j
.text:004D5087                 pop     edi
...

The call at +0xD5055 (red) leads down a dead-end path after a bit of tracing. The same goes for +0xD5064 (orange). If you step into them with the debugger and begin tracing through the code paths, these two functions have very similar behavior. However, there is nothing to indicate that they have anything to do with specific functionality of the “Fog of War” button in terms of interacting with the map. Setting a breakpoint on these two instructions will show that they are constantly being invoked from somewhere and that they only perform logic on the caller object. At this point, we are still in common code related to UI and button clicks, so it’s reasonably safe to rule out these two functions as having anything to do with revealing the map.

The last call site is at +0xD5084 (blue). Stepping into it leads to +0xD4EF0, which is again another large function.

.text:004D4EF0                 push    ebx
.text:004D4EF1                 mov     ebx, [esp+4+arg_0]
.text:004D4EF5                 push    ebp
.text:004D4EF6                 mov     ebp, [esp+8+arg_8]
.text:004D4EFA                 push    esi
.text:004D4EFB                 mov     esi, ecx
.text:004D4EFD                 mov     ecx, [esi+0B8h]
...

Toggling a breakpoint on it still shows it getting hit all of the time, so it still remains common handling code. If you step through it, you will actually see that it goes back to code found in the previous listing. The same two calls to 0x00424750 and 0x004D58B0 will be made. Then there will be a call to [EDX+0x54], except this time EDX will have a different value. On this second call, it will lead to the following function at +0xD0C70:

.text:004D0C70                 mov     ecx, [ecx+14Ch]
.text:004D0C76                 test    ecx, ecx
.text:004D0C78                 jz      short loc_4D0C91
.text:004D0C7A                 mov     edx, [esp+arg_8]
.text:004D0C7E                 mov     eax, [ecx]
.text:004D0C80                 push    edx
.text:004D0C81                 mov     edx, [esp+4+arg_4]
.text:004D0C85                 push    edx
.text:004D0C86                 mov     edx, [esp+8+arg_0]
.text:004D0C8A                 push    edx
.text:004D0C8B                 call    dword ptr [eax+30h]
.text:004D0C8E                 retn    0Ch
.text:004D0C91 ; ---------------------------------------------------------------------------
.text:004D0C91
.text:004D0C91 loc_4D0C91:                             ; CODE XREF: sub_4D0C70+8j
.text:004D0C91                 xor     al, al
.text:004D0C93                 retn    0Ch
.text:004D0C93 sub_4D0C70      endp

There’s only one real call site here so this function is pretty easy to analyze. Setting a breakpoint on it still reveals that it is getting hit from everywhere, so this is still common code. The call to [EAX+0x30] leads to +0x680D0. Repeating the breakpoint process yet again reveals that it is still getting hit from everywhere, so still nothing useful yet.

.text:004680D0                 push    0FFFFFFFFh
.text:004680D2                 push    offset SEH_4680D0
.text:004680D7                 mov     eax, large fs:0
.text:004680DD                 push    eax
.text:004680DE                 mov     large fs:0, esp
.text:004680E5                 sub     esp, 0F8h
.text:004680EB                 mov     eax, [esp+104h+arg_8]
.text:004680F2                 push    ebx
.text:004680F3                 push    ebp
.text:004680F4                 push    esi
.text:004680F5                 mov     esi, [esp+110h+arg_0]
.text:004680FC                 push    edi
.text:004680FD                 mov     ebp, ecx
.text:004680FF                 mov     ecx, [esp+114h+arg_4]
.text:00468106                 push    eax
.text:00468107                 push    ecx
.text:00468108                 push    esi
.text:00468109                 mov     ecx, ebp
.text:0046810B                 mov     [esp+120h+var_F0], ebp
.text:0046810F                 call    sub_4718B0
.text:00468114                 test    al, al
...

Finding the specific code

Stepping into the first call site at +0x6810F takes you to a function that contains a gigantic jump table (screenshot below). This could be a promising sign of hitting an area that is responsible for dispatching events or invoking callbacks.

Stepping through the code leads down to the following case:

.text:00471DB4 loc_471DB4:                             ; CODE XREF: sub_4718B0+4FDj
.text:00471DB4                                         ; DATA XREF: .text:off_471FA0o
.text:00471DB4                 push    edi             ; jumptable 00471DAD case 4
.text:00471DB5                 call    sub_54E7D0
.text:00471DBA                 mov     esi, eax
.text:00471DBC                 add     esp, 4
.text:00471DBF                 test    esi, esi
.text:00471DC1                 jz      loc_471F5F      ; jumptable 00471DAD case 3
.text:00471DC7                 push    edi
.text:00471DC8                 call    sub_4D58B0
.text:00471DCD                 add     esp, 4
.text:00471DD0                 test    esi, esi
.text:00471DD2                 jz      loc_471F5F      ; jumptable 00471DAD case 3
.text:00471DD8                 mov     edx, [esi+1A4h]
.text:00471DDE                 mov     ecx, [esp+50h+var_40]
.text:00471DE2                 cmp     edx, ebx
.text:00471DE4                 setz    al
.text:00471DE7                 push    eax
.text:00471DE8                 call    sub_58EA10
.text:00471DED                 mov     al, 1
.text:00471DEF                 jmp     loc_471F65
...

Setting a breakpoint on +0x71DB4 (pink) and continuing shows that nothing is constantly getting hit anymore. Clicking the “Fog of War” button shows +0x71DB4 getting hit. Finally, after tracing for a long time, there is a sign of being inside of code that is specific to the “Fog of War” button. The first call instruction is at+0x71DB5 (red). This function takes one parameter via EDI, and was always a constant value. Carefully stepping through it and observing the values of all of the parameters or referenced/loaded addresses, there was nothing that indicated a toggle value. Specifically, clicking to reveal and hide the map in game and then tracing through this function did not show anything different, so it was ruled out.  The instruction at +0x71DC8 (orange) calls address 0x004D58B0, which is one that was already investigated before. The same case happened with this function. It always took in the same value as the previous function and did not show anything that indicated writing in a toggle value or handling code based on a toggle value.

The next call is at +0x71DE8. This function also takes in one parameter and is also the last function called before the jump-table handling function exits. There is some really interesting logic in the teal block. A value is loaded from [ESI+0x1A4] then compared against EBX. The result of this comparison sets a byte in AL to 0 or 1 depending on the case. EAX, which will be 0 or 1, is then passed as an argument to the function at 0x0058EA10. Toggling the button in the game and stepping through shows that EBX always contains the value of 1 and EDX contains a 0 or 1 depending on whether the map is being hidden or revealed. An assumption can begin to be made that this is the function used to reveal and hide the map. The assembly listing for 0x0058EA10 is shown below:

.text:0058EA10 sub_58EA10      proc near               ; CODE XREF: sub_4718B0+538p
.text:0058EA10                                         ; sub_58DF30+919p ...
.text:0058EA10
.text:0058EA10 arg_0           = dword ptr  4
.text:0058EA10
.text:0058EA10                 push    ebx
.text:0058EA11                 mov     ebx, [esp+4+arg_0]
.text:0058EA15                 mov     [ecx+53h], bl
.text:0058EA18                 mov     eax, dword_A9D244
.text:0058EA1D                 mov     ecx, [eax+140h]
.text:0058EA23                 test    ecx, ecx
.text:0058EA25                 jz      short loc_58EA43
.text:0058EA27                 push    1
.text:0058EA29                 push    ebx
.text:0058EA2A                 call    sub_5316B0
.text:0058EA2F                 mov     ecx, dword_A9D244
.text:0058EA35                 mov     ecx, [ecx+140h]
.text:0058EA3B                 push    1
.text:0058EA3D                 push    ebx
.text:0058EA3E                 call    sub_5316D0
.text:0058EA43
.text:0058EA43 loc_58EA43:                             ; CODE XREF: sub_58EA10+15j
.text:0058EA43                 pop     ebx
.text:0058EA44                 retn    4
.text:0058EA44 sub_58EA10      endp

It forwards the value of 0 or 1 to two more functions, which both take two parameters. The first parameter is the 0 or 1 toggle and the second one is always a hard-coded 1 value. Looking at these two functions shows that they write the 0 or 1 value to an object and then call a function

.text:005316B0 ; =============== S U B R O U T I N E =======================================
.text:005316B0
.text:005316B0
.text:005316B0                 public sub_5316B0
.text:005316B0 sub_5316B0      proc near               ; CODE XREF: sub_442070+1684p
.text:005316B0                                         ; sub_4C91E0+14Cp ...
.text:005316B0
.text:005316B0 arg_0           = byte ptr  4
.text:005316B0 arg_4           = dword ptr  8
.text:005316B0
.text:005316B0                 mov     edx, [esp+arg_4]
.text:005316B4                 mov     al, [esp+arg_0]
.text:005316B8                 push    edx
.text:005316B9                 push    1
.text:005316BB                 mov     [ecx+40Eh], al
.text:005316C1                 call    sub_5316F0
.text:005316C6                 retn    8
.text:005316C6 sub_5316B0      endp
.text:005316C6
.text:005316C6 ; ---------------------------------------------------------------------------
.text:005316C9                 align 10h
.text:005316D0
.text:005316D0 ; =============== S U B R O U T I N E =======================================
.text:005316D0
.text:005316D0
.text:005316D0 sub_5316D0      proc near               ; CODE XREF: sub_442070+1698p
.text:005316D0                                         ; sub_4C91E0+137p ...
.text:005316D0
.text:005316D0 arg_0           = byte ptr  4
.text:005316D0 arg_4           = dword ptr  8
.text:005316D0
.text:005316D0                 mov     edx, [esp+arg_4]
.text:005316D4                 mov     al, [esp+arg_0]
.text:005316D8                 push    edx
.text:005316D9                 push    1
.text:005316DB                 mov     [ecx+40Fh], al
.text:005316E1                 call    sub_5316F0
.text:005316E6                 retn    8
.text:005316E6 sub_5316D0      endp

Patching
mov al, [esp+arg_0]
to
mov al, 0
nop
nop

now leaves the mini-map revealed regardless of whether the “Fog of War” button is set to be enabled or disabled. The code to reveal and hide the map has now been found.

Developing the hack

At this point, it is possible to develop a hack to reveal the map — it should simply be a matter of invoking 0x0058EA10 with true/false depending on how we want to the map to look. There is one slight problem however: there is a write to [ECX+0x53] at 0x0058EA15. This means that we would need to pass in an object with a writable field at +0x53, which would serve as the “this” parameter that is typically passed in via ECX with the __thiscall calling convention. ECX is re-written further along in the function after being loaded from a constant address, so this seems to be a safe approach. The hacky code for this is found below:

#include <Windows.h>
 
struct DummyObj
{
    char Junk[0x53];
};
DummyObj dummy = { 0 };
 
using pToggleMapFnc = void (__thiscall *)(void *pDummyObj, bool bHideAll);
 
int APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
    switch (dwReason)
    {
    case DLL_PROCESS_ATTACH:
    {
        (void)DisableThreadLibraryCalls(hModule);
 
        pToggleMapFnc ToggleMap = (pToggleMapFnc)0x0058EA10;
 
        while (!GetAsyncKeyState('0'))
        {
            if (GetAsyncKeyState('7'))
            {
                ToggleMap(&dummy, true);
            }
            else if (GetAsyncKeyState('8'))
            {
                ToggleMap(&dummy, false);
            }
 
            Sleep(10);
        }
 
        break;
    }
 
    case DLL_PROCESS_DETACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    }
 
    return TRUE;
}

After injecting the DLL into the game process, the map can be toggled to a fully revealed state or a hidden state via the ‘7’ and ‘8’ keys.

Conclusion

This concludes developing a map hack for the game. This approach was very involved and complicated and the next post will show how to greatly simplify everything by taking advantage of useful information that the developers left in the executable. Reading through this might give the impression that everything followed a linear path from start to finish, but there was a lot omitted for the sake of brevity in terms of dead-end code paths. Including those and their explanations would have likely made this post the length of a typical dissertation. While initially developing the hack, I stepped through these various code paths many dozens of times while scribbling notes and as to what might be relevant. The end result of this post is all of the useful information aggregated together into a coherent and semi-linear guide.

Thanks for reading and follow on Twitter for more updates

Runtime DirectX Hooking

December 14th, 2015 1 comment

This post will cover the topic of hooking DirectX in a running application. This post will cover DirectX9 specifically, but the general technique applies to any version. A previous and similar post covered virtual table hooking for DirectX10 and DirectX11 (with minor adjustments). Unlike the previous post, this one aims to establish a technique to hook running DirectX applications. This means that it can be installed at any time, unlike the previous technique, which required starting a process in a suspended state and then hooking to get the device pointer.

Motivations

The motivations are similar to the previous post. By hooking the DirectX device, we can inspect or change the properties of rendered scenes (i.e. depth testing, object colors), overlay text or images, better display visual information, or do anything else with the scene. However, to achieve anything beyond the basics, it also takes a lot of effort in reverse engineering the actual application; simply having access to the rendered scene won’t get you too far.maxresdefault

An example of DirectX hooking to make certain models have a bright color, and to allow seeing of depth through objects that obstruct a view.

SC2Console

An example of outputting reverse engineered data from a client and overlaying it as text in the application. This is a pretty awesome project whose description and source code is available here.
Techniques

Typically when hooking DirectX, there are several popular options:

  • Hook IDirect3D9::CreateDevice and store the IDirect3DDevice9 pointer that is initialized when the function returns successfully. This needs to be done when the process is started in a suspended state, otherwise the device will have already been initialized.
  • Perform a byte pattern scan in memory for the signature of IDirect3DDevice9::EndScene, or any other DirectX function.
  • Create a dummy IDirect3DDevice9 instance, read its virtual table, find the address of EndScene, and hook at the target site.
  • Look for the CD3DBase::EndScene symbol in d3d9.dll and get its address.

Each one has its drawbacks, but my personal preference is the last option. It’s the one that offers the greatest reliability for the least amount of overhead code. The code for it is pretty straightforward, with the help of the Windows debugging APIs:

const DWORD_PTR GetAddressFromSymbols()
{
    BOOL success = SymInitialize(GetCurrentProcess(), nullptr, true);
    if (!success)
    {
        fprintf(stderr, "Could not load symbols for process.\n");
        return 0;
    }
 
    SYMBOL_INFO symInfo = { 0 };
    symInfo.SizeOfStruct = sizeof(SYMBOL_INFO);
 
    success = SymFromName(GetCurrentProcess(), "d3d9!CD3DBase::EndScene", &symInfo);
    if (!success)
    {
        fprintf(stderr, "Could not get symbol address.\n");
        return 0;
    }
 
    return (DWORD_PTR)symInfo.Address;
}

Once the address is retrieved, it’s simply a matter of installing the hook and writing code in the new hook function. The Hekate engine was used for hook installation/removal, making the code simple:

const bool Hook(const DWORD_PTR address, const DWORD_PTR hookAddress)
{
    pHook = std::unique_ptr<Hekate::Hook::InlineHook>(new Hekate::Hook::InlineHook(address, hookAddress));
 
    if (!pHook->Install())
    {
        fprintf(stderr, "Could not hook address 0x%X -> 0x%X\n", address, hookAddress);
    }
 
    return pHook->IsHooked();
}

The EndScene function was chosen specifically due to how DirectX9 applications are developed. For those unfamiliar with DirectX, the flow of rendering a scene generally goes as follows: BeginScene -> Draw the scene -> EndScene -> Present. Other DirectX9 hook implementations hook Present instead of EndScene, it becomes a matter of preference unless the target application does something special. In the example application, some text is overlaid on top of the scene:

HRESULT WINAPI EndSceneHook(void *pDevicePtr)
{
    using pFncOriginalEndScene = HRESULT (WINAPI *)(void *pDevicePtr);
    pFncOriginalEndScene EndSceneTrampoline =
        (pFncOriginalEndScene)pHook->TrampolineAddress();
 
    IDirect3DDevice9 *pDevice = (IDirect3DDevice9 *)pDevicePtr;
    ID3DXFont *pFont = nullptr;
 
    HRESULT result = D3DXCreateFont(pDevice, 30, 0, FW_NORMAL, 1, false,
        DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, ANTIALIASED_QUALITY,
        DEFAULT_PITCH | FF_DONTCARE, L"Consolas", &pFont);
    if (FAILED(result))
    {
        fprintf(stderr, "Could not create font. Error = 0x%X\n", result);
    }
    else
    {
        RECT rect = { 0 };
        (void)SetRect(&rect, 0, 0, 300, 100);
        int height = pFont->DrawText(nullptr, L"Hello, World!", -1, &rect,
            DT_LEFT | DT_NOCLIP, -1);
        if (height == 0)
        {
            fprintf(stderr, "Could not draw text.\n");
        }
        (void)pFont->Release();
    }
 
    return EndSceneTrampoline(pDevicePtr);
}

Building as a DLL and injecting into the running application should show the text overlay (below):

sampleimgdx9

Hekate supports clean unhooking, so unloading the DLL should remove the text and let the application continue undisturbed.

Code

The Visual Studio 2015 project for this example can be found here. The source code is viewable on Github here. The Hekate static library dependency is included in a separate download here and goes into the DirectXHook/lib folder. Capstone Engine is used as a runtime dependency, so capstone_x86.dll/capstone_x64.dll in DirectXHook/thirdparty/capstone/lib should be put in the same directory that the target application is running from.

Thanks for reading and follow on Twitter for more updates