elf-got-patcher
April 27, 2026 · View on GitHub
What is elf-got-patcher?
elf-got-patcher is an open-source, config-driven static binary patcher for ARM64 (AArch64) ELF executables. It injects a pure-C shellcode payload into an existing code cave, hijacks .init_array via RELA addend modification, and installs a GOT hook — all without modifying the original code or data segments.
The entire hook logic is written in pure C (no hand-assembled hex), compiled with Android NDK clang, and extracted as a flat binary. A sentinel-based configuration pool allows the same shellcode to be reused across different target binaries by simply changing a JSON config file.
Use Cases
- Security Research — Analyze network protocols of obfuscated binaries by intercepting
sendto/sendcalls - CTF / Reverse Engineering — Practice ELF patching, GOT hooking, and OLLVM deobfuscation techniques
- Protocol Analysis — Replace identifiers (e.g., App IDs) in outbound traffic to redirect API calls
- Binary Instrumentation Education — Learn how
.init_arrayhijacking, GOT hooking, and code caves work in practice - Anti-cheat / License Research — Study how software protection mechanisms can be bypassed (for authorized testing only)
Understanding GOT Hooking
What is the GOT?
The Global Offset Table (GOT) is a data structure used by ELF binaries to resolve addresses of external (dynamically linked) functions at runtime. When a program calls an external function, it doesn't jump directly to libc — instead it reads the real address from the GOT and branches there.
Normal call flow:
code → GOT[target_func] → libc target_func()
After GOT hook:
code → GOT[target_func] → hook_func() → scan/replace buffer → libc target_func()
Key insight: The GOT resides in a rw- (read-write) memory segment, which means we can modify function pointers at runtime to hijack any external function call.
Choosing a Hook Target
This tool can hook any GOT-visible external function. Choose the target based on your reverse engineering analysis:
| Target Function | Use Case | buf arg | len arg |
|---|---|---|---|
sendto(fd, buf, len, ...) | UDP networking | arg1 | arg2 |
send(fd, buf, len, flags) | TCP networking | arg1 | arg2 |
write(fd, buf, len) | General I/O | arg1 | arg2 |
SSL_write(ssl, buf, len) | TLS encrypted traffic | arg1 | arg2 |
custom_func(ctx, buf, ...) | Custom protocol functions | varies | varies |
By hooking these functions, you can:
- Inspect outbound payloads before they leave the process
- Modify specific byte patterns (e.g., replace an App ID, version string) in-flight
- Log traffic for protocol reverse engineering
- Redirect requests by altering parameters
Default:
hook.cships with asendtohook. To hook a different function, search for[CUSTOMIZE]comments in the source. Functions sharing the same(fd, buf, len, ...)layout (likewrite,send) only require changing the GOT address in the JSON config — no code changes needed.
How It Works (Diagram)
┌──────────────────────────────────────────────────────────┐
│ ELF Binary (before patch) │
│ │
│ .init_array[N] ──RELA──▶ original_init() │
│ GOT[target] ────────▶ libc target_func (obfuscated) │
│ code cave ────────▶ 0x00 0x00 0x00 ... (zeros) │
└──────────────────────────────────────────────────────────┘
▼ patcher.exe + config.json ▼
┌──────────────────────────────────────────────────────────┐
│ ELF Binary (after patch) │
│ │
│ .init_array[N] ──RELA──▶ init_wrapper() ◄── code cave │
│ │ │
│ ├─ call original_init() │
│ ├─ save real func addr to BSS │
│ └─ GOT[target] = hook_func │
│ │
│ hook_func(): │
│ ├─ scan buffer for needle │
│ ├─ replace with target string │
│ └─ tail-call real target_func │
└──────────────────────────────────────────────────────────┘
GOT Obfuscation (OLLVM)
Some binaries (especially those protected by OLLVM) don't store the real function address in the GOT. Instead, they store an obfuscated value:
GOT[target] = real_addr + CONSTANT (obfuscated)
real_addr = GOT[target] - CONSTANT (de-obfuscate)
elf-got-patcher handles this via the got_addend config field. The shellcode:
- Reads the obfuscated GOT value
- Subtracts the constant to get the real function address
- Saves the real address for later use
- Writes
hook_func + CONSTANTback to the GOT (preserving the obfuscation scheme)
How It Works
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ config.json │───▶│ patcher.exe │───▶│ patched ELF │
│ (addresses) │ │ (host x64) │ │ (target ARM64) │
└─────────────┘ └──────┬───────┘ └─────────────────┘
│
┌──────┴───────┐
│ hook.bin │
│ (ARM64 flat) │
└──────────────┘
- Shellcode (
hook.c) is compiled to a flat ARM64 binary (hook.bin) using NDK clang with-mcmodel=tiny(pure PC-relative, no ADRP page alignment issues) - Patcher (
main.c) reads a JSON config, locates sentinel markers (0xCAFEBABE...) in the shellcode, fills in runtime addresses, verifies the code cave, writes the payload, and patches the RELA addend - At runtime:
.init_arraycallsinit_wrapper→ saves real function address → installs hook in GOT → every call to the target function flows through the hook for string replacement
Project Structure
elf-got-patcher/
├── shellcode/
│ ├── hook.c Pure C shellcode (init_wrapper + hook_sendto + scan_and_replace)
│ ├── hook.ld Linker script (controls section layout)
│ └── Makefile NDK clang build → hook.bin
├── patcher/
│ └── main.c ELF parser + sentinel patcher + cave writer + RELA modifier
├── configs/
│ └── mg_35029_to_51642.json Example config (with // comments)
├── build.bat One-click build script (Windows)
├── LICENSE GPL-3.0
└── README.md
Quick Start
Prerequisites
- Android NDK r21+ (for shellcode compilation:
aarch64-linux-androidclang) - Any C99 compiler (for patcher: MSVC
cl.exe, MinGWgcc, or Linuxgcc/clang)
Build
# Set NDK path
export ANDROID_NDK="/path/to/ndk" # Linux/macOS
$env:ANDROID_NDK = "C:\path\to\ndk" # Windows PowerShell
# Build everything
./build.bat # Windows
# or: make -C shellcode && gcc -O2 -o patcher patcher/main.c # Linux
Run
./patcher.exe configs/mg_35029_to_51642.json
Output:
[+] payload shellcode/hook.bin : 504 bytes
[+] g_config patched at payload off 0x180 (self_va=0x7d4c0)
[+] cave is 504 bytes of zero, fits.
[+] RELA addend verified = 0xef320
=========================================
PATCH OK : input -> output
payload @ file 0x7d340 (VA 0x7d340) 504 bytes
RELA addend @ 0x1ae8 : 0xef320 -> 0x7d340
=========================================
Deploy to Device
adb push output_elf /data/local/tmp/
adb shell "su -c 'cp /data/local/tmp/output_elf /target/path; chmod 755 /target/path'"
Adapting to a New Target
Create a new JSON config. Use readelf and IDA/Ghidra to extract:
| Field | Description | How to Find |
|---|---|---|
cave_va / cave_off | Code cave address | readelf -l → find zero-filled region in r-x PT_LOAD |
rela_addend_off | RELA addend file offset | readelf -r → R_AARCH64_RELATIVE entry in .init_array |
orig_init | Original init function VA | The RELA addend's original value |
got_sendto | GOT entry VA for target function | IDA → trace call wrapper to LDR from GOT |
got_addend | GOT obfuscation constant | IDA → MOVZ+MOVK×3 in wrapper (0 if no obfuscation) |
saved_sendto | Writable BSS slot VA | readelf -l → end of rw- PT_LOAD BSS region |
needle / replace | Search / replace strings | Application-specific (must be equal length) |
Hooking a Different Function
The default hook targets sendto, but you can hook any GOT-visible function by editing hook.c. Look for [CUSTOMIZE] comments:
- Change the typedef & function signature to match your target (e.g.,
write,send, or a custom function) - Adjust which arguments are the buffer pointer and length in the
scan_and_replacecall - Set
got_sendtoin the JSON config to your target function's GOT entry VA
For functions with the same (fd, buf, len, ...) layout (like write, send), no code change is needed — just update the GOT address in the config.
Design Highlights
- Pure C shellcode — readable, modifiable, no hand-assembled ARM64 hex
-mcmodel=tiny— all addressing is pure PC-relative (ADR), no ADRP page-alignment issues at arbitrary code cave locations- Sentinel-based config — patcher locates
g_configby scanning for0xCAFEBABEmarkers, fills addresses dynamically - ASLR-safe — runtime base computed via
&g_config - self_va, all VA fields adjusted by base offset - Zero-invasive — only writes to zero-filled cave + modifies 8-byte RELA addend; original code/data untouched
- Config-driven — same
hook.binworks for any target; just change JSON addresses
Disclaimer
This tool is provided for educational and authorized security research purposes only. The authors are not responsible for any misuse. Users must comply with all applicable laws and obtain proper authorization before modifying any software.
License
This project is licensed under the GNU General Public License v3.0.
Contributing
Issues and Pull Requests are welcome. Please follow the existing code style and include comments in your submissions.
Acknowledgments
- Android NDK — ARM64 cross-compilation toolchain
- LLVM/Clang — Compiler infrastructure
- The reverse engineering community for sharing knowledge on ELF internals, GOT hooking, and OLLVM analysis