Home Patch an executable with Python
Post
Cancel

Patch an executable with Python

Hopper is a very useful disassembler for Mac/Linux and I use it all the time when I need to reverse a binary.
The problem is that it costs a whole 99€ to get a license and be able to export a patched binary.
In this post, I will show how to apply patches to a binary thanks to Python.
Note that this method can also be adapted to any programming language, such as C or others. It can also be used to automate patches if needed.

Find something to patch

Let’s use a simple example.
Consider the following code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdlib.h>
#include <stdio.h>

int main(){
    int age=0;
    printf("How old are you ? ");
    scanf("%d", &age);
    if (age<18){
        printf("You are a child\n");
    }
    else {
        printf("You are an adult\n");
    }
    return 0;
}

Assume you are given only the compiled binary, but you are only 10 y.o.
We will patch the binary so that it displays You are an adult even if you are too young.

Find what patches to apply with Hopper

Load the compiled binary in Hopper and navigate to its main function.
It should be easy to see that there is an if instruction which redirects execution if you are an adult. Desktop View The strings are displayed, but too far on the right to be visible here

We must patch the jump to redirect execution to the second bloc regardless of the result of the test before.
The solution is to replace the conditional jump b.le by an unconditionnal jump b.
To do this, go to Modify > Assemble instruction... or use the keyboard shortcut Option + A and type the new instruction (adapt it if necessary with the correct name of the bloc) : b loc_100003f18
Desktop View
As you can see after patching, the “child” region is never executed, and the execution flow will directly jump to the “adult” bloc.
Desktop View
Now, the first thing you need to note down is the offset from the beginning of your binary.
It is the offset from the binary’s base 0x100000000 to your instruction.
To double check what the base is, navigate to the Mach-O header :
Desktop View This is the Mach-O header For us, the offset will be 0x100003f04 - 0x100000000 = 0x3f04
Next, we need to find what the new instruction is like in binary code.
Select the instruction in the ASM view and switch to hexadecimal view.
Desktop View Make sure you have selected the patched instruction You will see values in red, these are the new hex values for the patched instruction.
Desktop View The patched instruction has its hex representation in red Note them down as well, as we will need them later on.
The Hopper part is done, so make sure you got the offset and the hex representation of the instruction.

Write a Python script and apply patches

I have provided the script, just after this text.
As you can see, we are first loading the original binary in binary mode. This gives us a <bytes> object. Since <bytes> objects are immutable, we need to convert it to a (mutable) array.
Once this is done, we simply replace the value at the offset by the new hex representation.
That is why we needed both values.
Here is the script :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/python3

import sys
 
if len(sys.argv) != 2:
    print('Please input path to the binary you want to patch.')
    sys.exit()

def replace(content, offset, instruction):
    print("[i] Replacing instruction at offset", hex(offset))
    content[offset : offset+len(instruction)] = instruction

def bytes_to_array(input):
    n = len(input)
    L = [b'\x00']*n
    for i in range(n):
        L[i] = input[i]
    return L

orig_file = sys.argv[1]
patched_file = orig_file + '_patched'
file = open(orig_file, 'rb')
content = bytes_to_array(file.read())
file.close()
replace(content, 0x3f04, b'\x05\x00\x00\x14')  # replace if necessary
file = open(patched_file, 'wb')
file.write(bytes(content))
file.close()
print("Patched file created")

Then, patch the executable :

1
./patcher.py <path-to-executable>

The produced binary is called <original_name_patched> and can be found at the same path than the original file.
What you have to do next is configure your system to execute this patched binary. Many systems include protections against this type of attack, and iOS or macOS are no exceptions to this.
See my other blog article about running patched executables on macOS to effectively run this patched executable on your machine !
However, as a spoiler, I can already show you that our patch worked : Desktop View Wow, society is moving at a fast pace !

Conclusion

To summarize, you can use Hopper to see what/where patches should be applied and see what the new instructions will look like after the patch.
Then, a simple script (which can of course be adapted to any language) applies the patches and produces the patched executable.
Finally, you have to configure your system to run this patched executable, but this is too OS-specific to be discussed here.

This post is licensed under CC BY 4.0 by the author.
Contents