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 ROPgadgetOr from source:
$ git clone https://github.com/JonathanSalwan/ROPgadget.git
$ cd ROPgadget && pip install .ropper
$ pip install ropperGDB-PEDA
$ git clone https://github.com/longld/peda.git ~/peda
$ echo "source ~/peda/peda.py" >> ~/.gdbinitNote: If you use multiple GDB extensions (pwndbg, GEF), manage them with a wrapper script or separate
.gdbinitfiles 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 # ArchOr via pwntools, which bundles its own implementation:
$ pip install pwntools
$ pwn checksec ./vulnNote: The standalone
checksecand pwntoolspwn checksecproduce 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 ./vulnThis 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 .textFiltering 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 --nosysThe --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 --ropchainNote: 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 ./vulnInteractive Mode
ropper’s interactive shell is where it shines:
$ ropper
(ropper)> file ./vuln
[INFO] Load gadgets from section .text
(ropper)> search pop rdiThis keeps the binary loaded in memory, making repeated searches fast.
Semantic Search
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 sysBad Characters
Exclude gadgets with problematic address bytes:
$ ropper -f ./vuln -b 000a0dUnlike 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 x86This 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 10The 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 rdiAutomatic Chain Generation
ropper can generate chains for specific operations:
$ ropper -f ./vuln --chain execve$ ropper -f ./vuln --chain mprotectThis 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$ ropgadgetThis 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 0x401000The 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 rspgdb-peda$ jmpcall eaxThis 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 < payloadWhen execution hits the breakpoint, inspect the state:
gdb-peda$ context
gdb-peda$ x/4i $rip
gdb-peda$ i r rdi rsi rdxPEDA’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 < payloadAt each breakpoint, check that the stack contains the expected values:
gdb-peda$ x/8gx $rspChecking Memory Permissions
Before relying on gadgets from specific regions, verify that the memory is executable:
gdb-peda$ vmmapLook 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" libcThis 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$ gotThis 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 ./vuln2. 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"3. Targeted Search
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 000a0d6. 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 $ripConfirm 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
- 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.