In this article I will show some techniques used in dynamic and static analysis and demonstrate them using a simple console game.
1. A Brief Introduction
When reverse-engineering any software you can choose between two different approaches: you can examine software by dissecting its binaries and ressources (static reverse-engineering) or you can run software and observe its behaviour and memory (dynamic reverse-engineering).
Dynamic analysis is used to observe data and behaviour at runtime. It uses some very intuitive and easy to understand concepts such as memory scanners and debuggers but also network sniffers.
Memory scanners (e.g. Cheat Engine and CrySearch) allow reverse-engineers to observe and scan memory of running programs. They can be used to find the location of values of interest (a popular example is health points or money in games) and manipulate them (e.g. increasing your funds ingame).
Debuggers (like x64dbg and OllyDebug), allow us to halt programs, execute instructions one at a time, examine registers and stack frames and trace function calls. They are especially useful to dereference pointer-chains, intercept calls to functions of interest (such as calls to WinAPI functions) and understand how data of interest is being accessed at runtime.
Network sniffers (like Wireshark) are used to capture and inspect network traffic. They are especially useful when analyzing multiplayer games and networked applications in general, allowing us to get an understanding about what parts of logic are executed locally and remotely.
Static analysis is performed without running the software that is to be analyzed, examining code, assets and dependencies. It is generally thought of as a more complex approach as it usually requires in-depth knowledge about the platform software is run on, frameworks being used by software and, depending on the programming language and environment used, the language software is compiled to. Most modern game engines are programmed in C++ and require game-developers to develop their features and logic bits in C++, thus the resulting games are usually compiled down to binary code that can be directly run on processors (using x86 and x64 instruction sets on most desktop CPUs).
Disassemblers (e.g. IDA Pro, Binary Ninja and radare2) interpret the raw bytes that represent the x86 and x64 assembly and display them in a human-readable fashion using mnemonics (e.g. translating “b8 64 00 00 00” to “mov eax, 0x40”). In case of software that is compiled down to bytecode using intermediate languages (such as Java or C#) one can use decompilers (like JD-GUI for java programs and dnSpy or ILSpy for .NET programs) that reconstruct code that often is nearly identical to the original source-code.
Aside from analyzing code one can also dissect a game’s ressources: in many games that use the source engine one can unpack game assets and change textures and meta-data about textures that result in objects being rendered differently, like glowing in the dark (emitting light) or being drawn on top of everything else (ignoring z-buffering).
For more general analysis, tools like PEiD and Filealyzer can be used to get a general idea about executables (like sections, imports/exports and hashes). From time to time one may also make use of hex-editors (like HxD, HexEdit and 010 Editor) to dissect binary data. They come in handy when looking for strings in said data as most hex-editors feature a hex-view that shows both data encoded in hex and printable characters alongside.
An extensive list of programs used for gamehacking (and reverse-engineering in general) can be found here: https://github.com/dsasmblr/game-hacking
Usually one analyzes software using both static and dynamic analysis as they are used in different phases in analysis and there are cases when one of them simply is not possible or feasible (e.g. encrypted binaries that have to be dumped at runtime, binaries that employ anti-debugging techniques, etc) .
2. Analyzing the game
2.1. The game
The game I will demonstrate dynamic and static analysis on is a simple console application. In the beginning of each round, a random number is generated. The player enters a guess: depending on the input, the game responds with an answer that leads the player into the right direction (“number too low/high”). If the player guesses the wrong number it would allow them to take another guess. If the player guesses the number correctly though, the game prints the amount of turns it took the player to guess the number and starts anew.
You can download the game here, feel free to hack it!
2.2. Initial Analysis
I usually begin reverse-engineering any application with some simple initial analysis: loading the executable into FileAlyzer provided me with a nice overview of various properties the file has. In the first image you can see that the file was built for 32bit machines and uses the x86 instruction set. The second image shows the file’s sections: there were no unusual sections to be seen here. It is noteworthy though that the code and rdata sections were both only 4kb in size, suggesting a very small and simple application. Lastly, the only imports of the file that were not MSVC-libraries were functions imported from kernel32 which are used for error handling.
2.3. Dynamic Analysis
Proceeding to a more intuitive approach I started the game and attached Cheat Engine to it. I knew that it counted the number of rounds so I gave scanning memory for this counter a try. So I took a first guess and searched for the value “1” (image 4) using the unknown type option which yielded more than 34.000 results, taking another guess and searching (for the value “2”) yielded 140 results and taking a third guess (value “3”) left me with only one single result (at 0x000242F8).
Using Cheat Engine’s “Memory Viewer” I inspected the value in memory and the adjacent data: as can be seen in the screen shots, the values “1” (0x00024E8) and “100” (0x00024EC) constitute the range of values that the random number lies in, “6” (0x00024F0) and “8” (0x00024F4) are the closest guesses (lower and upper values) to the random number “7” (0x00024E4).
Now I knew where these values of interest were stored at but I still had to find a pointer to this data. To do this I simply used Cheat Engine’s “Find out what accesses this address” feature which attaches Cheat Engine to the process and logs accesses to the address (image 8). [Note that I restarted the application and the counter-value was now located at 0x0097C9D8.]
Image 9 shows the “inc”-instruction that increases the counter. It increased a value at [esi+0x18], suggesting that the address of the structure that held the data was at [esi]. A few lines above there was an address moved into esi (mov esi,[PatternScanningSampleApplication.exe+33E4]) indicating that there is a pointer in the executable that points to the dynamically allocated structure that holds our data of interest. At this point I ended this phase of analysis as I had all the information I needed to create a hack for it: I could NOP the instruction that increased the counter, resulting in “0” registered guesses taken, read/write the random number and even limit the value range new random numbers would be generated in.
2.4. Static analysis
While at this point I knew everything I needed to know in order to write a hack for the game, I performed static analysis anyways for demonstration. So I loaded it into Cutter (which is a GUI for radare2) and as a first measure browsed the list of strings in this binary. I knew that “Take a guess” and “You guessed right” were displayed in the game and so I found those strings
(image 11) and took a look at the disassembly graph (full graph in image 12). The generated c-like pseudocode (excerpt shown in image 13) displays how the program worked: first it stored the address of the game-structure in esi (0x4033E4 with 0x400000 being the binarie’s base address, 0x33E4 being the offset to the pointer to the game-structure). It entered a do-while loop until the first field of the game-structure was non-zero (indicating that the first field of this structure was a boolean that held information about whether the game was over or not). In the loop it uses the register ecx for the guessed number that was entered by players and edx for the random number (esi+4) as can be seen by the various comparisions performed from line 13 to 23. The last part from line 25 to 34 seemed to be wrong as it did not make any use of the strings “The number is higher than”/“The number is lower than”. Considering the bits I learned about the game-structure by the dynamic analysis I performed earlier, I could get an understanding of which offset to esi used which value.
3. Conclusion
Dynamic and static analysis are important and effective concepts when it comes to dissecting and hacking applications. While the tools and methods I showed in this post were rather specific to desktop applications, dynamic and static analysis can be performed on pretty much any application: hacking websites, mobile apps and applications in embedded systems follows the same principles.
I hope this post can be of use to you! In another post I will cover how to make a hack persistent so that future updates of programs won’t bother our hacks.