Tutorial

ROP Gadget Hunting Toolkit: ropper, ROPgadget, and GDB-PEDA

A practical reference for finding, filtering, and verifying ROP gadgets using ropper, ROPgadget, and GDB-PEDA. Covers common workflows, advanced filtering, and chaining strategies.

7 min read intermediate

Prerequisites

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

Part 8 of 12 in Linux Exploitation Fundamentals

Table of Contents

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.

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

$ pip install ROPgadget

Or from source:

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

ropper

$ pip install ropper

GDB-PEDA

$ 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:

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

Or via pwntools, which bundles its own implementation:

$ 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:

$ 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:

$ ROPgadget --binary ./vuln --only "pop|ret"
$ 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:

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

Search for all readable strings:

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

Finding String Bytes in Memory

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

$ 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:

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

Filtering by Register

Find gadgets that operate on a specific register:

$ ROPgadget --binary ./vuln --only "pop|ret" | grep "rdi"
$ 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:

$ 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:

$ 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:

$ 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:

$ ropper -f ./vuln

Interactive Mode

ropper’s interactive shell is where it shines:

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

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

$ ropper -f ./vuln --search "pop rdi"
$ 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:

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

Find gadgets for setting up multiple argument registers at once:

$ 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:

$ 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:

$ ropper -f ./vuln --type rop
$ ropper -f ./vuln --type jop
$ ropper -f ./vuln --type sys

Bad Characters

Exclude gadgets with problematic address bytes:

$ 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:

$ ropper -f ./vuln -a x86_64
$ 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:

$ 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:

$ 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:

(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:

$ ropper -f ./vuln --chain execve
$ 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:

gdb-peda$ ropgadget

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

Search for gadgets matching a pattern:

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:

gdb-peda$ ropsearch "pop rdi; ret"

Finding JMP/CALL Instructions

gdb-peda$ jmpcall rsp
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:

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

When execution hits the breakpoint, inspect the state:

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:

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:

gdb-peda$ x/8gx $rsp

Checking Memory Permissions

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

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

gdb-peda$ find "/bin/sh"
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

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

A typical gadget hunting session follows this progression:

1. Reconnaissance

Start by understanding what you have to work with:

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

2. Broad Discovery

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

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

Switch to ropper for the specific gadgets your chain needs:

$ 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:

$ 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:

$ 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:

$ 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:

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

TaskCommand
All gadgetsROPgadget --binary ./vuln
Filter by instructionROPgadget --binary ./vuln --only "pop|ret"
Find stringROPgadget --binary ./vuln --string "/bin/sh"
Bad charactersROPgadget --binary ./vuln --badbytes "00|0a|0d"
Auto chainROPgadget --binary ./vuln --ropchain
Specific sectionROPgadget --binary ./vuln --section .text

ropper

TaskCommand
All gadgetsropper -f ./vuln
Search by instructionropper -f ./vuln --search "pop rdi"
Write-what-whereropper -f ./vuln --search "mov [???], ???"
Bad charactersropper -f ./vuln -b 000a0d
Set max instructionsropper -f ./vuln --inst-count 10
Auto chainropper -f ./vuln --chain execve
Interactive moderopper then file ./vuln

GDB-PEDA

TaskCommand
Find gadgetsropgadget
Search patternropsearch "pop rdi" start end
JMP/CALL searchjmpcall rsp
Check permissionsvmmap
Find stringfind "/bin/sh"
Show GOTgot

When to Use Which Tool

ScenarioBest ToolWhy
Initial survey of available gadgetsROPgadgetFast bulk output, good for wc -l and grep
Searching for a specific gadget patternropperSemantic search with wildcards
Finding write-what-where primitivesropper??? wildcard support
Filtering bad charactersEitherBoth support it; ropper syntax is slightly cleaner
Auto-generating chainsropperBetter chain templates (execve, mprotect)
Verifying gadget behavior at runtimeGDB-PEDAStep through execution, inspect registers and stack
Searching loaded libraries at runtimeGDB-PEDAAccess to resolved addresses in memory
Scripting and automationROPgadgetCleaner raw output for piping
Working with multiple binariesropperInteractive mode loads multiple files

Key Takeaways

  • ROPgadget is your broad net — use it for initial discovery, bulk filtering, and scriptable output.
  • ropper is your scalpel — use it for targeted searches, wildcard patterns, and interactive exploration when you know what you need.
  • GDB-PEDA is your verification layer — never commit a gadget to your chain without confirming its behavior at runtime.
  • 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.