When it comes to patching certain functions of a binary on ASM level, it’s often performed by modifying the binary itself. This post shows a different approach to accomplish the same thing: Removing game cheat protections using runtime function patching.
The Target
This is being shown in the following context: Quake3 based multiplayer games include certain settings (CVars) which are disabled for players to prevent cheating. These settings would enable clients to disable fog and shadows and use different camera angles. Using these settings would bring an advantage over clients not being able to use them, hence the protection mechanism. However, it would be convenient to use these settings anyway and remove the protection mechanism on the client side – that’s what function patching is being used for.
Looking for the Code
The first step is to locate the code segment which is being used to implement the client side protection. An easy way to do this is to search for error messages. There’s an error message being displayed in case a client tries to use an illegal CVar which would remove all shadows from the current map:
This can be used to locate the relevant code segment in IDA:
Navigate to the machine code by clicking on the cross reference (XRef) of the first search result:
The code in this particular segment prevents the usage of illegal CVar settings and values by call
ing an error handling method based on a cmp
operation resulting in a conditional jump:
Observing the code on runtime suggests that to remove the protection, it’s enough to always perform the conditional jump – making it a jmp
instead of a jnz
to skip the error handling. This can also be seen in the corresponding graph view:
Checking the hex view of the conditional shows that the jnz
has opcode 75
(jnz
rel 8). This results in the requirement of patching exactly one byte - making it a jmp
(rel 8) with opcode EB
.
Now this could be patched directly in the binary but it would be way more elegant to patch it in the memory after executing the binary. This is what the Windows API method WriteProcessMemory
can be used for. You can see this implemented in this proof of concept. Take note of the following things:
ADDR
holds the memory address of thejnz
call, which was obtained via the IDA hex view. This operation uses two bytes: One for the operation and one for the destination address. For this hack, only the operation code has to be patchedCreateToolhelp32Snapshot
is being used to map the required process ID (PID) to the executable file name- Patching is only being performed after verifying that the expected byte is present using
ReadProcessMemory
- The target process gets halted with
DebugActiveProcess
to prevent side effects when hot-patching WriteProcessMemory
takes care of all memory protection issues, so noVirtualProtect
is required
After executing the code, the graph view in memory looks like this, making the error handling code unreachable:
Let’s verify the memory patching worked by using an illegal CVar:
This hack could now be distributed as executable file, making it possible to use it in combination with other modifications which work in-memory.