-
Notifications
You must be signed in to change notification settings - Fork 159
Modifying Game Code
Scanning and modifying variables is nice and all but you can hit a ceiling pretty fast if you limit yourself to only doing this.
Learning basic assembly language and starting to use the disassembly window will help you massively with mastering game hacking.
Please note that if you're not comfortable yet in using the basic features, such as scanning and modifying variables, you shouldn't be doing anything from this page!
Games are compiled from source code (that uses a programming language) to machine code (lots of 1s and 0s, which can be arranged in hex format for easier readability) for the CPU to execute.
Assembly is the lowest-level programming language for any particular architecture that can be turned into machine code. In fact, most programming languages are compiled to assembly first which is then assembled into machine code.
Disassembly is the process of generating a human-readable interpretation of the machine code, which is done by converting machine code lines into its equivalent assembly language instructions. This is possible because each assembly instruction has a 1:1 mapping with machine code instructions.
Learning to read assembly can help you understand how the game code works, giving you the ability to create complex hacks that change the way the game works, not just modifying variable values or freezing them in place.
Teaching you assembly is out of scope for this quick overview, but you can always look up tutorials online for the basics, such as this one. You can also always look up instruction references to find more info about particular instructions or to refresh your memory.
Inside Memory Viewer you can find the Disassembly View, which lists the entire disassembly output of your target process. Pressing F2 will pause the process and refresh your disassembly view, moving your current line to where the last executed instruction was.
Right clicking on any instruction will bring up the context menu, where you can find 2 interesting options: Edit instruction and Replace instruction with NOPs, both of which will be outlined below.
NOP (No OPeration), represented by the machine code byte 0x90 in x86 assembly, is an instruction that does nothing. Now you may wonder why such an instruction exists and there are a few reasons, but one of them is using as padding to align instructions or other data in the code.
0x00 can be interpreted as an argument or something else while the CPU is fetching and executing the machine code instructions, but having a dedicated NOP byte makes it easier for the CPU to ignore.
This is where the Replace instruction with NOPs option comes in handy. You can use it to basically nullify any instruction in the game code, such as instructions that lower your health when you get damaged. This way you don't have to keep searching for health variables or freeze them, but just simply modify the game for the same behaviour.
Let's take a free Linux native game as an example, Assault Cube. Download and run it, attach to it with PINCE and then find the current clip ammo variable by using the usual steps in finding variables. Now instead of freezing it, you can:
- Right click on the variable and then press on
Find out what writes to this address. - This will spawn a window which will show you what instructions modify the variable.
- Shoot once to populate the window and find the instruction responsible for the ammo decrease.
- Double click on the address to open it in the disassembly view.
- You can optionally click on
Stopand thenCloseas you won't be needing the window anymore.
- You should now see the instruction responsible at the topmost line.
- For my machine and latest AC version as of writing this, it reads
add DWORD PTR [rax], 0xffffffff, which translates into "add -1 to the value pointed by the RAX register". - The RAX register currently contains a Pointer to your ammo value.
- Right click on it and then press on
Replace instruction with NOPs.
Congratulations, you have made your first infinite ammo hack. As you can see, that 3 bytes instruction was replaced with 3 0x90 bytes (3 NOPs), which instead of subtracting your ammo count by 1, now do nothing.
If you ever want to undo a NOP replacement, you can go to View->Restore Instructions inside Memory Viewer. Once you're there, you can see the address of the replacement, the original machine code bytes of the instruction and its assembly equivalent to the right of it.
Right click on the entry you want and then select Restore this instruction. Your previously installed NOPs are now reverted and the original instruction is back into its place. Shooting your weapon now will once again start decreasing your clip ammo.
On the other hand, if you want to change the instruction completely instead of nullifying it, you can use the Edit instruction option.
Doing so will bring a small window where you can edit the machine code bytes (opcode bytes) or the assembly to modify the instruction itself.
Continuing with the example from NOPs above, if you instead choose to edit the instruction from add dword ptr [rax], -1 to add dword ptr [rax], -2 and then press ok, your ammo will now decrease by 2 on each shot.
You can even modify it to add dword ptr [rax], 2 so you gain 2 ammo on each shot instead of losing ammo.
Editing instructions in any way will also save the original instruction to View->Restore Instructions, so you can follow the Restoring NOP'd Instructions above.
Each assembly instruction maps to a specific sequence of machine code bytes, which means that each instruction has a specific number of bytes.
Changing instructions to use less or more bytes can shift the other instructions around and break the program!
In order to minimize breakage, the Edit Instruction feature automatically pads missing bytes with 0x90 bytes (NOP). This means that if you edit the 3 bytes ammo instruction from above to something like test eax,eax which uses 2 bytes (0x85 0xC0), the dangling 3rd byte is automatically set to 0x90 to ensure instruction alignment does not change.
If you try to change the instruction to one that uses more bytes, you will get an overflow warning. Pressing OK on that warning will cause the extra bytes to overwrite the bytes of the instructions that follow. You should never press on OK if you have no idea what you're doing, but if you do you probably don't need to read this anyway.
The way we get over the above limitation is by grabbing at least 5 bytes starting from the address of the instruction you want to modify, which can be the instruction itself (if >= 5 bytes) or 2+ instructions (until you reach at least 5 bytes) and then modifying them to a jump instruction (which takes 5 bytes). The jump instruction will point to a memory location that is unused by the program or allocated by you using various injection methods.
That memory location will now be your canvas for custom code, where you can call the original code, modify it freely there or write entirely new code to change the game's behaviour. At the end of this canvas, you would jump back to the instructions following your original jump so the game can continue executing the other code as intended.
This technique is called a "Code Cave" and the details of how to implement this are out of scope for this quick overview, but you can use Libpince Engine to achieve this.