| name | exploitation |
| description | Search for ROP/JOP/COP gadgets in a binary via r2xsql's rop_gadgets and rop_gadget_instructions tables, and look up syscall names/numbers via the syscalls reference table. Use when building an exploit chain, hunting for stack-pivot/syscall/write-what-where primitives, resolving a target's syscall table, or answering 'what gadgets are available here'. |
| allowed-tools | ["Bash","Read"] |
When to use
Pick this skill when the task is finding usable code sequences for
control-flow hijacking, not understanding normal program logic:
- return-, call-, or jump-terminated gadget search (
rop_gadgets)
- reading a specific gadget's instruction sequence (
rop_gadget_instructions)
- filtering by radare2's own gadget-class vocabulary (
pivot, syscall,
www / write-what-where, …)
For ordinary disassembly/decompilation of a function, use disassembly or
decompiler instead — this skill is specifically about short, out-of-context
instruction sequences ending in a control-flow transfer.
Tables
| table | source | columns |
|---|
rop_gadgets | /gj / /gRj / /gCj / /gJj | addr, retaddr, size, classes, ninstrs; hidden pattern, terminator, klass, max_results |
rop_gadget_instructions | same search, flattened | gadget_addr, seq, addr, size, opcode, type; same four hidden inputs as rop_gadgets |
No predicate is required on either table — an unfiltered scan is a real,
bounded answer (a few hundred milliseconds on a typical binary). The search
scope defaults to executable memory only (io.maps.x), not radare2's own
CLI default (io.maps, which also matches non-code data and can report
false-positive "gadgets" built from string tables).
Common queries
SELECT COUNT(*) FROM rop_gadgets;
SELECT printf('0x%x', addr) AS at, classes, ninstrs FROM rop_gadgets
WHERE terminator = 'ret' AND pattern = 'pop';
SELECT printf('0x%x', addr) AS at, retaddr, classes FROM rop_gadgets
WHERE klass = 'pivot';
SELECT printf('0x%x', addr) AS at, classes FROM rop_gadgets
WHERE klass = 'syscall';
SELECT printf('0x%x', addr) AS at, classes FROM rop_gadgets
WHERE klass = 'www';
SELECT printf('0x%x', addr) AS at, size, ninstrs, classes
rop_gadgets ninstrs LIMIT ;
seq, printf(, addr) , opcode, type
rop_gadget_instructions
gadget_addr terminator
seq;
printf(, addr) , classes rop_gadgets
max_results ;
klass vocabulary
radare2's own gadget-class names, used verbatim (never invent a parallel
vocabulary): ret, jop, cop, cond.always, cond.controlled,
syscall, pivot, memread, memwrite, www (write-what-where),
rww (read-what-where), signal, mov, ldconst, arithm, logic,
shift, cmp, nop, arithm_ct. cond.always/cond.controlled only
appear when the radare2 session has gadget.esil enabled (off by default).
Caveats
- The unfiltered ("any" terminator) gadget set is not a strict superset of
a terminator-filtered one, address for address. radare2's gadget
construction walks backward from qualifying end instructions and consumes
start bytes per pass, so a start address one search finds is not
guaranteed to also start a gadget in a differently-filtered pass. Always
apply the same
pattern/terminator/klass to both rop_gadgets and
rop_gadget_instructions when relating a row in one to a row in the
other — an unfiltered lookup on one side and a filtered one on the other
can silently return nothing even though the gadget clearly exists.
- Do not join
rop_gadget_instructions against many rop_gadgets rows at
once. There is no radare2 primitive that resolves a single gadget by
address, so a query like rop_gadgets g JOIN rop_gadget_instructions gi ON gi.gadget_addr = g.addr with no narrow filter re-runs the whole gadget
search once per outer row. For "every gadget's instructions", query
rop_gadget_instructions directly with the filters you want (it already
carries gadget_addr) instead of joining. For one specific gadget,
WHERE gadget_addr = <addr> AND <same filters> is cheap (one search).
retaddr is the gadget's own terminating instruction's address — not a
resolved branch target, despite the name. A "R" (return-class) gadget's
terminating instruction can be something other than a literal ret
mnemonic (e.g. int3, which radare2's own return-class test still
counts) — rop_gadget_instructions.type reflects that classification.
opcode on rop_gadget_instructions is rendered disassembly text (same
renderer as instructions.disasm), not raw bytes.
classes is free (no extra command, no config gate) — it is always safe
to select even on a broad scan.
Syscall reference
syscalls is a static per-(arch,bits,os) name/number lookup table
(backed by asj — not aslj, which does not exist). It is a reference
dictionary keyed by the current target's architecture/OS, not derived
from the binary's own bytes — the same table exists whether or not the
binary makes any syscalls at all.
| table | source | columns |
|---|
syscalls | asj | name, num, arch, os (arch/os echo asm.arch/asm.os) |
SELECT name FROM syscalls WHERE num = 60;
SELECT num FROM syscalls WHERE name = 'execve';
SELECT name, num FROM syscalls ORDER BY num;
Where this fits with the gadget search above: rop_gadgets WHERE klass = 'syscall' finds gadgets that end in a syscall instruction; syscalls
tells you what a given number/name means once you have one. They answer
different questions and are not joined to each other (there is no shared
key — a gadget's classes field doesn't carry a resolved syscall number).
Where syscall instructions are in the binary is a different question,
already answered without this table: SELECT addr FROM instructions WHERE op_type = 'swi' finds every syscall/software-interrupt instruction site —
no syscalls lookup needed for that.
What this table does NOT do: resolve which syscall number a specific
swi instruction actually makes. radare2's own static emulation cannot
reliably determine that on realistic compiler-generated code — the number
is typically set in a caller and reloaded across a function-call boundary
into a syscall wrapper (e.g. glibc's syscall()), which single-function
emulation does not follow. This project deliberately does not ship a
best-effort/guessed resolution column for that — a value that is sometimes
right and silently wrong is worse than none. Don't try to derive it by
joining syscalls against instructions/esil_ops; there is nothing
reliable to join on.