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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdio.h>

int main (int argc, char **argv)
{
	int valid = 0;

	if (valid)
		printf ("You did it!\n");

	return 0;
}

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

  1. How else could you patch the binary instead of changing the instruction?
  2. Can you use LLDB to change the instruction directly, rather than changing the binary on disk?