Skip to main content

ropnroll

PyPI Python versions CI License: GPL v3

ropnroll is a command-line tool and Python library for finding return-oriented programming (ROP) and jump-oriented programming (JOP) gadgets in Windows PE binaries (EXE/DLL, x86/x86-64/ARM64). It supports instruction-pattern and semantic searches, builds function-call chains against the MS x64/cdecl/ stdcall calling conventions, and can check generated chains with Unicorn emulation.

Use it to inspect PE mitigations (DEP, ASLR, CFG, XFG, CET, SafeSEH), find gadgets with specific register effects, and assemble chains for Windows exploit-development research and CTF challenges.

ropnroll reporting mitigations, then finding gadgets by instruction pattern and by stack-pivot effect

Why ropnroll

Most gadget tools infer a gadget's effect from a hand-written instruction model. ropnroll instead measures it: every gadget runs for real on an emulated CPU (Unicorn) with several input vectors, and a closed-form relation (constant, copy, affine, bitwise) is fit to the outputs. If a relation fits every trial, it's exact, not a guess -- and it's correct by construction for every architecture Unicorn supports, including quirks a hand-rolled model would miss.

Chain synthesis uses a best-first (A*) search over candidate gadgets rather than a fixed-width traversal, so it isn't limited to only the first few shortest candidates at each step -- within its depth and work bounds, it finds a minimum-gadget chain instead of giving up on one that a narrower search would miss. Repeated analysis of the same binary (the normal workflow while building an exploit) is backed by a persistent on-disk cache, so the second and later search/call invocation against the same target is fast; see --no-cache and ROPNROLL_CACHE_DIR below if you need to bypass or relocate it.

Installation

Requires Python 3.10 or newer. Install from PyPI in a virtual environment:

python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install ropnroll
ropnroll --help

Capstone, LIEF, Unicorn, and Rich are installed automatically. ropnroll itself is a pure analysis tool (it never executes target code on the host), so it also runs fine from Linux/macOS if you're cross-analyzing a PE -- but Windows is the only platform it targets and tests against.

If you use uv, you can run the CLI without a persistent installation:

uvx ropnroll --help

Quick start

Replace ./target with the path to a binary you want to inspect.

# Report a PE's mitigations: DEP, ASLR, stack canary, CFG/XFG, CET, SafeSEH.
ropnroll security ./target.exe

# List up to 20 gadgets.
ropnroll scan ./target.exe --limit 20

# Find x86-64 gadgets by instruction text.
ropnroll scan ./target.exe --regex 'pop rcx'

# Find x86-64 gadgets by their effect on registers.
ropnroll search ./target.exe --query 'rcx=rax+8'

scan prints gadget addresses and disassembly. search uses emulation to infer register effects; the query above asks for a gadget that sets rdi to rax + 8. Matches depend on the instructions available in your binary.

Run ropnroll <command> --help for command options.

Commands

Command Purpose
security Report a PE's mitigations.
scan List gadgets, optionally filtered by an instruction regex.
search Search for register or memory effects using semantic queries.
pivot Find stack-pivot gadgets.
jop Find JOP dispatcher gadgets.
call Build a chain that calls a function by symbol or address.
pattern Generate a cyclic pattern, or look up a crash offset within one.

Build and export a chain

For a binary that contains a suitable exported symbol and gadgets:

ropnroll call ./target.exe --target ExitProcess --args 0 --verify --emit json --out chain.json

ropnroll building a call chain -- picking a pop-rcx gadget for the argument, laying out the stack, emitting a pwntools payload, then verifying it under Unicorn emulation

--target accepts a symbol name or numeric address. --args accepts comma-separated integers. --verify prints an emulation report; inspect that report before using the output. Export formats are json, raw, c, and pwntools. Use --out to save a payload separately from console diagnostics.

You can pool gadgets from multiple binaries:

ropnroll search ./target.exe ./kernel32.dll --query 'rcx=rax+8'

Crash-offset triage

Before hunting for gadgets, find out how many bytes of your overflow precede the data you control:

ropnroll pattern create 400 --out pattern.bin   # send this as your crash input
ropnroll pattern offset 0x6a413169              # whatever a debugger showed in EIP/RIP

offset treats its argument as the value a register held (packed little-endian, matching real memory layout); pass --text to instead look up a literal pattern substring.

Arguments you'll resolve yourself

Some argument values ropnroll simply has no way to compute -- most commonly a pointer relative to the payload's own stack position, since ropnroll only reasons about the static binaries it scanned, never a live process's stack. Pass 0xfeedfacecafebabe for that argument instead of a real value:

ropnroll call ./target.exe --target VirtualProtect \
  --args 0xfeedfacecafebabe,0x1000,0x40,0xfeedfacecafebabe \
  --emit json --out chain.json

Every --emit format flags a word carrying that value instead of treating it as a real literal: json sets "placeholder": true and nulls value, pwntools annotates the line with PLACEHOLDER -- resolve outside ropnroll, and raw/c refuse to export until it's gone. Patch it into the exported chain yourself once you know the real value.

Avoiding bad characters

If the payload reaches the target through something byte-sensitive (a strcpy-style copy, a URL-decoder, ...), pass --bad-chars (any command that builds or searches gadgets accepts it) to exclude gadgets whose address would introduce one of those bytes, and to flag any call target or literal argument that still contains one:

ropnroll call ./target.exe --target ExitProcess --args 0 --bad-chars 000a0d

This is distinct from --bad-bytes, which filters a gadget's own instruction encoding at its fixed location in the binary -- a narrower, scanner-level filter, not what a delivered payload actually contains.

CET

If the target is CET shadow-stack compatible (/CETCOMPAT), call warns that a return-based chain will fault on its first ret and suggests jop instead, which CET's shadow stack does not check.

call, pivot, and jop also accept multiple paths. Addresses come from the loaded images and reflect each file's own preferred base -- pass --base path=0xaddr (repeatable) to override a specific binary's base with a leaked runtime address instead, e.g. for an ASLR-relocated DLL:

ropnroll call ./target.exe ./kernel32.dll --base kernel32.dll=0x7ffb2a3c0000 \
  --target VirtualProtect --args 0x140001000,0x1000,0x40,0x140002000

Loading kernel32.dll alongside the target is what makes --target VirtualProtect resolve at all: the CLI only resolves symbol names against a binary's own exports, not another binary's imports, so a function the target merely calls (rather than defines) must come from a binary that actually exports it. Pointer arguments (like VirtualProtect's output parameter above) must refer to valid memory in the intended target; nothing here allocates scratch space for you.

For x86-64, call accepts --bytes-before-chain N (bytes of payload preceding the chain in your final buffer) to automatically correct stack alignment for the call instruction, matching what a real call would have left behind -- entering a function at the wrong 16-byte parity is a common, easy-to-miss way a chain crashes inside the callee's own SSE instructions.

Caching

Semantic effects (the expensive part -- several Unicorn runs per gadget) are cached on disk per binary, keyed by its content hash, so repeated commands against the same target reuse prior analysis instead of redoing it. Pass --no-cache to any command to bypass the cache for that run. The cache lives under %LOCALAPPDATA%\ropnroll by default; set ROPNROLL_CACHE_DIR to relocate it.

Supported targets and limitations

Target Scope
Windows x86-64 PE (EXE/DLL) Primary target for scanning, semantic analysis, chain building, and verification.
Windows x86 PE (32-bit) Loading, mitigation reporting (incl. SafeSEH), and scanning; cdecl/stdcall calling-convention support is implemented.
Windows ARM64 PE Scanner and semantic-engine implementation; not covered by real-binary architecture tests.
ELF, Mach-O, other architectures Unsupported -- ropnroll only reads PE.
  • Semantic effects are inferred from a finite set of emulation trials, not formally proved for every possible input.
  • Chain search (a best-first/A* search over candidate gadgets) has depth and work limits; within those bounds it finds a minimum-gadget chain if one exists, but a gadget-poor pool can still exhaust the budget without one.
  • Chain verification uses the first supplied image; it does not fully validate chains spanning multiple images or guarantee success in a live process.
  • Raw gadget-scan speed still trails ROPgadget (not ropper, closely) on large real-world binaries, though a scanner rewrite closed most of a former 3-8x gap; see benchmarks/ for measured numbers and honest notes on where the remaining gap is. Scanning parallelizes across CPU cores by default (--jobs to control worker count; --jobs 1 to disable).

Development

git clone https://github.com/jordanallred/ropnroll.git
cd ropnroll
python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install -e '.[dev]'
python -m pytest -q

Several tests scan C:\Windows\System32\ntdll.dll as a realistic, large PE fixture; they skip automatically off Windows. The repository also includes small, purpose-built PE fixtures under tests/fixtures/pe/ for loader and scanner tests.

Help and contributions

Report bugs or suggest improvements in GitHub Issues. For bug reports, include the command, full error output, Python and ropnroll versions, and the target's architecture and file format. Include a minimal reproducer when possible.

Pull requests are welcome; see CONTRIBUTING.md for the development setup and PR expectations. To report a security vulnerability in ropnroll itself, see SECURITY.md instead of opening a public issue.

See CHANGELOG.md for release history.

License

GPL-3.0-or-later.

Metadata

Release files for ropnroll 0.3.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ropnroll 0.3.5
File Size Uploaded
ropnroll-0.3.5.tar.gz 91.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ropnroll 0.3.5
File Interpreter ABI Platform
ropnroll-0.3.5-py3-none-any.whl Python 3 none any Details

Total release size: 172.6 kB

Release files / ropnroll-0.3.5.tar.gz

Download URL ropnroll-0.3.5.tar.gz
Size 91.6 kB
Tags Source
SHA-256 checksum
How to use checksums
31a43c7c412d6a2018959c748e4234f016d45e79d3e976551bb325e31cf645b5
BLAKE2b-256 checksum
How to use checksums
494df8413bc419469733cd8df87ebb2e6e00dcd720b2c184e539b68040f5b31b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / ropnroll-0.3.5-py3-none-any.whl

Download URL ropnroll-0.3.5-py3-none-any.whl
Size 81.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
52db39391b52c8f6bf70536fad2ced396b70d0a08b0515363a10f212568e081d
BLAKE2b-256 checksum
How to use checksums
1cecfc3aeb32b76f81f0fdefdaa9010f634b459abef6d96f12e1a32846c0be2a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.5 This release

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page