Metadata-Version: 2.3
Name: ropinator
Version: 0.3.0
Summary: A better way to build a ROP chain.
Author: Jeremy Dunn
Author-email: Jeremy Dunn <jeremy.dunn315@gmail.com>
Requires-Dist: capstone>=5.0.6
Requires-Dist: keystone-engine>=0.9.2
Requires-Dist: mcp>=1.0.0
Requires-Dist: textual>=0.80.0
Requires-Dist: textual-autocomplete>=4.0.6
Requires-Dist: z3-solver>=4.15.4.0
Requires-Python: >=3.13
Description-Content-Type: text/markdown

# ropinator

ROP gadget finder with constraint-based semantic search.

Finds gadgets in ELF, PE, Mach-O, and raw binaries across x86, ARM, MIPS,
PowerPC, and RISC-V. Includes a Z3-backed symbolic execution engine for
searching gadgets by behavior rather than text patterns.

## Install

```
pip install ropinator
```

Requires Python 3.13+.

## Usage

Running `ropinator` with no arguments launches the interactive TUI, where you
load binaries and search for gadgets — no flags required:

```
ropinator
```

Optionally preload a binary on startup:

```
ropinator -f binary.elf
```

Set search depth (max instructions per gadget, default 3):

```
ropinator -f binary.elf -d 5
```

Custom base address:

```
ropinator -f binary.elf -b 0x400000
```

Export gadgets to a file non-interactively (find, write, exit — the only batch mode):

```
ropinator -f binary.elf -o gadgets.txt
```

Preload previously exported gadgets into the TUI:

```
ropinator -f binary.elf -g gadgets.txt
```

Load gadgets without the original binary (requires `--arch`):

```
ropinator --arch x86_64 -g gadgets.txt
```

Override architecture detection:

```
ropinator -f binary.raw --arch arm -b 0x10000
```

Expand all gadget addresses:

```
ropinator -f binary.elf -a
```

## Interactive TUI

Launch the full-screen TUI and do everything from there — load a binary, search
by behavior, inspect, and export. No commands to memorize: you pick a search from
a sidebar, fill in a small form, and read the results in a table.

```
ropinator
```

With no binary loaded you land on a **load screen**: enter a file path, optionally
pick an architecture (defaults to auto-detect), depth, and base, then load. As you
type a path, a **dropdown lists the matching files and folders** (native Windows
paths included) — arrow keys or click to choose, and selecting a folder descends
into it. A second tab loads a previously exported gadgets file. (`ropinator -f
binary.elf` preloads and drops you straight into the workspace.)

The workspace has four parts:

- a **sidebar** of searches, grouped **Structural** / **Semantic** / **Plan**
  (semantic and plan searches are greyed out on architectures without a solver),
- a **form** that changes to match the selected search — register fields
  autocomplete from the loaded architecture,
- a **results table** (address · effect · instructions · notes), and
- a **detail panel** showing the full symbolic register state of the highlighted
  result.

| Key | Action |
|-----|--------|
| ↑/↓ or click | Pick a search in the sidebar |
| Enter / **Run search** | Run the current search |
| ↑/↓ in the table | Highlight a result → its register state fills the detail panel |
| ↑/↓, Enter/Tab, click (path fields) | Choose from the file dropdown; picking a folder descends into it |
| **a** | Toggle showing unchanged registers in the detail panel |
| **F2** | Command palette — jump straight to any search or action |
| **F3** | Load another binary |
| **F4** | Re-scan the current binary at a new depth (deeper = more, longer gadgets) |
| **F6** | Export the current results to a file |
| **F7** | Show the architecture's registers |
| **Ctrl-C** | Quit |

The hotkeys are function keys (plus Ctrl-C) chosen so nothing collides with VS
Code's default shortcuts when you run ropinator in its integrated terminal — and
because function keys reach the app even while a text field is focused.

While a search or a re-scan is running, a status box keeps you informed. Re-scan
(**F4**) re-runs gadget finding on the loaded binary with a new depth, so you can
start shallow (fast) and go deeper when you need longer gadgets.

### Searches

| Search | Fields | Finds |
|--------|--------|-------|
| Text search | pattern, regex | Raw instruction-text matches |
| Control transfer | kind (syscall/call/jmp/any) | Gadgets that fire the chain |
| Set constant | dst, value | `dst = value` |
| Pop / controllable set | dst | Controllable setters (`pop reg ; ret`) |
| Move register | dst, src | `dst = src` |
| Load from memory | dst, addr reg/literal, offset | `dst = [src + offset]` |
| Store to memory | addr reg, src, offset | Write-what-where (`mov [addr+off], src`) |
| Arithmetic | dst, op (add/sub), src1, src2 | `dst = src1 op src2` |
| Stack pivot | src, max offset | `rsp = src + constant` |
| Plan register goals | goals (`rdi=0x404000 rsi=0`) | Every gadget needed to set a group of registers |
| Plan a call | abi, target, args | Argument-register setters + a control transfer |

**Text search** and **Control transfer** are structural (no symbolic execution)
and work on **every** supported architecture. The semantic and plan searches
require the constraint solver (x86-64, x86, ARM64).

The two **Plan** searches don't return a result table — they render a
requirements bundle: for each register, the direct constant setters, the
controllable (pop) setters, and one-level move fallbacks, plus the control
transfer for a call. Nothing is ordered or emitted — it's the raw material for
building a chain.

### Stack-pivot offsets

For pivots, the offset is the signed difference `rsp_final = src + offset`. For a
clean x64 pivot (`mov rsp, rcx ; ret`) the offset is `+0x8` because `ret` consumes
one return address from the newly-pivoted stack — place your fake ROP stack
starting at `[rcx]`. Memory pivots (`mov rsp, [rax+0x10]`) show up under **Load
from memory** with `dst = rsp`.

## MCP Server

Ropinator ships an MCP server so AI agents (Claude, Cursor, etc.) can search
gadgets programmatically without a human at the shell.

### Setup

Add to your MCP client config (e.g. Claude Code's `.claude/settings.json`):

```json
{
  "mcpServers": {
    "ropinator": {
      "command": "ropinator-mcp"
    }
  }
}
```

Or if running from the repo with `uv`:

```json
{
  "mcpServers": {
    "ropinator": {
      "command": "uv",
      "args": ["run", "--project", "/path/to/ropinator", "ropinator-mcp"]
    }
  }
}
```

### Tools

Search tools return **JSON** so agents can parse results directly. Each result
has `address`, `instructions`, `type`, `effect`, and (where relevant) `clobbers`
or control `kind`/`target` fields.

| Tool | Description |
|------|-------------|
| `load_binary` | Load a binary and scan for gadgets |
| `load_gadgets_file` | Load gadgets from a previously exported file |
| `search_gadgets` | Raw instruction-text search — the generic finder (all arches) |
| `find_pivots` | Find stack-pivot gadgets (`src_reg`, `max_offset`) |
| `find_moves` | Find register-to-register move gadgets |
| `find_loads` | Find memory-read gadgets (also covers memory pivots) |
| `find_const` | Find constant-loading gadgets |
| `find_stores` | Find write-what-where gadgets (`mov [addr+off], src`) |
| `find_setreg` | Find controllable setters (`pop reg ; ret`) |
| `find_arith` | Find arithmetic gadgets (add/sub) |
| `find_control` | Find control-transfer gadgets (`syscall` / `call reg` / `jmp reg`) |
| `plan_registers` | Bundle: all gadgets needed to set several registers to values |
| `plan_call` | Bundle: all gadgets needed to call a function/syscall with args |
| `show_result` | Full register state for a result index (JSON) |
| `get_registers` | List GP registers for the loaded architecture |
| `session_status` | Summary of the loaded binary and cached results |

### Workflow

The server is stateful: call `load_binary` once, then run as many searches as
needed. Results from the most recent search are cached so `show_result(index)`
always works after any `find_*`/`search_gadgets` call.

```
load_binary(file_path="target.exe")
find_pivots(src_reg="rcx", max_offset="0x40")
show_result(0)
find_moves(dst_reg="rdi", src_reg="rax")
```

### Getting all gadgets for a chain

`plan_registers` and `plan_call` return a **requirements bundle**: every gadget an
agent needs to reach a goal, annotated with clobbers. The bundle gathers
candidates but does not order them or emit a payload — the agent composes the
chain from the returned options.

```
# Set up a Linux execve/mprotect-style syscall (SysV ABI: rdi, rsi, rdx, ...)
plan_call(args="0x404000 0 7", abi="sysv64", target="syscall")

# Or drive individual register goals directly:
plan_registers(goals="rdi=0x404000 rsi=0 rdx=7")
```

Each register entry lists `controllable` (pop-style — supply the value on the
fake stack), `direct` (constant setters), and a one-level `via_move` fallback,
plus a `control` section with syscall/call/jmp gadgets to fire the chain.

## Supported Formats

| Format | Description |
|--------|-------------|
| **ELF** | Linux, BSD, embedded |
| **PE** | Windows executables and DLLs |
| **Mach-O** | macOS, iOS |
| **Raw** | Flat binaries (use with `-b` to set base address) |

## Supported Architectures

**Gadget finding:** x86 (16/32/64-bit), ARM (32/64/Thumb), MIPS (32/64),
PowerPC (32/64), RISC-V (32/64).

**Constraint solver:** x86-64. Other architectures planned.

## Options

All options are optional. With none, `ropinator` launches the TUI; the flags
below just preload a binary/gadgets (or, with `-o`, run a one-shot export).

```
-f, --file FILE           Binary to preload into the TUI
-b, --base ADDR           Override base address
-d, --depth N             Max gadget depth (default: 3)
-a, --all                 Expand all gadget addresses (in the -o export)
-o, --output FILE         Non-interactive: find gadgets, export to file, and exit
-g, --gadgets-file FILE   Preload gadgets from an exported file
--arch ARCH               Override architecture detection (see below)
```

Architecture names for `--arch`:

| Name | Aliases |
|------|---------|
| x86_64 | x86-64, x64 |
| x86 | i386 |
| ARM64 | aarch64 |
| ARM32 | arm |
| ThumbBE | thumb-be |
| MIPS32 | mips |
| MIPS64 | |
| PowerPC32 | ppc, ppc32 |
| PowerPC64 | ppc64 |
| RISCV64 | riscv |

## Dependencies

Installed automatically via pip:

- [Capstone](https://www.capstone-engine.org/) - disassembly engine
- [Keystone](https://www.keystone-engine.org/) - assembler engine (for gadget file loading)
- [z3-solver](https://github.com/Z3Prover/z3) - symbolic execution backend
- [Textual](https://textual.textualize.io/) - interactive TUI framework

## License

[GPL-3.0](LICENSE)
