Runtime memory patching on x86/x64
In this blog post we will be discussing different ways of modifying code that is being executed by changing the underlying x86/x64 instructions. This will not only allow us to improve iteration times but also increase efficiency while debugging.
These techniques will be demonstrated using C++ on Windows under Visual Studio but should be applicable on any x86/x64 environment. We assume you are familiar with C or C++ but everything else beyond that will be explained.
Imagine you are debugging a game and a C assert is firing every frame. This assert is not even relevant to the problem you are trying to debug! Annoying, right? It is also time consuming since the natural solution is to stop, comment out the assert, and restart the application - unless you use a cool hot reloading system like any of these. But let’s assume you are not *that* cool. One thing you could do is nuke the assert from orbit by patching it to a no operation in memory and resume execution:
Hey Mikey, I think he likes it! Job done right? Yes, but we can do even better. Fast iteration is the key to productivity and this solution is not that convenient if we need to do it more than once. But first let’s understand what steps were taken above:
Go to Disassembly and copy the address of the next instruction to be executed
Go to the memory window and paste the address
In this example the assert is simulated by a Microsoft specific intrinsic __debugbreak, which generates the INT 3 instruction. For Clang / LLVM this instruction is generated by the __builtin_debugtrap intrinsic function. This is an interrupt that the debugger understands so it can break at that point. The C assert function will also generate this instruction.
Since we would like to replace this instruction with a NOP, the first thing to do is to find where that instruction is in memory, which Visual Studio conveniently points out with a yellow arrow as the next one to be executed.
Now we know that the debugger is breaking on an INT 3 instruction and that the opcode for this instruction is 0xcc, as that’s the first opcode that is visible when we paste the instruction’s address in the memory window. However we don’t know what the opcode of NOP is. To answer that we need some reference like the intel architecture software manual or a helpful page. If you search that page for the cc opcode you will find the INT 3 instruction and if you look for the NOP instruction you will find the opcode 90. Now it is just a matter of replacing the opcode in the memory window - Make sure that ‘Reevaluate Automatically’ is enabled so you can see the changes on the fly.
Since we now understand this solution, we can develop a better one. The first step is to realise that the EIP (x86) / RIP (x64) register is already pointing to the address of the next instruction to be executed. No more copypasta!
Type RIP in Visual Studio’s watch window (or EIP on x86) and you will get the register’s integer value, which is not very convenient, but after dereferencing it to char*, then you can type the decimal representation of 0x90 which is 144, like so:
Notice how by changing the register’s value, the opcode also changed in the memory window. So now we have a convenient way to NOP out instructions; just keep that register in the watch window at all times and you’re sorted!
You can use this knowledge for other purposes such as changing if statements or breaking out of loops on the fly. For example:
Understanding the sequence of events above is left as an exercise for the reader but have you noticed that when we stepped over the __debugbreak it didn’t fire? This is because the debugger had already been triggered at that instruction while stepping over so it can’t be triggered again. You can read more about it here.
An assert not triggering when it should is certainly not expected so one way to fix this is by replacing it with the __ud2 intrinsic, which generates an undefined instruction. __builtin_trap generates the same instruction for Clang / LLVM. This is slightly more cumbersome because the debugger can’t continue stepping through code after hitting that instruction. This limitation can be bypassed by dragging the yellow arrow to the next line when using Visual Studio, or by manually setting the RIP pointer as shown above.
Follow Nuno on Twitter @nunopleiria