Skip to main content
Angrop provides powerful methods to find and analyze ROP gadgets in binaries. This guide covers the different approaches to gadget discovery and caching.

Overview

After initializing a ROP analysis, you need to find gadgets before you can build chains. Angrop offers three main approaches:
  1. Multithreaded search - Fast gadget discovery using multiple processes
  2. Single-threaded search - Useful for performance evaluation and debugging
  3. Load from cache - Reuse previously discovered gadgets

Finding Gadgets with Multiprocessing

The find_gadgets() method searches for gadgets using multiple processes for optimal performance.

Method Signature

rop.find_gadgets(optimize=True, **kwargs)
Parameters:
  • optimize (bool): Whether to run chain_builder.optimize() after finding gadgets. This may take time but makes the chain builder more powerful. Default is True.
  • processes (int): Number of processes to use for multiprocessing. Default is 4.
  • show_progress (bool): Whether to display a progress bar. Default is True.

Example Usage

1

Initialize the ROP analysis

import angr
import angrop

proj = angr.Project("/bin/bash")
rop = proj.analyses.ROP()
2

Find gadgets with default settings

# Find gadgets using 4 processes with optimization
rop.find_gadgets()

print(f"Found {len(rop.rop_gadgets)} ROP gadgets")
print(f"Found {len(rop.syscall_gadgets)} syscall gadgets")
print(f"Found {len(rop.pivot_gadgets)} pivot gadgets")
3

Customize the search parameters

# Use more processes for faster discovery
rop.find_gadgets(processes=16, show_progress=True)

# Skip optimization to save time
rop.find_gadgets(optimize=False)
The optimize parameter runs graph optimization algorithms that discover additional ROP capabilities by chaining gadgets together. While this increases setup time, it significantly improves chain generation success rates.

Finding Gadgets Single-Threaded

The find_gadgets_single_threaded() method is useful for performance evaluation and debugging.

Method Signature

rop.find_gadgets_single_threaded(show_progress=True, optimize=True)
Parameters:
  • show_progress (bool): Whether to display a progress bar. Default is True.
  • optimize (bool): Whether to run optimization. Default is True.

Example Usage

import time
import angr
import angrop

proj = angr.Project("./binary")
rop = proj.analyses.ROP()

# Measure gadget finding performance
start = time.time()
rop.find_gadgets_single_threaded()
print(f"Gadget finding time: {time.time() - start:.2f}s")
Use single-threaded mode when you need reproducible results or want to profile the gadget discovery process.

Saving and Loading Gadgets

For large binaries, gadget discovery can take significant time. Angrop supports caching gadgets to disk.

Saving Gadgets

rop.save_gadgets(path)
Parameters:
  • path (str): File path where gadgets will be stored using pickle format.

Loading Gadgets

rop.load_gadgets(path, optimize=True)
Parameters:
  • path (str): File path from which to load gadgets.
  • optimize (bool): Whether to run optimization after loading. Default is True.

Example: Cache-First Workflow

import os
import angr
import angrop

proj = angr.Project("./vmlinux_sym")
rop = proj.analyses.ROP(kernel_mode=True)

cache_file = "/tmp/gadget_cache"

if os.path.exists(cache_file):
    # Load from cache if available
    rop.load_gadgets(cache_file)
    print("Loaded gadgets from cache")
else:
    # Find and save gadgets
    rop.find_gadgets(processes=16)
    rop.save_gadgets(cache_file)
    print("Saved gadgets to cache")

# Now you can build chains
chain = rop.set_regs(rax=0x1337)
The cache files are specific to the binary and configuration parameters. If you change ROP() initialization parameters (like kernel_mode, fast_mode, etc.), you should regenerate the cache.

Real-World Example: Linux Kernel Gadgets

This example from angrop’s test suite shows a complete workflow for finding gadgets in the Linux kernel:
import os
import time
from multiprocessing import cpu_count
import angr
import angrop

# Load Linux kernel binary
proj = angr.Project("./vmlinux_sym")
rop = proj.analyses.ROP(
    fast_mode=False,
    only_check_near_rets=False,
    max_block_size=12,
    kernel_mode=True
)

cpu_num = cpu_count()
cache = "/tmp/linux_gadget_cache"

# Find or load gadgets
start = time.time()
if os.path.exists(cache):
    rop.load_gadgets(cache, optimize=False)
else:
    rop.find_gadgets(processes=cpu_num, optimize=False)
    rop.save_gadgets(cache)
print(f"Gadget finding time: {time.time() - start}s")

# Run optimization separately
start = time.time()
rop.optimize(processes=cpu_num)
print(f"Graph optimization time: {time.time() - start}s")

# Build chains
chain = rop.func_call("commit_creds", [0xffffffff8368b220])

What Happens During Gadget Finding

When you call find_gadgets() or find_gadgets_single_threaded(), angrop:
  1. Identifies potential gadget locations - Scans for return instructions and nearby code
  2. Analyzes each gadget - Symbolically executes instruction sequences to determine their effects
  3. Categorizes gadgets - Sorts them into:
    • rop_gadgets - General-purpose ROP gadgets (pop, mov, arithmetic, etc.)
    • syscall_gadgets - Gadgets ending in syscall/int 0x80
    • pivot_gadgets - Stack pivoting gadgets
  4. Filters by badbytes - Removes gadgets whose addresses contain badbytes (if configured)
  5. Optimizes chains (if enabled) - Discovers complex multi-gadget capabilities

Analyzing Found Gadgets

After finding gadgets, you can inspect what was discovered:
# View gadget counts
print(f"ROP gadgets: {len(rop.rop_gadgets)}")
print(f"Syscall gadgets: {len(rop.syscall_gadgets)}")
print(f"Pivot gadgets: {len(rop.pivot_gadgets)}")

# Examine individual gadgets
for gadget in rop.rop_gadgets[:5]:
    print(f"Address: {hex(gadget.addr)}")
    print(f"Popped registers: {gadget.popped_regs}")
    print(f"Changed registers: {gadget.changed_regs}")
    print(f"Stack change: {gadget.stack_change}")
    print()

Performance Tips

For large binaries:
  • Use fast_mode=True to skip complex gadgets
  • Increase processes to match your CPU core count
  • Set optimize=False during initial discovery, then run rop.optimize() separately
  • Always cache gadgets with save_gadgets() for reuse
For kernel binaries:
  • Set kernel_mode=True
  • Use only_check_near_rets=False to find more gadgets
  • Increase max_block_size if needed (default varies by architecture)

Build docs developers (and LLMs) love