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.

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

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.2
File Size Uploaded
ropnroll-0.3.2.tar.gz 87.8 kB Details

Built distribution (wheel)

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

Total release size: 165.9 kB

Release files / ropnroll-0.3.2.tar.gz

Download URL ropnroll-0.3.2.tar.gz
Size 87.8 kB
Tags Source
SHA-256 checksum
How to use checksums
682fd78189ad7d2c4e5255900eced1abe8f5b261f6056de41e7fc8c4233be6a3
BLAKE2b-256 checksum
How to use checksums
5437b8708e2ce738b5d68bbfaeaef8ea4b8eccb2c255e3ef7ae57ac6cce6b075
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.2-py3-none-any.whl

Download URL ropnroll-0.3.2-py3-none-any.whl
Size 78.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4b8d4dff59dbb244d4db00bdbda787039d7307155c76d1c3c43bb6b2d3c60f2b
BLAKE2b-256 checksum
How to use checksums
139a45f232edfd63b3d333da6d7b251f4ac618b2682372d3f0eaac5ecb994013
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

0.3.5

2 release files

0.3.4

2 release files

0.3.3

2 release files

This release

0.3.2 This release

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