QuickPatch is mainly a GDB plug-in giving users the ability to patch an ELF file quickly, just by write the instructions to patch.

With QuickPatch is also possible to patch/disassemble a binary file for the architectures x86-32 x86-64 arm and arm64.

It is based on Capstone and Keystone.

Here we will see how to use the software with a very simple example.

This is the source of the file that we are going to analyze:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PASSWORD "patch_me"
#define SIZE 256

int main(void) {
    char buffer[SIZE];

    printf("Password: ");

    fgets(buffer, SIZE, stdin);

    if (strncmp(buffer, PASSWORD, strlen(buffer)-1) == 0) {
        printf("Password correct!\n");
        return 0;
    }
    printf("Password incorrect!\n");

    return 0;
}

Compile it:

$ gcc -o patch_me_pie -fpie -pie patch_me.c
checksec patch_me_pie
[*] '/home/andrea/patch/tests/patch_me_pie'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

It is a very simple program, and we want to see the message “Password correct!” for any user password, for that purpose we have to patch it.

Patch the binary with GDB

$ gdb ./tests/patch_me_pie
gdb> source gdbQuickPatch.py
gdb> b main
gdb> run
gdb> disassemble main

Dump of assembler code for function main:
   0x0000555555554920 <+0>:	push   rbp
   0x0000555555554921 <+1>:	mov    rbp,rsp
=> 0x0000555555554924 <+4>:	sub    rsp,0x110
   0x000055555555492b <+11>:	mov    rax,QWORD PTR fs:0x28
   0x0000555555554934 <+20>:	mov    QWORD PTR [rbp-0x8],rax
   0x0000555555554938 <+24>:	xor    eax,eax
   0x000055555555493a <+26>:	lea    rdi,[rip+0x113]        # 0x555555554a54
   0x0000555555554941 <+33>:	mov    eax,0x0
   0x0000555555554946 <+38>:	call   0x5555555547b0 <printf@plt>
   0x000055555555494b <+43>:	mov    rdx,QWORD PTR [rip+0x20070e]        # 0x555555755060 <stdin@@GLIBC_2.2.5>
   0x0000555555554952 <+50>:	lea    rax,[rbp-0x110]
   0x0000555555554959 <+57>:	mov    esi,0x100
   0x000055555555495e <+62>:	mov    rdi,rax
   0x0000555555554961 <+65>:	call   0x5555555547d0 <fgets@plt>
   0x0000555555554966 <+70>:	lea    rax,[rbp-0x110]
   0x000055555555496d <+77>:	mov    rdi,rax
   0x0000555555554970 <+80>:	call   0x555555554790 <strlen@plt>
   0x0000555555554975 <+85>:	lea    rdx,[rax-0x1]
   0x0000555555554979 <+89>:	lea    rax,[rbp-0x110]
   0x0000555555554980 <+96>:	lea    rsi,[rip+0xd8]        # 0x555555554a5f
   0x0000555555554987 <+103>:	mov    rdi,rax
   0x000055555555498a <+106>:	call   0x555555554770 <strncmp@plt>
   0x000055555555498f <+111>:	test   eax,eax
   0x0000555555554991 <+113>:	jne    0x5555555549a6 <main+134>
   0x0000555555554993 <+115>:	lea    rdi,[rip+0xce]        # 0x555555554a68
   0x000055555555499a <+122>:	call   0x555555554780 <puts@plt>
   0x000055555555499f <+127>:	mov    eax,0x0
   0x00005555555549a4 <+132>:	jmp    0x5555555549b7 <main+151>
   0x00005555555549a6 <+134>:	lea    rdi,[rip+0xcd]        # 0x555555554a7a
   0x00005555555549ad <+141>:	call   0x555555554780 <puts@plt>
   0x00005555555549b2 <+146>:	mov    eax,0x0
   0x00005555555549b7 <+151>:	mov    rcx,QWORD PTR [rbp-0x8]
   0x00005555555549bb <+155>:	xor    rcx,QWORD PTR fs:0x28
   0x00005555555549c4 <+164>:	je     0x5555555549cb <main+171>
   0x00005555555549c6 <+166>:	call   0x5555555547a0 <__stack_chk_fail@plt>
   0x00005555555549cb <+171>:	leave
   0x00005555555549cc <+172>:	ret

we want to patch the instruction at 0x000055555555498f and the instruction at 0x0000555555554991 with nops instructions.

Let’s get the nop opcode for the specific architecture:

gdb> get_bytes "nop"
[*] get_bytes command is called
-----------------------------------------------------
[*] Arch is i386:x86-64
[*] Instructions: ['nop'] (len: 1)
[*] Encoding: 0x90 (len: 1)

The length of the nop instruction is one byte (architecture is i386:x86-64), so we will need to use it 4 times in order to patch 4 bytes.

We try to patch it in not persistent mode in order, in order to verify if everything works fine:

gdb> memory_patch "nop;nop;nop;nop" 0x000055555555498f
[*] memory_patch command is called
-----------------------------------------------------
[*] Arch is i386:x86-64
[*] Instructions: ['nop', 'nop', 'nop', 'nop'] (len: 4)
[*] Encoding: 0x90 0x90 0x90 0x90 (len: 4)
[*] Memory is successfully patched at address 0x000055555555498f

Let’s look the disassembly again:

gdb> disassemble main
Dump of assembler code for function main:
   0x0000555555554920 <+0>:	push   rbp
   0x0000555555554921 <+1>:	mov    rbp,rsp
=> 0x0000555555554924 <+4>:	sub    rsp,0x110
   0x000055555555492b <+11>:	mov    rax,QWORD PTR fs:0x28
   0x0000555555554934 <+20>:	mov    QWORD PTR [rbp-0x8],rax
   0x0000555555554938 <+24>:	xor    eax,eax
   0x000055555555493a <+26>:	lea    rdi,[rip+0x113]        # 0x555555554a54
   0x0000555555554941 <+33>:	mov    eax,0x0
   0x0000555555554946 <+38>:	call   0x5555555547b0 <printf@plt>
   0x000055555555494b <+43>:	mov    rdx,QWORD PTR [rip+0x20070e]        # 0x555555755060 <stdin@@GLIBC_2.2.5>
   0x0000555555554952 <+50>:	lea    rax,[rbp-0x110]
   0x0000555555554959 <+57>:	mov    esi,0x100
   0x000055555555495e <+62>:	mov    rdi,rax
   0x0000555555554961 <+65>:	call   0x5555555547d0 <fgets@plt>
   0x0000555555554966 <+70>:	lea    rax,[rbp-0x110]
   0x000055555555496d <+77>:	mov    rdi,rax
   0x0000555555554970 <+80>:	call   0x555555554790 <strlen@plt>
   0x0000555555554975 <+85>:	lea    rdx,[rax-0x1]
   0x0000555555554979 <+89>:	lea    rax,[rbp-0x110]
   0x0000555555554980 <+96>:	lea    rsi,[rip+0xd8]        # 0x555555554a5f
   0x0000555555554987 <+103>:	mov    rdi,rax
   0x000055555555498a <+106>:	call   0x555555554770 <strncmp@plt>
   0x000055555555498f <+111>:	nop
   0x0000555555554990 <+112>:	nop
   0x0000555555554991 <+113>:	nop
   0x0000555555554992 <+114>:	nop
   0x0000555555554993 <+115>:	lea    rdi,[rip+0xce]        # 0x555555554a68
   0x000055555555499a <+122>:	call   0x555555554780 <puts@plt>
   0x000055555555499f <+127>:	mov    eax,0x0
   0x00005555555549a4 <+132>:	jmp    0x5555555549b7 <main+151>
   0x00005555555549a6 <+134>:	lea    rdi,[rip+0xcd]        # 0x555555554a7a
   0x00005555555549ad <+141>:	call   0x555555554780 <puts@plt>
   0x00005555555549b2 <+146>:	mov    eax,0x0
   0x00005555555549b7 <+151>:	mov    rcx,QWORD PTR [rbp-0x8]
   0x00005555555549bb <+155>:	xor    rcx,QWORD PTR fs:0x28
   0x00005555555549c4 <+164>:	je     0x5555555549cb <main+171>
   0x00005555555549c6 <+166>:	call   0x5555555547a0 <__stack_chk_fail@plt>
   0x00005555555549cb <+171>:	leave
   0x00005555555549cc <+172>:	ret

We did well, so we can patch the program in a persistent way:

gdb> program_patch "nop;nop;nop;nop" 0x000055555555498f patch_me_with_patch
[*] program_patch command is called
-----------------------------------------------------
[*] Arch is i386:x86-64
[*] Address 0x555555554000 module name /home/invictus/Documents/QuickPatch/patch/tests/patch_me_pie address is 0x000055555555498f
[*] Offset is 0x98f
[*] Instructions: ['nop', 'nop', 'nop', 'nop'] (len: 4)
[*] Encoding: 0x90 0x90 0x90 0x90 (len: 4)
[*] File patch_me_with_patch with patch is created

Let’s try it

$ ./patch_me_with_patch
Password: randomsasa
Password correct!

Patch the binary without GDB

python3 QuickPatch.py -A x86-64 -a 'nop;nop;nop;nop' -o 0x98f -b ./tests/patch_me_pie -of patch_nogdb
[*] Instructions: ['nop', 'nop', 'nop', 'nop'] (len: 4)
[*] Encoding: 0x90 0x90 0x90 0x90 (len: 4)
[*] File patch_nogdb with patch is created

Let’s try it

$ ./patch_nogdb
Password: kkfskfsdk
Password correct!

Disassemble it before and after:

$ python3 QuickPatch.py -A x86-64 -o 0x98f -b ./tests/patch_me_pie -dl 5
0x98f: 85c0             test  eax, eax        
0x991: 7513             jne   0x9a6           
0x993: 488d3dce000000   lea   rdi, [rip + 0xce]
0x99a: e8e1fdffff       call  0x780           
0x99f: b800000000       mov   eax, 0

$ python3 QuickPatch.py -A x86-64 -o 0x98f -b ./patch_nogdb -dl 5
0x98f: 90               nop                   
0x990: 90               nop                   
0x991: 90               nop                   
0x992: 90               nop                   
0x993: 488d3dce000000   lea   rdi, [rip + 0xce]

SHELLCODE

This other simple example will show how to disassemble from user input a shellcode.

For example for this shellcode

$ python3 QuickPatch.py -A x86-64 -d "0x48,0x31,0xd2,0x48,0xbb,0x2f,0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x48,0xc1,0xeb,0x08,0x53,0x48,0x89,0xe7,0x50,0x57,0x48,0x89,0xe6,0xb0,0x3b,0x0f,0x05"
0x0: 4831d2           xor   rdx, rdx
0x3: 48bb2f2f62696e2f7368 movabs rbx, 0x68732f6e69622f2f
0xd: 48c1eb08         shr   rbx, 8
0x11: 53               push  rbx
0x12: 4889e7           mov   rdi, rsp
0x15: 50               push  rax
0x16: 57               push  rdi
0x17: 4889e6           mov   rsi, rsp
0x1a: b03b             mov   al, 0x3b
0x1c: 0f05             syscall