Crates.io | xgadget |
lib.rs | xgadget |
version | 0.11.1 |
source | src |
created_at | 2020-06-21 17:27:48.835515 |
updated_at | 2023-11-24 15:17:56.246258 |
description | Fast, parallel, cross-variant ROP/JOP gadget search for x86/x64 binaries. |
homepage | https://github.com/entropic-security/xgadget |
repository | https://github.com/entropic-security/xgadget |
max_upload_size | |
id | 256375 |
size | 336,397 |
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).
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
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.
ROP Attack Model (recreated from: Bletsch et. al.)
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
).
JOP Attack Model (recreated from: Bletsch et. al.)
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.
--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:
pop rdi; ret;
0xc748d
Cross-variant Full Match
pop rdi; ret;
bin_v1.1
: 0xc748d
bin_v1.2
: 0xc9106
Cross-variant Partial Match
Other features include:
Run xgadget --help
to enumerate available options.
/usr/bin/sudo
for reliable ways to control rdi
:xgadget /usr/bin/sudo --reg-only --reg-overwrite rdi
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
/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
xgadget /usr/bin/sudo --regex-filter "^(?:pop)(?:.*(?:pop))*.*(?:call|jmp)" --att --max-len 10
sudo
and lighttpd
have been compiled with:xgadget /usr/bin/sudo /usr/sbin/lighttpd --check-sec
lighttpd
:xgadget /usr/sbin/lighttpd --symbols
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()
}
Tools that attempt to automate ROP/JOP chain generation require heavyweight analysis - typically symbolic execution of an intermediate representation.
This works well for small binaries and CTF problems, but tends to be error-prone and difficult to scale for large, real-world programs.
At present, xgadget
has a different goal: enable an expert user to manually craft stable exploits by providing fast, accurate gadget discovery.
To build a Docker container and connect to it:
user@host$ git clone git@github.com:entropic-security/xgadget.git
user@host$ cd xgadget
user@host$ docker build -t xgadget_bench_container .
user@host$ docker run -it xgadget_bench_container
root@container:/xgadget#
The final build step runs ./benches/bench_setup_ubuntu.sh
.
This script downloads and builds 10 consecutive Linux kernels (versions 5.0.1
to 5.0.10
- with x86_64_defconfig
).
Grab a coffee, it can take a while.
Once it's done, run cargo bench
to search all 10 kernels for common gadgets (among other benchmarks):
root@container:/xgadget# cargo bench
On an i7-9700K (8C/8T, 3.6GHz base, 4.9 GHz max) machine with gcc
version 8.4.0: the average runtime, to process all ten 54MB kernels simultaneously with a max gadget length of 5 instructions and full-match search for all gadget types (ROP, JOP, and syscall gadgets), is only 6.3 seconds! Including partial matches as well takes just 7.9 seconds.
The --fess
flag uses cross-variant gadget matching as a metric of binary similarity.
It's an experiment in anti-diversification for exploitation.
To view similarity scores for kernel versions 5.0.1
, 5.0.5
, and 5.0.10
within the container:
root@container# cd ./benches/kernels/
root@container# xgadget vmlinux-5.0.1 vmlinux-5.0.5 vmlinux-5.0.10 --fess
TARGET 0 - [ name: 'vmlinux-5.0.1' | fmt-arch: ELF-X64 | entry: 0x00000001000000 | exec bytes/segments: 21,065,728/2 ]
TARGET 1 - [ name: 'vmlinux-5.0.5' | fmt-arch: ELF-X64 | entry: 0x00000001000000 | exec bytes/segments: 21,069,824/2 ]
TARGET 2 - [ name: 'vmlinux-5.0.10' | fmt-arch: ELF-X64 | entry: 0x00000001000000 | exec bytes/segments: 21,069,824/2 ]
┌─────────────┬──────────────────────┬──────────────────────┬───────────────────────┐
│ Gadget Type │ vmlinux-5.0.1 (base) │ vmlinux-5.0.5 (diff) │ vmlinux-5.0.10 (diff) │
├─────────────┼──────────────────────┼──────────────────────┼───────────────────────┤
│ ROP (full) │ 108,380 │ 7,351 (6.78%) │ 556 (0.51%) │
├─────────────┼──────────────────────┼──────────────────────┼───────────────────────┤
│ ROP (part) │ - │ 80,783 (74.54%) │ 78,053 (72.02%) │
├─────────────┼──────────────────────┼──────────────────────┼───────────────────────┤
│ JOP (full) │ 79,685 │ 1,007 (1.26%) │ 276 (0.35%) │
├─────────────┼──────────────────────┼──────────────────────┼───────────────────────┤
│ JOP (part) │ - │ 16,458 (20.65%) │ 12,461 (15.64%) │
├─────────────┼──────────────────────┼──────────────────────┼───────────────────────┤
│ SYS (full) │ 8,276 │ 422 (5.10%) │ 119 (1.44%) │
├─────────────┼──────────────────────┼──────────────────────┼───────────────────────┤
│ SYS (part) │ - │ 4,317 (52.16%) │ 3,864 (46.69%) │
└─────────────┴──────────────────────┴──────────────────────┴───────────────────────┘
Note these totals exclude low-quality gadgets (use --all
flag to include).
In the output table, we see that up to 72.02% of individual ROP gadgets, and 15.64% of JOP gadgets, are portable across all three versions (counting partial matches).
This project started as an optimized solution to Chapter 8, exercise 3 of "Practical Binary Analysis" by Dennis Andreisse (affiliate link), and builds on the design outlined therein.
Free book about software assurance: https://highassurance.rs/
Licensed under the MIT license. Contributions are welcome!