Xgadget
Fast, parallel, cross-variant ROP/JOP gadget search for x86/x64 binaries.
Install / Use
/learn @entropic-security/XgadgetREADME
xgadget
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
WRITEandEXECUTEmemory 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 endingretinstruction pops the next gadget's address into the CPU's instruction pointer. The result? Turing-complete control over a victim process.
- 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/WRITEmemory 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 ajmpback to the dispatcher (instead of aret).
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
- Optionally filter to JOP "dispatcher" gadgets with flag
-
Cross-variant: Finds gadgets that work across multiple variants of a binary (e.g. anti-diversification for different program or compiler versions). Two strategies:
- Full-match - Same instruction sequence, same program counter: gadget fully re-usable. Example:
- Gadget:
pop rdi; ret; - Address (in all binaries):
0xc748d
- Gadget:
- 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
- Gadget:
- 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/sudofor reliable ways to controlrdi:
xgadget /usr/bin/sudo --reg-only --reg-overwrite rdi
- Example: Search for ROP gadgets that control the value of
rdi, never readrsiorrdx, and occur at addresses that don't contain bytes0x32or0x0d:
xgadget /usr/bin/sudo --rop --reg-overwrite rdi --reg-no-read rsi rdx --bad-bytes 0x32 0x0d
- Example: Search
/usr/bin/sudofor "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
sudoandlighttpdhave 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