# ROP Gadget Hunting Toolkit

> A practical reference for finding, filtering, and verifying ROP gadgets with ropper, ROPgadget, and GDB-PEDA, plus chaining strategies.

- Published: 2025-09-18
- Difficulty: intermediate
- Series: Linux Exploitation Fundamentals
- Tags: Linux, Exploit Development, ROP, x64, Tools, GDB
- Source: https://stevenfoerster.com/tutorials/rop-gadget-hunting-toolkit/

## Prerequisites

- Understanding of ROP concepts and gadget chaining
- Basic GDB and GDB-PEDA usage
- Previous buffer overflow experience
- Familiarity with x86/x64 calling conventions

> [!tip]
> This tutorial serves as a reference companion to the ROP tutorials in this series. If you're new to ROP, start with [Bypassing NX with ROP on x64 Linux](https://stevenfoerster.com/tutorials/bypassing-nx-with-rop-x64/) for the concepts, then return here when you need to find specific gadgets.

When you sit down to build a ROP chain, the conceptual part, what you need the chain to _do_, is usually the easy half. The hard half is digging through a binary to find the gadgets that actually let you do it. ropper, ROPgadget, and GDB-PEDA each approach this problem differently, and knowing when to reach for which tool is the difference between a quick chain and an hour of scrolling through output.

A related technique, JOP (Jump-Oriented Programming), uses `jmp` instructions instead of `ret` to chain gadgets. JOP can bypass some ROP-specific defenses but is harder to construct due to the lack of a convenient stack-based dispatch mechanism.

This tutorial covers all three tools as a unified workflow: broad discovery with ROPgadget or ropper, targeted searching with ropper's semantic filters, and verification in GDB-PEDA before you commit a gadget to your chain.

## Installation

### ROPgadget

```bash
$ pip install ROPgadget
```

Or from source:

```bash
$ git clone https://github.com/JonathanSalwan/ROPgadget.git
$ cd ROPgadget && pip install .
```

### ropper

```bash
$ pip install ropper
```

### GDB-PEDA

```bash
$ git clone https://github.com/longld/peda.git ~/peda
$ echo "source ~/peda/peda.py" >> ~/.gdbinit
```

> **Note**: If you use multiple GDB extensions (pwndbg, GEF), manage them with a wrapper script or separate `.gdbinit` files rather than sourcing all of them at once.

### checksec

checksec inspects binary hardening properties. NX, stack canaries, PIE, and RELRO. Combined with runtime ASLR status, this tells you what protections you're up against.

Install from your package manager:

```bash
$ sudo apt install checksec        # Debian/Ubuntu
$ sudo pacman -S checksec          # Arch
```

Or via pwntools, which bundles its own implementation:

```bash
$ pip install pwntools
$ pwn checksec ./vuln
```

> **Note**: The standalone `checksec` and pwntools `pwn checksec` produce slightly different output formats, but report the same information. Either works.

## ROPgadget

ROPgadget takes a brute-force approach: it disassembles every possible instruction sequence ending in a `ret` (or other control-flow instruction) and dumps the results. This makes it excellent for initial discovery and bulk analysis.

### Basic Usage

Dump all gadgets from a binary:

```bash
$ ROPgadget --binary ./vuln
```

This produces a large list. For a typical dynamically-linked binary you might see hundreds of gadgets; for a statically-linked binary or libc, expect tens of thousands.

### Filtering by Instruction

Use `--only` to restrict output to gadgets containing specific instructions:

```bash
$ ROPgadget --binary ./vuln --only "pop|ret"
```

```bash
$ ROPgadget --binary ./vuln --only "mov|pop|ret"
```

The filter is an OR match; any gadget containing at least one of the listed mnemonics will appear.

### Searching for Strings

Find the address of a specific string in the binary:

```bash
$ ROPgadget --binary ./vuln --string "/bin/sh"
```

Search for all readable strings:

```bash
$ ROPgadget --binary ./vuln --string "sh"
```

### Finding String Bytes in Memory

When you need to locate useful bytes already present in memory:

```bash
$ ROPgadget --binary ./vuln --memstr "/bin/sh"
```

This searches for each character of the string across readable sections and reports where they can be found. It's useful when the target string does not exist intact in the binary.

### Searching Specific Sections

Limit gadget search to particular sections:

```bash
$ ROPgadget --binary ./vuln --section .text
```

### Filtering by Register

Find gadgets that operate on a specific register:

```bash
$ ROPgadget --binary ./vuln --only "pop|ret" | grep "rdi"
```

```bash
$ ROPgadget --binary ./vuln --only "pop|ret" | grep "rsi"
```

For x64, the registers you care about most often for function arguments are `rdi`, `rsi`, `rdx`, `rcx`, `r8`, and `r9`, in that order.

### Handling Bad Characters

Exclude gadgets whose addresses contain specific bytes:

```bash
$ ROPgadget --binary ./vuln --badbytes "00|0a|0d"
```

This is critical when your input is read by functions like `gets()`, `scanf()`, or `strcpy()` that treat null bytes, newlines, or carriage returns as terminators.

### Raw Output for Scripting

Strip formatting for easier parsing:

```bash
$ ROPgadget --binary ./vuln --rawArch=x86 --rawMode=64 --only "pop|ret" --nojop --nosys
```

The `--nojop` flag excludes JOP (Jump-Oriented Programming) gadgets, and `--nosys` excludes syscall gadgets, useful for narrowing results when you only want classic ROP gadgets.

### Automatic Chain Generation

ROPgadget can attempt to build an `execve("/bin/sh")` chain automatically:

```bash
$ ROPgadget --binary ./vuln --ropchain
```

> **Note**: Auto-generated chains are a useful starting point but rarely work out of the box. They often require manual adjustment, wrong offsets, missing gadgets substituted with multi-step alternatives, or stack alignment issues. Always verify the chain in a debugger.

## ropper

Where ROPgadget excels at bulk output, ropper is built for targeted, interactive searching. Its semantic search capabilities make it the better tool when you know _what_ a gadget needs to do but not exactly what it looks like.

### Basic Usage

List all gadgets:

```bash
$ ropper -f ./vuln
```

### Interactive Mode

ropper's interactive shell is where it shines:

```bash
$ ropper
(ropper)> file ./vuln
[INFO] Load gadgets from section .text
(ropper)> search pop rdi
```

This keeps the binary loaded in memory, making repeated searches fast.

### Semantic Search

Search for gadgets by what they do, not by exact instruction:

```bash
$ ropper -f ./vuln --search "pop rdi"
```

```bash
$ ropper -f ./vuln --search "mov [rdi], rsi"
```

ropper understands operand patterns: `pop rdi` matches `pop rdi; ret`, `pop rdi; pop rbp; ret`, and other sequences that include the target instruction.

### Register-Specific Searches

Find gadgets that load a value into a specific register:

```bash
$ ropper -f ./vuln --search "pop rdi; ret"
```

Find gadgets for setting up multiple argument registers at once:

```bash
$ ropper -f ./vuln --search "pop rdi; pop rsi"
```

### Searching for Write-What-Where Gadgets

One of ropper's strongest features, find gadgets that write to an arbitrary memory location:

```bash
$ ropper -f ./vuln --search "mov [???], ???"
```

The `???` wildcard matches any register. Common results include `mov [rdi], rsi; ret` or `mov [rax], edx; ret`.

### Filtering by Type

Limit results to specific gadget types:

```bash
$ ropper -f ./vuln --type rop
```

```bash
$ ropper -f ./vuln --type jop
```

```bash
$ ropper -f ./vuln --type sys
```

### Bad Characters

Exclude gadgets with problematic address bytes:

```bash
$ ropper -f ./vuln -b 000a0d
```

Unlike ROPgadget's pipe-delimited format, ropper takes bad bytes as a continuous hex string.

### Setting Architecture

Explicitly set the target architecture:

```bash
$ ropper -f ./vuln -a x86_64
```

```bash
$ ropper -f ./vuln -a x86
```

This matters when working with multi-arch binaries or raw shellcode.

### Gadget Length and Quality

Control the maximum number of instructions in a gadget:

```bash
$ ropper -f ./vuln --inst-count 10
```

The default `--inst-count` is 6. Increase it when you need longer gadgets for complex operations; decrease it when shorter, cleaner gadgets are preferred.

### Searching Across Libraries

Search the binary and its loaded libraries together:

```bash
$ ropper -f ./vuln /lib/x86_64-linux-gnu/libc.so.6 --search "pop rdi; ret"
```

In interactive mode, you can load multiple files and search across all of them:

```bash
(ropper)> file ./vuln
(ropper)> file /lib/x86_64-linux-gnu/libc.so.6
(ropper)> search pop rdi
```

### Automatic Chain Generation

ropper can generate chains for specific operations:

```bash
$ ropper -f ./vuln --chain execve
```

```bash
$ ropper -f ./vuln --chain mprotect
```

This is particularly useful for the `mprotect` chain, which requires setting up three registers and a syscall, something that takes time to build manually.

## GDB-PEDA for Gadget Verification

GDB-PEDA's role in the gadget hunting workflow is verification and runtime analysis. Once you've found candidate gadgets with ropper or ROPgadget, PEDA lets you confirm they behave as expected at runtime.

### Finding Gadgets in PEDA

PEDA has its own gadget search capabilities:

```text
gdb-peda$ ropgadget
```

This searches the binary for common useful gadgets (`pop; ret` sequences, `leave; ret`, `syscall; ret`).

Search for gadgets matching a pattern:

```text
gdb-peda$ ropsearch "pop rdi" 0x400000 0x401000
```

The address range limits the search to specific memory regions, typically the `.text` section.

Search for specific instruction sequences:

```text
gdb-peda$ ropsearch "pop rdi; ret"
```

### Finding JMP/CALL Instructions

```text
gdb-peda$ jmpcall rsp
```

```text
gdb-peda$ jmpcall eax
```

This finds `jmp` and `call` instructions targeting a specific register, useful for `jmp rsp` trampolines or when building JOP chains.

### Verifying Gadget Behavior

Set a breakpoint at a candidate gadget and step through it:

```text
gdb-peda$ b *0x401234
gdb-peda$ r < payload
```

When execution hits the breakpoint, inspect the state:

```text
gdb-peda$ context
gdb-peda$ x/4i $rip
gdb-peda$ i r rdi rsi rdx
```

PEDA's `context` display automatically shows registers, the stack, and disassembly around `$rip`, giving you everything you need to confirm the gadget sets up registers correctly.

### Stepping Through a Chain

To verify multi-gadget chains, set breakpoints at each gadget address and step through the chain one gadget at a time:

```text
gdb-peda$ b *0x401234
gdb-peda$ b *0x401256
gdb-peda$ b *0x401278
gdb-peda$ r < payload
```

At each breakpoint, check that the stack contains the expected values:

```text
gdb-peda$ x/8gx $rsp
```

### Checking Memory Permissions

Before relying on gadgets from specific regions, verify that the memory is executable:

```text
gdb-peda$ vmmap
```

Look for the `x` flag in the permissions column. Gadgets from non-executable regions will cause a segfault.

### Finding Strings at Runtime

```text
gdb-peda$ find "/bin/sh"
```

```text
gdb-peda$ find "/bin/sh" libc
```

This searches memory at runtime, including dynamically loaded libraries, addresses will differ from static analysis if ASLR is enabled, but the offsets remain consistent.

### Examining the GOT

```text
gdb-peda$ got
```

This shows the Global Offset Table, revealing resolved addresses of libc functions at runtime. Useful for calculating libc base address when building ret2libc chains.

## Workflow: Putting It All Together

The end goal of gadget hunting is a complete ROP chain laid out on the stack. Here is what a typical chain looks like once assembled:

```text
Complete ROP Chain Layout:

Low addr
  +-------------------------------+
  |  padding / junk to reach RIP  |
  +===============================+
  | SLOT 1: pop rdi; ret         | <- saved RIP
  +-------------------------------+
  | DATA:   arg for RDI          |
  +-------------------------------+
  | SLOT 2: pop rsi; pop r15; ret|
  +-------------------------------+
  | DATA:   arg for RSI          |
  +-------------------------------+
  | DATA:   junk (absorbed by r15)|
  +-------------------------------+
  | SLOT 3: pop rdx; ret         |
  +-------------------------------+
  | DATA:   arg for RDX          |
  +-------------------------------+
  | SLOT 4: target function/syscall|
  +-------------------------------+
High addr

Each SLOT is a gadget address ending in
"ret", which pops the next SLOT into RIP.
DATA entries are consumed by "pop" inside
the preceding gadget, not executed.
```

A typical gadget hunting session follows this progression:

### 1. Reconnaissance

Start by understanding what you have to work with:

```bash
$ checksec --file=./vuln
$ file ./vuln
$ ldd ./vuln
```

### 2. Broad Discovery

Dump all available gadgets to get a sense of what's in the binary:

```bash
$ ROPgadget --binary ./vuln --only "pop|ret" | wc -l
$ ROPgadget --binary ./vuln --only "pop|ret" | grep -E "rdi|rsi|rdx"
```

### 3. Targeted Search

Switch to ropper for the specific gadgets your chain needs:

```bash
$ ropper -f ./vuln --search "pop rdi; ret"
$ ropper -f ./vuln --search "pop rsi; pop r15; ret"
$ ropper -f ./vuln --search "mov [???], ???"
```

### 4. Handle Missing Gadgets

If the binary lacks a gadget you need, search libc:

```bash
$ ropper -f /lib/x86_64-linux-gnu/libc.so.6 --search "pop rdx; ret"
```

Or look for multi-gadget alternatives. Need `pop rdx; ret` but can't find one? Look for:

```bash
$ ropper -f ./vuln --search "pop rdx"
$ ropper -f ./vuln --search "mov rdx"
$ ropper -f ./vuln --search "xchg"
```

### 5. Check for Bad Characters

Once you have candidate addresses, verify they don't contain bad bytes:

```bash
$ ropper -f ./vuln --search "pop rdi; ret" -b 000a0d
```

### 6. Verify in GDB

Load the binary, set breakpoints at your gadget addresses, and run with a test payload:

```text
gdb-peda$ b *0x401234
gdb-peda$ r < payload.bin
gdb-peda$ context
gdb-peda$ x/4i $rip
```

Confirm registers are set correctly before continuing to the next gadget.

Generate `payload.bin` first (for example, `python3 exploit.py > payload.bin`) before running the command above.

## Quick Reference

### ROPgadget

| Task                  | Command                                             |
| --------------------- | --------------------------------------------------- |
| All gadgets           | `ROPgadget --binary ./vuln`                         |
| Filter by instruction | `ROPgadget --binary ./vuln --only "pop\|ret"`       |
| Find string           | `ROPgadget --binary ./vuln --string "/bin/sh"`      |
| Bad characters        | `ROPgadget --binary ./vuln --badbytes "00\|0a\|0d"` |
| Auto chain            | `ROPgadget --binary ./vuln --ropchain`              |
| Specific section      | `ROPgadget --binary ./vuln --section .text`         |

### ropper

| Task                  | Command                                      |
| --------------------- | -------------------------------------------- |
| All gadgets           | `ropper -f ./vuln`                           |
| Search by instruction | `ropper -f ./vuln --search "pop rdi"`        |
| Write-what-where      | `ropper -f ./vuln --search "mov [???], ???"` |
| Bad characters        | `ropper -f ./vuln -b 000a0d`                 |
| Set max instructions  | `ropper -f ./vuln --inst-count 10`           |
| Auto chain            | `ropper -f ./vuln --chain execve`            |
| Interactive mode      | `ropper` then `file ./vuln`                  |

### GDB-PEDA

| Task              | Command                         |
| ----------------- | ------------------------------- |
| Find gadgets      | `ropgadget`                     |
| Search pattern    | `ropsearch "pop rdi" start end` |
| JMP/CALL search   | `jmpcall rsp`                   |
| Check permissions | `vmmap`                         |
| Find string       | `find "/bin/sh"`                |
| Show GOT          | `got`                           |

## When to Use Which Tool

| Scenario                                | Best Tool | Why                                                 |
| --------------------------------------- | --------- | --------------------------------------------------- |
| Initial survey of available gadgets     | ROPgadget | Fast bulk output, good for `wc -l` and `grep`       |
| Searching for a specific gadget pattern | ropper    | Semantic search with wildcards                      |
| Finding write-what-where primitives     | ropper    | `???` wildcard support                              |
| Filtering bad characters                | Either    | Both support it; ropper syntax is slightly cleaner  |
| Auto-generating chains                  | ropper    | Better chain templates (`execve`, `mprotect`)       |
| Verifying gadget behavior at runtime    | GDB-PEDA  | Step through execution, inspect registers and stack |
| Searching loaded libraries at runtime   | GDB-PEDA  | Access to resolved addresses in memory              |
| Scripting and automation                | ROPgadget | Cleaner raw output for piping                       |
| Working with multiple binaries          | ropper    | Interactive mode loads multiple files               |

## Key Takeaways

- Use ROPgadget for bulk discovery, ropper for targeted searches, and GDB-PEDA to verify a gadget's behavior before committing it to your chain.
- Start broad, narrow progressively, and always verify. The gadget that looks perfect in static analysis might clobber a register you didn't notice.
- When a gadget doesn't exist in the binary, check libc before building a complex multi-gadget workaround. Statically-linked binaries offer the most gadgets; small dynamically-linked binaries offer the fewest.
