SkillAgentSearch skills...

Xgadget

Fast, parallel, cross-variant ROP/JOP gadget search for x86/x64 binaries.

Install / Use

/learn @entropic-security/Xgadget
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

xgadget

crates.io docs.rs GitHub Actions License: MIT

Fast, parallel, cross-{patch,compiler}-variant ROP/JOP gadget search for x86 (32-bit) and x64 (64-bit) binaries. Uses the iced-x86 disassembler library.

This crate can be used as a CLI binary (Windows/Linux/MacOS) or a library (7 well-known dependencies, all Rust).

Quickstart

Install the CLI tool and show its help menu:

cargo install xgadget --features cli-bin    # Build on host (pre-req: https://www.rust-lang.org/tools/install)
xgadget --help                              # List available command line options

How do ROP and JOP code reuse attacks work?

  • Return Oriented Programming (ROP) introduced code-reuse attacks, after hardware mitigations (aka NX, DEP) made code-injection less probable (no simultaneous WRITE and EXECUTE memory permissions). An attacker with stack control chains together short, existing sequences of assembly (aka "gadgets") - should a leak enable computing gadget addresses in the face of ASLR. When contiguous ROP gadget addresses are written to a corrupted stack, each gadget's ending ret instruction pops the next gadget's address into the CPU's instruction pointer. The result? Turing-complete control over a victim process.
<p style="text-align: center;" align="center"> <img src="https://raw.githubusercontent.com/tnballo/high-assurance-rust/main/src/chp4/exploit_rop_model.svg" width="60%" alt="rop model"> <figure style="text-align:center;"> </figure> </p> <p align="center"> <i><b>ROP</b> Attack Model (recreated from:<a href="https://www.comp.nus.edu.sg/~liangzk/papers/asiaccs11.pdf"> Bletsch et. al.</a>)</i> </p>
  • Jump Oriented Programming (JOP) is a newer code reuse method which, unlike ROP, doesn't rely on stack control. The attack bypasses hardware-assisted shadow-stack implementations (e.g. Intel CET's shadow stack), and is limited but not prevented by prototype-insensitive indirect target checks (e.g. Intel CET's IBT). JOP allows storing a table of gadget addresses in any READ/WRITE memory location. Instead of piggy-backing on call-return semantics to execute a gadget list, a "dispatch" gadget (e.g. add rax, 8; jmp [rax]) controls table indexing. Chaining happens if each gadget ends with a jmp back to the dispatcher (instead of a ret).
<p style="text-align: center;" align="center"> <img src="https://raw.githubusercontent.com/tnballo/high-assurance-rust/main/src/chp4/exploit_jop_model.svg" width="100%" alt="jop model"> <figure style="text-align:center;"> </figure> </p> <p align="center"> <i><b>JOP</b> Attack Model (recreated from:<a href="https://www.comp.nus.edu.sg/~liangzk/papers/asiaccs11.pdf"> Bletsch et. al.</a>)</i> </p>

About

xgadget is a tool for Return-Oriented Programming (ROP) and Jump-Oriented Programming (JOP) exploit development. It's a fast, multi-threaded alternative to awesome tools like ROPGadget, Ropper, and rp.

The goal is supporting practical usage while simultaneously exploring unique and experimental features. To the best of our knowledge, xgadget is the first gadget search tool to be:

  • Fast-register-sensitive: Filters gadgets by register usage behavior, not just matches for a given regex, without SMT solving (more powerful, but often impractical).

    • --reg-overwrite [<OPT_REG(S)>...] - control any reg (no args) or specific regs (args)

    • --reg-mem-write [<OPT_REG(S)>...] - write mem indexed via any reg (no args) or specific regs (args)

    • --reg-no-write [<OPT_REG(S)>...] - don't write any reg (no args) or specific regs (args)

    • --reg-read [<OPT_REG(S)>...] - read any regs (no args) or specific regs (args)

    • --reg-mem-read [<OPT_REG(S)>...] - read mem indexed via any reg (no args) or specific regs (args)

    • --reg-no-read [<OPT_REG(S)>...] - don't read any regs (no args) or specific regs (args)

  • JOP-efficient: JOP search uses instruction semantics - not hardcoded regex for individual encodings.

    • Optionally filter to JOP "dispatcher" gadgets with flag --dispatcher
  • Cross-variant: Finds gadgets that work across multiple variants of a binary (e.g. anti-diversification for different program or compiler versions). Two strategies:

  1. Full-match - Same instruction sequence, same program counter: gadget fully re-usable. Example:
    • Gadget: pop rdi; ret;
    • Address (in all binaries): 0xc748d
<p style="text-align: center;" align="center"> <img src="https://raw.githubusercontent.com/entropic-security/xgadget/main/img/xgadget_all_match.svg" width="70%" alt="full match"> <figure style="text-align:center;"> </figure> </p> <p align="center"> <i>Cross-variant <b>Full Match</b></i> </p>
  1. Partial-match - Same instruction sequence, different program counter: gadget logic portable. Example:
    • Gadget: pop rdi; ret;
    • Address in bin_v1.1: 0xc748d
    • Address in bin_v1.2: 0xc9106
<p style="text-align: center;" align="center"> <img src="https://raw.githubusercontent.com/entropic-security/xgadget/main/img/xgadget_addr_match.svg" width="70%" alt="partial match"> <figure style="text-align:center;"> </figure> </p> <p align="center"> <i>Cross-variant <b>Partial Match</b></i> </p>
  • This is entirely optional, you're free to run this tool on a single binary.

Other features include:

  • Supports ELF32, ELF64, PE32, PE32+, Mach-O, and raw files
  • Parallel across available cores, whether searching a single binary or multiple variants
  • Currently 8086/x86/x64 only (uses a speed-optimized, arch-specific disassembler)

CLI Examples

Run xgadget --help to enumerate available options.

  • Example: Search /usr/bin/sudo for reliable ways to control rdi:
xgadget /usr/bin/sudo --reg-only --reg-overwrite rdi
  • Example: Search for ROP gadgets that control the value of rdi, never read rsi or rdx, and occur at addresses that don't contain bytes 0x32 or 0x0d:
xgadget /usr/bin/sudo --rop --reg-overwrite rdi --reg-no-read rsi rdx --bad-bytes 0x32 0x0d
  • Example: Search /usr/bin/sudo for "pop, pop, {jmp,call}" gadgets up to 10 instructions long, print results using AT&T syntax:
xgadget /usr/bin/sudo --jop --reg-pop --att --max-len 10
  • Example: Same as above, except using a regex filter to match "pop, pop, {jmp,call}" instruction strings (slower/less-accurate here, but regex enables flexible search in general):
xgadget /usr/bin/sudo --regex-filter "^(?:pop)(?:.*(?:pop))*.*(?:call|jmp)" --att --max-len 10
  • Example: Examine the exploit mitigations binaries sudo and lighttpd have been compiled with:
xgadget /usr/bin/sudo /usr/sbin/lighttpd --check-sec
  • Example: List imported and internal symbols for lighttpd:
xgadget /usr/sbin/lighttpd --symbols

API Usage

Find gadgets:

use xgadget::{Binary, SearchConfig};

let max_gadget_len = 5;

// Search single binary
let bin = &[Binary::from_path("/path/to/bin_v1").unwrap()];
let gadgets =
    xgadget::find_gadgets(bin, max_gadget_len, SearchConfig::default()).unwrap();
let stack_pivot_gadgets = xgadget::filter_stack_pivot(gadgets);

// Search for cross-variant gadgets, including partial matches
let search_config = SearchConfig::default() | SearchConfig::PART;
let bins = &[
    Binary::from_path("/path/to/bin_v1").unwrap(),
    Binary::from_path("/path/to/bin_v2").unwrap(),
];
let cross_gadgets =
    xgadget::find_gadgets(bins, max_gadget_len, search_config).unwrap();
let cross_reg_pop_gadgets = xgadget::filter_reg_pop_only(cross_gadgets);

Custom filters can be created using the GadgetAnalysis object and/or functions from the semantics module. How the above filter_stack_pivot function is implemented:

use rayon::prelude::*;
use iced_x86;
use xgadget::{Gadget, GadgetAnalysis};

/// Parallel filter to gadgets that write the stack pointer
pub fn filter_stack_pivot<'a, P>(gadgets: P) -> P
where
    P: IntoParallelIterator<Item = Gadget<'a>> + FromParallelIterator<Gadget<'a>>,
{
    gadgets
        .into_par_iter()
        .filter(|g| {
            let regs_overwritten = g.analysis().regs_overwritten(true);
            if regs_overwritten.contains(&iced_x86::Register::RSP)
                || regs_overwritten.contains(&iced_x86::Register::ESP)
                || regs_overwritten.contains(&iced_x86::Register::SP)
            {
                return true;
            }
            false
        })
        .collect()
}
<!--- TODO: add back later ### CLI Build and Install (Recommended) Build a dynamically-linked binary from source and install it locally: ```bash cargo install xgadget --features cli-bin # Build on host (pre-req: https://www.rust-lang.org/tools/install) ``` ### CLI Binary Releases for Linux Commits to this repo's `main` branch automatically run integration tests and build a statically-linked binary for 64-bit Linux. You can [download it here](https://github.com/entropic-security/xgadget/releases) to try out the CLI immediately, instead of building from source. Static binaries for Windows may also be supported in the future. Unfortunately the statically-linked binary is several times slower on an i7-9700K, likely due to the built-in memory allo
View on GitHub
GitHub Stars124
CategoryDevelopment
Updated1mo ago
Forks6

Languages

Rust

Security Score

100/100

Audited on Feb 24, 2026

No findings