xgadget 0.5.1

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

![crates.io](https://img.shields.io/crates/v/xgadget.svg)
![GitHub Actions](https://github.com/entropic-security/xgadget/workflows/test/badge.svg)

Fast, parallel, cross-variant ROP/JOP gadget search for x86 (32-bit) and x64 (64-bit) binaries.
Uses the [iced-x86 disassembler library](https://github.com/0xd4d/iced).

**Current state:** decent test coverage, but still in beta. Issues/PRs welcome :)

### About

To the best of my knowledge, `xgadget` is the first gadget search tool to have these features:

* JOP search uses instruction semantics - not hardcoded regex for individual encodings
   * Optionally filter to JOP "dispatcher" gadgets with flag `--dispatcher`
* Finds gadgets that work across multiple variants of a binary (e.g. different program or compiler versions)
   * **Full-match** - Same instruction sequence, same program counter: gadget fully re-usable.
       * E.g. `pop rsp; add [rax-0x77], cl; ret ------------------------------------- [ 0xc748d ]`
   * **Partial-match** - Same instruction sequence, different program counter: gadget logic portable.
       * E.g. `pop rsp; add [rax-0x77], cl; ret; --- [ 'bin_v1.1': 0xc748d, 'bin_v1.2': 0xc9106 ]`
   * This is entirely optional, you're free to run this tool on a single binary.
* The stack pointer is explicitly colored in terminal output, for workflow convenience.

Other features include:

* Both library API and CLI tool
* Supports ELF32, ELF64, PE32, PE32+ \[1\], and raw files
* Parallel across available cores \[2\], whether searching a single binary or multiple variants
* CI/CD for automated integration test and binary releases (Linux, 64-bit) \[3\]
* Statistical benchmark harness for performance tuning \[4\]
* 8086/x86/x64 only, uses a speed-optimized disassembly backend \[5\]

### API Usage

Find gadgets:

```rust
use xgadget;

let max_gadget_len = 5;

// Search single binary
let search_config = xgadget::SearchConfig::DEFAULT;
let bin_1 = xgadget::Binary::from_path_str("/path/to/bin_v1").unwrap();
let bins = vec![bin_1];
let gadgets = xgadget::find_gadgets(&bins, max_gadget_len, search_config).unwrap();
let stack_pivot_gadgets = xgadget::filter_stack_pivot(&gadgets);

// Search for cross-variant gadgets, including partial matches
let search_config = xgadget::SearchConfig::DEFAULT | xgadget::SearchConfig::PART;
let bin_1 = xgadget::Binary::from_path_str("/path/to/bin_v1").unwrap();
let bin_2 = xgadget::Binary::from_path_str("/path/to/bin_v2").unwrap();
let bins = vec![bin_1, bin_2];
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`](crate::gadget::GadgetAnalysis) object and/or functions from the [`semantics`](crate::semantics) module.
How the above [`filter_stack_pivot`](crate::filters::filter_stack_pivot) function is implemented:

```rust
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>(gadgets: &[Gadget<'a>]) -> Vec<Gadget<'a>> {
   gadgets
       .par_iter()
       .filter(|g| {
           let regs_overwritten = GadgetAnalysis::new(&g).regs_overwritten();
           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
       })
       .cloned()
       .collect()
}
```

### CLI Usage

Run `xgadget --help`:

```
xgadget v0.5.0

About:    Fast, parallel, cross-variant ROP/JOP gadget search for x86/x64 binaries.
Cores:    8 logical, 8 physical

USAGE:
   xgadget [FLAGS] [OPTIONS] <FILE(S)>...

FLAGS:
   -t, --att              Display gadgets using AT&T syntax [default: Intel syntax]
   -c, --check-sec        Run checksec on the 1+ binaries instead of gadget search
   -d, --dispatcher       Filter to potential JOP 'dispatcher' gadgets [default: all]
   -e, --extended-fmt     Print in terminal-wide format [default: only used for partial match search]
   -h, --help             Prints help information
       --inc-call         Include gadgets containing a call [default: don't include]
       --inc-imm16        Include '{ret, ret far} imm16' (e.g. add to stack ptr) [default: don't include]
   -j, --jop              Search for JOP gadgets only [default: ROP, JOP, and SYSCALL]
   -n, --no-color         Don't color output [default: color output]
       --param-ctrl       Filter to gadgets that control function parameters [default: all]
   -m, --partial-match    Include cross-variant partial matches [default: full matches only]
       --reg-pop          Filter to 'pop {reg} * 1+, {ret or ctrl-ed jmp/call}' gadgets [default: all]
   -r, --rop              Search for ROP gadgets only [default: ROP, JOP, and SYSCALL]
   -p, --stack-pivot      Filter to gadgets that write the stack ptr [default: all]
   -s, --sys              Search for SYSCALL gadgets only [default: ROP, JOP, and SYSCALL]
   -V, --version          Prints version information

OPTIONS:
   -a, --arch <ARCH>               For raw (no header) files: specify arch ('x8086', 'x86', or 'x64') [default: x64]
   -b, --bad-bytes <BYTE(S)>...    Filter to gadgets whose addrs don't contain given bytes [default: all]
   -l, --max-len <LEN>             Gadgets up to LEN instrs long. If 0: all gadgets, any length [default: 5]
       --no-deref <OPT_REG>        Filter to gadgets that don't deref any regs or a specific reg [default: all]
       --reg-ctrl <OPT_REG>        Filter to gadgets that control any reg or a specific reg [default: all]
   -f, --regex-filter <EXPR>       Filter to gadgets matching a regular expression

ARGS:
   <FILE(S)>...    1+ binaries to gadget search. If > 1: gadgets common to all
```

### 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 `master` 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 allocator for target `x86_64-unknown-linux-musl`.
So building a dynamically-linked binary from source with the above `cargo install` command is *highly* recommended for performance (links against your system's allocator).

### Why No Chain Generation?

Tools that attempt to automate ROP chain generation require heavyweight analysis - typically symbolic execution of an intermediate representation.
While this works well for small binaries and CTF problems, it tends to be slow 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.

### ~~Yeah, but can it do 10 OS kernels under 10 seconds?!~~ Repeatable Benchmark Harness

```bash
bash ./benches/bench_setup_ubuntu.sh     # Ubuntu-specific, download/build 10 kernel versions
cargo bench                              # Grab a coffee, this'll take a while...
```

* `bench_setup_ubuntu.sh` downloads and builds 10 consecutive Linux kernels (versions `5.0.1` to `5.0.10` - with `x86_64_defconfig`).
* `cargo bench`, among other benchmarks, searches all 10 kernels for common gadgets.

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

### Acknowledgements

This project started as an optimized solution to Chapter 8, exercise 3 of "Practical Binary Analysis" by Dennis Andreisse \[6\], and builds on the design outlined therein.

### References

* \[1\] [`goblin` crate by Lzu Tao, m4b, Philip Craig, seu, Will Glynn]https://crates.io/crates/goblin
* \[2\] [`rayon` crate by Josh Stone, Niko Matsakis]https://crates.io/crates/rayon
* \[3\] [`xgadget/.github/workflows`]https://github.com/entropic-security/xgadget/tree/master/.github/workflows
* \[4\] [`criterion` crate by Brook Heisler, Jorge Aparicio]https://crates.io/crates/criterion
* \[5\] [`iced-x86` crate by 0xd4d]https://crates.io/crates/iced-x86
* \[6\] ["Practical Binary Analysis" by Dennis Andreisse]https://practicalbinaryanalysis.com/