Exercise Summary
The idea of this self-inflicted exercise was to see if I could inspect and patch a binary to change the outcome of some conditional code. In days of long past up I had found tutorials on how to patch a binary to bypass copyright protection schemes. The type that would ask a random question, where the answer found in a manual.
You know, the one you would have had if the game wasn’t recieved on bootleg floppy diskettes.
It was easy enough to follow the instructions, change this byte at this offset and now your game will play as long as you get the answer wrong!
…but how does the person writing these binary modification instructions figure out what byte to change and what to change it to?
Today, we’ll figure that out!
Consider the following C code:
| |
We compile as follows, no optimization to make things easier to follow and/or prevent the compiler from being smart and excluding the unreachable code.
$ clang -O0 -g -o patchme patchme.c
$ ./patchme
As expected. Nothing happens. The variable valid is set to 0 (false) and printf is never called.
Inspecting the machine code with LLDB
Current executable set to '/usr/home/inept/patchme' (x86_64).
(lldb) b main
Breakpoint 1: where = patchme`main + 22 at patchme.c:5:13, address = 0x00000000002016c6
(lldb) run
Process 30226 launched: '/usr/home/inept/patchme' (x86_64)
Process 30226 stopped
* thread #1, name = 'patchme', stop reason = breakpoint 1.1
frame #0: 0x00000000002016c6 patchme`main(argc=1, argv=0x00000008205c79a0) at patchme.c:5:13
2
3 int main (int argc, char **argv)
4 {
-> 5 int valid = 0;
6
7 if (valid)
8 printf ("You did it!\n");
(lldb) disassemble -b
patchme`main:
0x2016b0 <+0>: 55 push rbp
0x2016b1 <+1>: 48 89 e5 mov rbp, rsp
0x2016b4 <+4>: 48 83 ec 20 sub rsp, 0x20
0x2016b8 <+8>: c7 45 fc 00 00 00 00 mov dword ptr [rbp - 0x4], 0x0
0x2016bf <+15>: 89 7d f8 mov dword ptr [rbp - 0x8], edi
0x2016c2 <+18>: 48 89 75 f0 mov qword ptr [rbp - 0x10], rsi
-> 0x2016c6 <+22>: c7 45 ec 00 00 00 00 mov dword ptr [rbp - 0x14], 0x0
0x2016cd <+29>: 83 7d ec 00 cmp dword ptr [rbp - 0x14], 0x0
0x2016d1 <+33>: 74 11 je 0x2016e4 ; <+52> at patchme.c:10:9
0x2016d3 <+35>: 48 bf d9 04 20 00 00 00 00 00 movabs rdi, 0x2004d9
0x2016dd <+45>: b0 00 mov al, 0x0
0x2016df <+47>: e8 ac 00 00 00 call 0x201790 ; symbol stub for: printf
0x2016e4 <+52>: 31 c0 xor eax, eax
0x2016e6 <+54>: 48 83 c4 20 add rsp, 0x20
0x2016ea <+58>: 5d pop rbp
0x2016eb <+59>: c3 ret
(lldb)
Patching the byte
Now that we know the machine code for the JE (jump if equal) instruction we can search it with bvi (\ key) and modify it.
We search for the pattern \7411, find it at 0x000006D1
Changing 74 to 75 changes the instruction from JE to JNE (jump if
not equal) reversing the condition and causing printf() to be
called.
000006A0 00 00 00 5D C3 CC CC CC CC CC CC CC CC CC CC CC ...]............
000006B0 55 48 89 E5 48 83 EC 20 C7 45 FC 00 00 00 00 89 UH..H.. .E......
000006C0 7D F8 48 89 75 F0 C7 45 EC 00 00 00 00 83 7D EC }.H.u..E......}.
000006D0 00 75 11 48 BF D9 04 20 00 00 00 00 00 B0 00 E8 .t.H... ........
000006E0 AC 00 00 00 31 C0 48 83 C4 20 5D C3 CC CC CC CC ....1.H.. ].....
Tada! This changes the binary as though the original C code read
if (!valid), one byte in the binary changes negates the outcome,
printf() is called and the string is printed. “You did it!”
Running the modified binary
% ./patchme
You did it!
Follow-up exercises
- How else could you patch the binary instead of changing the instruction?
- Can you use LLDB to change the instruction directly, rather than changing the binary on disk?