Binary patching is an intimidating sounding topic. It is very low level and requires lots of fiddly little knowledge.

Replacing and Deleting Code

Our first example is trying to patch a simple constant change.

int foo(int x){
    return x + 1;

int foo_patched(int x){
    return x + 2;
echo "
int foo(int x){
    return x + 1;
} " > /tmp/foo.c
gcc -O1 /tmp/foo.c -c -o /tmp/foo.o
objdump -d -F /tmp/foo.o # -F shows offsets which helps us find the right place to patch
0000000000000000 <foo> (File Offset: 0x40):
   0:   f3 0f 1e fa             endbr64 
   4:   8d 47 01                lea    0x1(%rdi),%eax
   7:   c3                      ret    

Looking at it, I suspect that 0x1 corresponds to the 1 in the binary. We can confirm by using the assembler to change that instruction and look at the output binary

echo "lea    0x2(%rdi),%eax" | as -o /tmp/toy - 
objdump -d /tmp/toy

We can then patch using xxd

echo "0000044: 8d4702" > /tmp/foopatchfile
xxd -r /tmp/foopatchfile /tmp/foo.o
objdump -d /tmp/foo.o

Using C compiler to generate helpful assembly.

The basic questions are:

  1. Where to read info you need
  2. Where writes need to go
  3. What cannot be clobbered.

C compilers do not compile program fragments. They must be in function bodies. Nevertheless it can be very illuminating

If your variables need to read from registers, make them arguments to functions. Make globals for things that need to be read from memory or use varargs to see how to read from the stack. If your variables need to be written to registers, make them arguments to called functions.

In this way, you can get a starting point of decent assembly code you can manipulate.

Suggestions on how to be more careful Save the objdump. Edit it to match your expectations. Do your patching, then diff this objdump with your intended objdump.

Suggestions on maintenance: Add a section to the patched binary documenting your edit.


Ghidra makes for a much nicer experience

Hex editors mean you don’t have to fiddle with xxd. VS Code has one for example.

You can also use xxd to dump the entire hex file, edit it, and then rebinarize it.

xxd -s 0x40 -l 8 /tmp/foo.o > /tmp/foo.o.hex
nano /tmp/foo.o.hex

Fusing out a password check

int patch_fun(char *passwd){
    if(strcmp(passwd, "MyGoodPassword7") == 0){
        return 0;
        return -1;

int patch_fun(char *passwd){
    if(0 == 0){
        return 0;
        return -1;

int main(){
    char* password = "MyGoodPassword";
    return patch_fun(password);

Patching a Call

int ret_3() { return 3; }

int ret_5() { return 5; }

int main() {
  return ret_5();

int main_patched() {
  return ret_3();

Changing a Type

This is a nightmare.

int foo(int x){
  if(x > 0){
    return 2;
    return 1;

int foo(unsigned int x){
  if(x > 0){
    return 2;
    return 1;

int main() {
  return foo(-1);

Adding Code

We could make this harder. Now there won’t be code to twiddle. Really we are adding functionality.

echo "
int  __attribute__ ((noinline))  foo(int x){
    return x;

int  __attribute__ ((noinline))  foo_patched(int x){
    return x + 1;

int main(int argc, char* argv[]){
    return foo(argc); // It will inline foo, which raises the question of how to patch _that_. Very nasty.
" > /tmp/add1.c
gcc /tmp/add1.c -fno-inline-small-functions -O1 -g -o /tmp/add1
objdump -d /tmp/add1

Disassembly of section .text:

0000000000000000 <foo>:
   0:   f3 0f 1e fa             endbr64 
   4:   89 f8                   mov    %edi,%eax
   6:   c3                      ret    

0000000000000007 <foo_patched>:
   7:   f3 0f 1e fa             endbr64 
   b:   8d 47 01                lea    0x1(%rdi),%eax
   e:   c3                      ret    

We can see that there is more code in foo_patched than foo. We can’t just manipulate some bytes to overwrrite instructions of change constants

objdump --help
readelf -a /tmp/add1
echo "
// re
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <elf.h>

int main() {
    FILE *file = fopen(\"/tmp/add2\", \"r+b\");
    if (!file) {
        perror(\"Error opening file\");
        return 1;

    // Read the ELF header
    Elf64_Ehdr ehdr;
    fread(&ehdr, sizeof(ehdr), 1, file);

    // Check if it's a valid ELF file
    assert(memcmp(ehdr.e_ident, ELFMAG, SELFMAG) == 0);
    // Check for 64-bit ELF
    assert (ehdr.e_ident[EI_CLASS] == ELFCLASS64);

    // Loop through program headers to find the first PT_LOAD segment
    for (int i = 0; i < ehdr.e_phnum; i++) {
        fseek(file, ehdr.e_phoff + i * ehdr.e_phentsize, SEEK_SET);
        Elf64_Phdr phdr;
        fread(&phdr, sizeof(phdr), 1, file);

        if (phdr.p_type == PT_LOAD && phdr.p_flags == (PF_R | PF_X)) {
            // Increase the segment size by a fixed amount, e.g., 512 bytes
            phdr.p_memsz += 512;
            phdr.p_filesz += 512;

            // Write back the modified program header
            fseek(file, ehdr.e_phoff + i * ehdr.e_phentsize, SEEK_SET);
            fwrite(&phdr, sizeof(phdr), 1, file);

    return 0;
} " > /tmp/re.c
cp /tmp/add1 /tmp/add2
gcc /tmp/re.c -o /tmp/re
readelf -l /tmp/add1 > /tmp/add1.elf
readelf -l /tmp/add2 > /tmp/add2.elf
diff -C 5 /tmp/add1.elf /tmp/add2.elf

Ok, so now we need to jump to the code and then jump back.

“detour” and “trampoline”

echo "jmp  . + 0x16" | as -o /tmp/toy - 
objdump -d /tmp/toy

We can add patch code after .fini at 0x1151, which let’s pad t 0x1154 just for good measure (alignmnent).

objdump -d /tmp/add1

0x112D: EB 16
0x1154: 8d 47 01 
        EB back

Dynamic Interposition

Some libraries are dynamically linked. These can be patched by using the dynamic linking mechanisms


gdb ptrace. Ptrace is the mechanism gdb uses.

Code Caves

Clobber unused code. Maybe require patching that code too. Big enough binaries are full of junk. Get a symbol dump and maybe ask chatgpt. Maybe just maybe there’s a pile of nops somewhere. Look for them Superoptimize some code that is

Elf padding Add new segment

Virus techniques tend to be applicable, but we’re using them for good right? Viruses need to find ways to add exectuable code too. We don’t care about detection.

You tend to want space closer rather than farther from your patch point

Make the file relinkable via that weird project?

Add Bounds Check

int patch_fun(int a[],int i){
    return a[i];

int patch_fun(int a[],int i){
    if(i < 3 && i >= 0){
        return a[i];
    return -1;

int main(){
    int x[] = {5,4,3};
    return patch_fun(x, 3);

Null Check

#include <stddef.h>

int patch_fun(int *x){
    return *x;

int patch_fun(int *x){
    if(x == NULL){
        return -1;
    return *x;

int main(){
    int x = 5;
    return patch_fun(&x);

High Patching

import subprocess
import re
from typing import List

sysv_params = ["edi", "esi", "edx", "ecx", "r8d", "r9d"]
sysv_callee_saved = ["rbx", "rbp", "r12", "r13", "r14", "r15"]
sysv_caller_saved = []

def multi_replace(text, rep):
    rep = dict((re.escape(k), v) for k, v in rep.items()) 
    pattern = re.compile("|".join(rep.keys()))
    return pattern.sub(lambda m: rep[re.escape(], text)

Reg = str
# Step 1: Compile a C file to assembly using GCC
def compile_patch(c_code, in_c : str, in_reg : List[Reg], out_reg, saved=[]):
    assert(len(in_reg) <= len(sysv_params))
    assert(len(out_reg) <= len(sysv_params))

    asm_file = "/tmp/dummy.S"
    # Build dummy C func
    # (int (*)(int)) consider using cast function pointer
    with open("/tmp/dummy.c", 'w') as temp_file:
        stub = f"""
        int dummy_callback({out_reg});
        int dummy_stub({in_c}) {{
            // int (*)(int)dummy_callback = return_address;
            return dummy_callback({out_reg});

        }}""" #;re
        # positin ndependent, O2 for tail cal optimization["gcc", "-O2", "-Wall", "-fPIC", "-S", "/tmp/dummy.c", "-o", asm_file], check=True)
    except subprocess.CalledProcessError as e:
        print("An error occurred during compilation:", e)
        raise e

    # Step 2: Read the assembly file and perform replacements
    with open(asm_file, 'r') as file:
      assembly_code =
    swap_reg = {k : v for k,v in zip(sysv_params, in_reg)}
    asm = multi_replace(assembly_code,swap_reg)
    with open(asm_file, 'w') as file:

compile_patch("a = a + b;", "int a, int b", ["eax", "ebx"], "a")


diff readelf

what can go wrong?

models of loading

Diffing nice summary ghidra diffing

This is not working right. I dunno why

echo "int bar(int x){printf(\"fofoo\");return x*x + 3;} int main(int x){return bar(x);}" > /tmp/foo1.c
gcc -O1 -gdwarf-4 /tmp/foo1.c -o /tmp/foo1.o
echo "int bar(int x){printf(\"fofofo\");return x*x + 5;} int main(int x){return bar(x);}" > /tmp/foo2.c
gcc -O1 -gdwarf-4 /tmp/foo2.c -o /tmp/foo2.o
ghidriff /tmp/foo1.o /tmp/foo2.o --engine VersionTrackingDiff

patch diffing bindiff binexport binexport

Also ghidra has built in diffing diffing portal Quokka - binary export Qbindiff elie mengin - binary diffing as network alignment - Diaphora, the most advanced Free and Open Source program diffing tool.

#radare / ghidra
echo "int foo(int x){return x*x + 3;}" > /tmp/foo1.c
gcc -O1 /tmp/foo1.c -c -o /tmp/foo1.o -fverbose-asm
echo "int foo(int x){return x*x + 5;}" > /tmp/foo2.c
gcc -O1 /tmp/foo2.c -c -o /tmp/foo2.o -fverbose-asm

objdump -d /tmp/foo1.o > /tmp/foo1.o.asm
objdump -d /tmp/foo2.o > /tmp/foo2.o.asm
#diff --color -C 2 /tmp/foo1.o.asm /tmp/foo2.o.asm
difft /tmp/foo1.o.asm /tmp/foo2.o.asm > /tmp/foo1.o.asm.diff

readelf -a /tmp/foo1.o > /tmp/foo1.o.elf
readelf -a /tmp/foo2.o > /tmp/foo2.o.elf
#diff --color -C 2 /tmp/foo1.o.elf /tmp/foo2.o.elf
difft /tmp/foo1.o.elf /tmp/foo2.o.elf > /tmp/foo1.o.elf.diff

# Make an html? markdown -> pdf
# checklist
# Make a CI thing?
# Print some kind of diffed CFG? radare maybe
# ghidra or angr
# dump into a section
echo "
# Patching Checklist
- [] metadata integrity
- [] decompilation makes sense
- [] Bug detected in original
- [] No changes in unexpected places
- [] Bug fixed in patched
- [] Tested details: _______________________________

# AI summary
# Program Info
## file1 
### Source
## file2 
### Source

# Patch Code

# ReadElf Diff
${cat /tmp/foo1.o.elf.diff}
# Objdump Diff
${cat /tmp/foo1.o.elf.diff}
# Ghidra Diff
" | llm -s "Look for possible problems with the following binary patch and give a high level summary. This will go into a report for a human to look at later"

bsdiff and bspatch tools for diffing and applying patches



So how do i even build this thing? Why musl?

libelfmaster libelf lief preloading the linker export ghidra elf elf cheatsheet - simple loader stelathy elf loader. oh god is this using bash to examine elf data?

kprobe ecfs binary protector kernel rootkit

dynamic linker fuzzing

echo "int foo(){
    return 42;
" | gcc -O1 -c -o /tmp/foo.o -x c -
objdump -d /tmp/foo.o

Copying over code a la jit and executing.

echo "
#include <stdio.h>
#include <stdint.h>
#include <sys/mman.h>
#include <string.h>

uint8_t foo[] = {
   0xf3, 0x0f, 0x1e, 0xfa,              //  endbr64 
   0xb8, 0x2a, 0x00, 0x00, 0x00,        //  mov    $0xa,%eax
   0xc3                    //  ret    

int main(){
    printf(\"hello world\n\");
    void* ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    memcpy(ptr, foo, sizeof(foo));
    int (*func)() = ptr;
    int retval = func();
    printf(\"%d\n\", retval);

    return 0;
" | gcc -Wall -Wextra -o /tmp/a.out -x c -


echo "
int f(int x);
__attribute__((regcall)) int foo(int x, int y, int z, int w){
    [[clang::musttail]] return f(x + y + z + w);
" | clang -O1 -c -o /tmp/foo.o -x c - copy and patch


echo "
int get_x();
int get_y();
int f(int x);
int foo(){
    int x = get_x();
    int y = get_y();
    return f(x + y);
} " | gcc -fPIC -x c -c -o /tmp/foo.o - 
gcc -shared /tmp/foo.o -o /tmp/
readelf -a /tmp/
objdump -d /tmp/

bnd jmp instruction is intel mpx memory protection plt.sec is form other security protections

echo "
// shiva style patch

void special(){
    reg = rax;



#/lib/ --list /bin/ls
/lib64/ --list /bin/ls

examining stuff just "printf" style seems kind of nice. I don't like using gdb even for normal stuff.
How can I examine (disassemble) current address? I guess this modifies to curreent code
echo '
#include "bhd.h"

int main(){
' | gcc -lbfd -o /tmp/a.out -x c - 

Using llvm mcdiassembler

ecfs pocgtfo 7 core dumps relationship if any to

Lotan - leviathon security

maskray symbol interposition code models. large code model relocation overflow “All programs should be built with -Bsymbolic and -fno-semantic-interposition. All symbols should be hidden by default.”





What about frida? dyninst intel pin injects quickjs

# injecting into binary. tracing all functions mtching a pattern
echo "
int foo( int x){
    return x * 20;

int main(){
    printf(\"hello world\n %d\", foo(7));
    return 0;
}" | gcc -x c -o /tmp/a.out -
objdump -d /tmp/a.out

frida-trace -i "recv*" -i "read" -i "write" -i "foo" /tmp/a.out


e9tool --help
e9patch --help
e9tool -M jmp -P print true --output /tmp/true.patch

--loader-static That’s interesting. --loader-phdr smash note, relro or stack phdr. plugins huh.

echo "
    .global _start

    .section .data
    .string \"Hello, World!\n\"

    .section .text
    # Write "Hello, World!" to stdout
    mov \$1, %rax        # System call number for sys_write
    mov \$1, %rdi        # File descriptor 1 (stdout)
    lea helloMessage(%rip), %rsi  # Pointer to the message
    mov \$14, %rdx       # Message length (including newline)
    syscall             # Invoke the system call

    # Exit the program
    mov \$60, %rax       # System call number for sys_exit
    xor %rdi, %rdi      # Exit status 0
    syscall             # Invoke the system call
" > /tmp/hello.s
gcc -nostartfiles -no-pie -nostdlib /tmp/hello.s -o /tmp/hello
objdump -d /tmp/hello

echo "
int my_patch(){

Patcherex i need this?

echo "
int foo(int x){
    return 20;
import patcherex2


PHDR patching

Move phdr to back end of binary, update the elf header pointer Move sections following phdr to back end to make space Asking for some space program header entries to fill in later.


Resources where execve is defined liballocs libdlbind metalevel runtime services for unix procieess. Unix as smalltalk The Minimalistic x86/x64 API Hooking Library for Windows

Frida - dynamic interposition rust detouring Simple C library for detour hooking in linux.

Game cheat community is good at hooking

“Instrumentation” is basically about patching. People instrument for logging or profiling por securty checks binary instrumentation profiler

Dynamic linking is basically a form of patching. “Hooking” gate

Hook detection C++11 memory patching and code hooking library. Capstone mov edi,edi hot patching. 5 nops before function. small jump there. kpatch.

In Practical Binary Analysis, there is chapter 7 and appebndix B. A tool called elfinject Nice chart of small patches diaphora diffing tool A Method to Evaluate CFG Comparison Algorithms

Reassembly is Hard: A Reflection on Challenges and Strategies goodman on binary rewriting binrec - lift program merge lifted bytecode into debloated egalito BOLT lifting bits/grr

code = '''
int fact(int x){
  int acc = 1;
  while(x > 0){
    acc *= x;
  return acc;
def fragment(x, header, invars, outvars):
  return f"""
    vod cb();
    void fragment(uint64_t r0, uint64_t r1, uint64_t r2, uint64_t r3, uint64_t r4){{ //varargs
          // inavrs
          // patch code
          // stack outvars{outvars}
# Hmm. could verify the permutation
def permute(asm, perm):
  # simultaneous replacement
  for k in perm.keys():
    asm = asm.replace(k, "TEMPYTEMPY"+k)
  for k,i in perm.items():
    asm = asm.replace("TEMPYTEMPY"+k, i)
  return asm

def clip_ret(asm):

def used_regs(asm, regs):
  return [reg for reg in regs if asm.find(reg) != -1]

import tempfile
import subprocess
import angr #, monkeyhex
import os
with tempfile.NamedTemporaryFile(suffix=".c") as fp:
  with tempfile.TemporaryDirectory() as mydir:
    outfile = mydir + "/fact"
    print(["gcc",  "-g",  "-c","-O1", "-o",  outfile,], check=True))

ms_hook_prologue & hotpatch are also patching relating attributes old?

Adding nops in places that are meant to be patcjable using inline asm making executable relocatable A small utility to modify the dynamic linker and RPATH of ELF executables. not insanely complicated. C and C++ Hot-Reload/Live Coding talk slides on how it works Liberating the Smalltalk lurking in C and Unix” by Stephen Kell Solaris dbx could “fix” Interactive Programming in C handbmade hero loading game code dynamically