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
-- every gadget, cheapest possible query
SELECT COUNT(*) FROM rop_gadgets;
-- return-terminated gadgets mentioning "pop" (classic stack-cleanup gadget)
SELECT printf('0x%x', addr) AS at, classes, ninstrs FROM rop_gadgets
WHERE terminator = 'ret' AND pattern = 'pop';
-- stack pivots (change esp/rsp to attacker-controlled data)
SELECT printf('0x%x', addr) AS at, retaddr, classes FROM rop_gadgets
WHERE klass = 'pivot';
-- syscall gadgets
SELECT printf('0x%x', addr) AS at, classes FROM rop_gadgets
WHERE klass = 'syscall';
-- write-what-where primitives
SELECT printf('0x%x', addr) AS at, classes FROM rop_gadgets
WHERE klass = 'www';
-- shortest gadgets first (fewer side effects to account for)
SELECT printf('0x%x', addr) AS at, size, ninstrs, classes
FROM rop_gadgets ORDER BY ninstrs ASC LIMIT 20;
-- read one gadget's instruction sequence, once you have its address
-- (apply the SAME filters used to find it -- see Caveats)
SELECT seq, printf('0x%x', addr) AS at, opcode, type
FROM rop_gadget_instructions
WHERE gadget_addr = 0x140001284 AND terminator = 'ret'
ORDER BY seq;
-- cap a broad, unfiltered sweep
SELECT printf('0x%x', addr) AS at, classes FROM rop_gadgets
WHERE max_results = 50;
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/klassto bothrop_gadgetsandrop_gadget_instructionswhen 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_instructionsagainst manyrop_gadgetsrows at once. There is no radare2 primitive that resolves a single gadget by address, so a query likerop_gadgets g JOIN rop_gadget_instructions gi ON gi.gadget_addr = g.addrwith no narrow filter re-runs the whole gadget search once per outer row. For "every gadget's instructions", queryrop_gadget_instructionsdirectly with the filters you want (it already carriesgadget_addr) instead of joining. For one specific gadget,WHERE gadget_addr = <addr> AND <same filters>is cheap (one search). retaddris 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 literalretmnemonic (e.g.int3, which radare2's own return-class test still counts) —rop_gadget_instructions.typereflects that classification.opcodeonrop_gadget_instructionsis rendered disassembly text (same renderer asinstructions.disasm), not raw bytes.classesis 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) |
-- what does syscall number 60 do on this target?
SELECT name FROM syscalls WHERE num = 60;
-- what's the syscall number for a name you already know?
SELECT num FROM syscalls WHERE name = 'execve';
-- full table (small -- hundreds of rows, one command)
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.