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'.
Installs into .claude/skills of the current project.
Are you the author of Exploitation?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/netvar1337-exploitation)
---
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
```sql
-- 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`/`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`) |
```sql
-- 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.