# Exploitation

> 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'.

- Skill: `netvar1337/exploitation` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add netvar1337/exploitation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/netvar1337/exploitation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: netvar1337 (https://skillmd.com/u/netvar1337)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/netvar1337/exploitation

---


## 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.

