Metadata-Version: 2.3
Name: ropinator
Version: 0.2.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: cmd2>=3.2.0
Requires-Dist: keystone-engine>=0.9.2
Requires-Dist: mcp>=1.0.0
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

Find gadgets:

```
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 to file:

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

Load previously exported gadgets:

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

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

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

Override architecture detection:

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

Expand all gadget addresses:

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

## Constraint Solver

Launch the interactive solver to search gadgets by semantic behavior:

```
ropinator -f binary.elf --start-solver
```

### Commands

| Command | Description |
|---------|-------------|
| `pivot [src] [--max-offset N] [--max N]` | Find stack-pivot gadgets |
| `move [dst] [src]` | Find register-to-register moves |
| `const <dst> <value>` | Find gadgets that load a constant |
| `load <dst> <src> [offset]` | Find memory read gadgets |
| `store <addr_reg> [src] [offset]` | Find write-what-where gadgets (`mov [addr+off], src`) |
| `setreg [dst]` | Find controllable setters (`pop reg ; ret`) |
| `arith <dst> add\|sub <src1> [src2]` | Find arithmetic gadgets |
| `search <pattern> [--regex]` | Raw instruction-text search (all architectures) |
| `branch [syscall\|call\|jmp]` | Find control-transfer gadgets (fire the chain) |
| `setregs reg=val ...` | Bundle: all gadgets needed to set several registers |
| `plan_call [--abi A] [--target T] arg...` | Bundle: gadgets to call a function/syscall with args |
| `show <index> [--all]` | Inspect symbolic register state |
| `export <file>` | Export results to file |
| `registers` | List available registers |

`search` and `branch` are structural (no symbolic execution) and work on **every**
supported architecture. The semantic commands (`move`, `const`, `load`, `store`,
`setreg`, `arith`, `pivot`) require the constraint solver (x86-64, x86, ARM64).

### Examples

#### Stack pivots

```
rop> pivot rcx
Found 3 pivot gadget(s) (rsp <- rcx):

  [0] 0x0000000000401000  rsp <- rcx + 0x8   mov rsp, rcx ; ret            [clobbers: none]
  [1] 0x0000000000401010  rsp <- rcx + 0x8   xchg rsp, rcx ; ret           [clobbers: rcx]
  [2] 0x0000000000401020  rsp <- rcx + 0x18  lea rsp, [rcx + 0x10] ; ret   [clobbers: none]
```

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]`.

Search all registers for any pivot:

```
rop> pivot
```

Limit to pivots that land within 0x10 bytes of the source:

```
rop> pivot rcx --max-offset 0x10
```

Memory pivots (`mov rsp, [rax+0x10]`) are found with the `load` command:

```
rop> load rsp rax 0x10
```

#### Register moves

```
rop> move rax rbx
Found 3 move gadget(s):

  [0] 0x0000000000401000: rax <- rbx | mov rax, rbx ; ret
  [1] 0x0000000000401020: rax <- rbx | push rbx ; pop rax ; ret
  [2] 0x0000000000401040: rax <- rbx | xchg rax, rbx ; xchg rax, rbx ; mov rax, rbx ; ret
```

#### Constant loads

```
rop> const rax 0xdeadbeef
Found 1 constant-loading gadget(s):

  [0] 0x0000000000401234: rax = 0xdeadbeef | pop rax ; ret
```

#### Memory reads

```
rop> load rax rbx 0x10
Found 2 load gadget(s):

  [0] 0x0000000000402000: rax <- [rbx + 0x10] | mov rax, qword ptr [rbx + 0x10] ; ret
  [1] 0x0000000000402030: rax <- [rbx + 0x10] | lea rcx, [rbx + 0x10] ; mov rax, qword ptr [rcx] ; ret
```

#### Inspecting results

`show` prints only the registers changed by the gadget. Add `--all` to see the full
register file:

```
rop> show 0
Result [0]
  Address:      0x0000000000401000
  Instructions: mov rsp, rcx ; ret
  Effect:       rsp <- rcx + 0x8

  Changed registers:
    rsp    = rcx + 0x8

  (15 registers unchanged — use 'show 0 --all' to see them)
```

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

```
-f, --file FILE           Binary to analyze (required unless --arch and -g are used)
-b, --base ADDR           Override base address
-d, --depth N             Max gadget depth (default: 3)
-a, --all                 Expand all gadget addresses
-o, --output FILE         Export gadgets to file
-g, --gadgets-file FILE   Load gadgets from exported file instead of searching
--arch ARCH               Override architecture detection (see below)
--start-solver            Launch constraint solver shell
```

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
- [cmd2](https://cmd2.readthedocs.io/) - interactive shell framework

## License

[GPL-3.0](LICENSE)
