IDA plugin for instrumenting bare-metal binaries without debugging capabilities.
Select any instruction in IDA, Greffe replaces it with a branch to a user-written C handler. The original instruction is relocated and executed transparently after the handler returns.
Instruction relocation is powered by frida-gum. Currently supported architectures: ARM, Thumb, ARM64.
git clone --recursive https://github.com/Lixhr/Greffe.git
IDA plugin
Copy ida_greffe/ and ida_entry.py into your IDA plugins directory.
CLI
sudo apt-get install make g++ libreadline-dev libzmq3-dev nlohmann-json3-dev libelf-dev libglib2.0-dev
cd greffe_cli && makeDepending on your target, a cross-compiler must be available in your $PATH.
Open your binary in IDA, then load the plugin: Edit -> Plugins -> Greffe.
This starts the IDA-side socket server. Then, in a separate terminal, run the CLI:
./greffe_cli/build/greffeThe CLI connects to IDA and creates __greffe_workdir/ next to the binary.
From IDA - right-click any instruction -> Add Greffe tracepoint (or Shift+G). The tracepoint then appears in the CLI.
From the CLI - by function name or address:
greffe> add my_func
greffe> add 0x8004A2C
A C stub is automatically created in __greffe_workdir/handlers/:
// handlers/my_func.c
void handler_my_func(void)
{
// bare-metal context - no libc
}You can add extra .c files in handlers/, all of them are compiled and linked into the same blob.
greffe> patch
Compiles all handlers and writes the patched binary alongside the original: firmware.bin.patched_. Flash it to your target.
Use save to persist the tracepoint list. It is automatically reloaded on the next CLI launch.
| Command | Description |
|---|---|
add <name|0xaddr> |
Register a tracepoint |
del <name|0xaddr> |
Remove a tracepoint |
list |
List registered tracepoints |
save |
Save state to workdir/.greffe |
patch |
Compile handlers and apply all tracepoints |
Flashing - Greffe produces a modified binary that must be flashed to the target. Any secure boot chain must be bypassed or disabled before.
Executable spare region - trampolines and compiled handlers are injected at patch_base. This region must be mapped as executable at runtime. Typical candidates: padding between sections, unused flash pages, a region reserved in the linker script.
Output channel - Greffe only patches the binary, it provides no data collection mechanism. Handlers need a way to exfiltrate results: UART, PWM, a very attentive oscilloscope, ... Whatever the target exposes.
No libc, no OS - handlers are compiled with -nostdlib. Any helper (logging, memory access) must be self-contained or reference existing firmware symbols explicitly. (beware of the ABI)
__greffe_workdir/
├── Makefile auto-generated
├── .greffe saved tracepoints
├── handlers/
│ ├── my_func.c <- edit this
│ └── usr_utils.c
└── build/
├── handlers.elf
└── handlers.bin