Skip to content

Commit

Permalink
Merge pull request #9 from entropic-security/v0.7.0
Browse files Browse the repository at this point in the history
v0.7.0
  • Loading branch information
tnballo authored May 2, 2022
2 parents e8891e3 + d71ddae commit d717656
Show file tree
Hide file tree
Showing 11 changed files with 584 additions and 75 deletions.
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "xgadget"
version = "0.6.0"
version = "0.7.0"
authors = ["Tiemoko Ballo"]
edition = "2018"
license = "MIT"
Expand Down Expand Up @@ -33,6 +33,9 @@ regex = { version = "1", optional = true }
term_size = { version = "0.3", optional = true }
checksec = { version = "0.0.8", features = ["elf", "pe", "color"], optional = true }
memmap = { version = "0.7", optional = true }
num-format = { version = "0.4", optional = true }
strip-ansi-escapes = { version = "0.1", optional = true }
cli-table = { version = "0.4", optional = true }

[dev-dependencies]
pprof = { version = "0.8", features = ["flamegraph"] }
Expand All @@ -45,7 +48,8 @@ tempfile = "3"
regex = "1"

[features]
cli-bin = ["lazy_static", "clap", "num_cpus", "regex", "term_size", "checksec", "memmap"]
cli-bin = ["lazy_static", "clap", "num_cpus", "regex", "term_size", "checksec",
"memmap", "num-format", "strip-ansi-escapes", "cli-table"]

[lib]
name = "xgadget"
Expand Down
37 changes: 37 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
FROM ubuntu:20.04

# Kernel build pre-reqs
# See: https://www.linux.com/topic/desktop/how-compile-linux-kernel-0/
ENV TZ=US/New_York
ENV DEBIAN_FRONTEND="noninteractive"
RUN apt-get update && apt-get install -y \
apt-utils \
tzdata \
sudo \
curl \
git \
fakeroot \
build-essential \
ncurses-dev \
xz-utils \
libssl-dev \
bc \
flex \
libelf-dev \
bison

# Rust toolchain
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y
ENV PATH=/root/.cargo/bin:$PATH

# Src import
RUN mkdir /xgadget
WORKDIR /xgadget
COPY . /xgadget

# Test and install
RUN cargo test --all-features
RUN cargo install --path . --features cli-bin

# Build kernels for benchmarking
RUN ./benches/bench_setup_ubuntu.sh
67 changes: 57 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# xgadget

![crates.io](https://img.shields.io/crates/v/xgadget.svg)
![GitHub Actions](https://github.com/entropic-security/xgadget/workflows/test/badge.svg)
[![crates.io](https://img.shields.io/crates/v/xgadget.svg)](https://crates.io/crates/xgadget)
[![GitHub Actions](https://github.com/entropic-security/xgadget/workflows/test/badge.svg)](https://github.com/entropic-security/xgadget/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/entropic-security/xgadget/blob/master/LICENSE)

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/icedland/iced).
Expand All @@ -24,7 +25,7 @@ It's a fast, multi-threaded alternative to awesome tools like [`ROPGadget`](http
Though not yet as mature as some of its contemporaries, it contains unique and experimental functionality.
To the best of our knowledge, `xgadget` is the first gadget search tool to have these features:

* Finds registers that can be controlled (overwritten) - not just those that match a user-provided regex
* Finds gadgets that control (overwrite) specific registers - not just operands of a `pop` instruction or matches for a given regex
* Use the `--reg-ctrl <optional_register_name>` flag
* JOP search uses instruction semantics - not hardcoded regex for individual encodings
* Optionally filter to JOP "dispatcher" gadgets with flag `--dispatcher`
Expand Down Expand Up @@ -101,8 +102,8 @@ pub fn filter_stack_pivot<'a>(gadgets: &[Gadget<'a>]) -> Vec<Gadget<'a>> {

Run `xgadget --help`:

```
xgadget v0.6.0
```ignore
xgadget v0.7.0
About: Fast, parallel, cross-variant ROP/JOP gadget search for x86/x64 binaries.
Cores: 8 logical, 8 physical
Expand All @@ -120,6 +121,7 @@ OPTIONS:
-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]
-f, --regex-filter <EXPR> Filter to gadgets matching a regular expression
--fess Compute Fast Exploit Similarity Score (FESS) table for 2+ binaries
-h, --help Print 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]
Expand All @@ -136,6 +138,7 @@ OPTIONS:
-s, --sys Search for SYSCALL gadgets only [default: ROP, JOP, and SYSCALL]
-t, --att Display gadgets using AT&T syntax [default: Intel syntax]
-V, --version Print version information
```

### CLI Build and Install (Recommended)
Expand All @@ -158,21 +161,65 @@ So building a dynamically-linked binary from source with the above `cargo instal
### Why No Chain Generation?

Tools that attempt to automate ROP/JOP chain generation require heavyweight analysis - typically symbolic execution of an intermediate representation.
While this works well for small binaries and CTF problems, but tends to be error-prone and difficult to scale for large, real-world programs.
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.

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

To build a Docker container and connect to it:

```bash
bash ./benches/bench_setup_ubuntu.sh # Ubuntu-specific, download/build 10 kernel versions
cargo bench # Grab a coffee, this'll take a while...
user@host$ git clone [email protected]: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#
```

* `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.
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):

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

### Fast Exploit Similarity Score (FESS)

The `--fess` flag uses cross-variant gadget matching as metric of binary similarity.
It's a 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:

```bash
root@container# cd ./benches/kernels/
root@container# xgadget vmlinux-5.0.1 vmlinux-5.0.5 vmlinux-5.0.10 --fess
TARGET 0 - 'vmlinux-5.0.1': ELF-X64, 0x00000001000000 entry, 21065728/2 executable bytes/segments
TARGET 1 - 'vmlinux-5.0.5': ELF-X64, 0x00000001000000 entry, 21069824/2 executable bytes/segments
TARGET 2 - 'vmlinux-5.0.10': ELF-X64, 0x00000001000000 entry, 21069824/2 executable bytes/segments

+-------------+----------------------+----------------------+-----------------------+
| Gadget Type | vmlinux-5.0.1 (base) | vmlinux-5.0.5 (diff) | vmlinux-5.0.10 (diff) |
+-------------+----------------------+----------------------+-----------------------+
| ROP (full) | 175,740 | 11,124 (6.33%) | 699 (0.40%) |
+-------------+----------------------+----------------------+-----------------------+
| ROP (part) | - | 85,717 (48.77%) | 79,367 (45.16%) |
+-------------+----------------------+----------------------+-----------------------+
| JOP (full) | 97,239 | 1,093 (1.12%) | 277 (0.28%) |
+-------------+----------------------+----------------------+-----------------------+
| JOP (part) | - | 16,792 (17.27%) | 12,635 (12.99%) |
+-------------+----------------------+----------------------+-----------------------+
| SYS (full) | 81 | 20 (24.69%) | 20 (24.69%) |
+-------------+----------------------+----------------------+-----------------------+
| SYS (part) | - | 59 (72.84%) | 58 (71.60%) |
+-------------+----------------------+----------------------+-----------------------+
```

In the output table, we see that up to 45.16% of gadgets of individual ROP gadgets are portable across all three versions (counting partial matches).

### 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.
Expand Down
5 changes: 3 additions & 2 deletions README.tpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# {{crate}}

![crates.io](https://img.shields.io/crates/v/xgadget.svg)
![GitHub Actions](https://github.com/entropic-security/xgadget/workflows/test/badge.svg)
[![crates.io](https://img.shields.io/crates/v/xgadget.svg)](https://crates.io/crates/xgadget)
[![GitHub Actions](https://github.com/entropic-security/xgadget/workflows/test/badge.svg)](https://github.com/entropic-security/xgadget/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/entropic-security/xgadget/blob/master/LICENSE)

{{readme}}
146 changes: 106 additions & 40 deletions src/cli/checksec_fmt.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,128 @@
use std::fmt;

use checksec::colorize_bool;
use checksec::elf::ElfCheckSecResults;
use checksec::pe::PECheckSecResults;
use checksec::{
colorize_bool, elf::ElfCheckSecResults, macho::MachOCheckSecResults, pe::PECheckSecResults,
};
use colored::Colorize;

// This file provides a multi-line alternative to checksec's single line print
// Unfortunately, coloring is a compile-time option for the checksec crate and not a run-time one,
// so this output doesn't respect the --no-color flag.
// This file provides a multi-line, optional-color alternative to checksec's single line print.

pub struct CustomElfCheckSecResults(pub ElfCheckSecResults);
// Manual color removal
fn remove_color(results: &str) -> String {
let stripped_bytes = strip_ansi_escapes::strip(&results).unwrap();
let mut stripped_string = "\t".to_owned();
stripped_string.push_str(
&std::str::from_utf8(&stripped_bytes)
.unwrap()
.replace('\n', "\n\t"),
);
stripped_string
}

// ELF results new type
pub struct CustomElfCheckSecResults {
pub results: ElfCheckSecResults,
pub no_color: bool,
}

// Custom ELF string format
// https://github.com/etke/checksec.rs/blob/3ef5573ac400c5d4aa5cd63cfcaab7db53f08b02/src/elf.rs#L133
// https://github.com/etke/checksec.rs/blob/0ad2d5fbcd4dd0c3c37e7773301ccb017c2d33c9/src/elf.rs#L149
impl fmt::Display for CustomElfCheckSecResults {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"\tCanary: {}\n\tCFI: {}\n\tSafeStack: {}\n\tFortify: {}\n\tFortified: {}\n\t\
let results = format!(
"\tCanary: {}\n\tCFI: {}\n\tSafeStack: {}\n\tFortify: {}\n\tFortified: {:2}\n\t\
NX: {}\n\tPIE: {}\n\tRelro: {}\n\tRPATH: {}\n\tRUNPATH: {}",
colorize_bool!(self.0.canary),
colorize_bool!(self.0.clang_cfi),
colorize_bool!(self.0.clang_safestack),
colorize_bool!(self.0.fortify),
self.0.fortified,
colorize_bool!(self.0.nx),
self.0.pie,
self.0.relro,
self.0.rpath,
self.0.runpath
)
colorize_bool!(self.results.canary),
colorize_bool!(self.results.clang_cfi),
colorize_bool!(self.results.clang_safestack),
colorize_bool!(self.results.fortify),
self.results.fortified,
colorize_bool!(self.results.nx),
self.results.pie,
self.results.relro,
self.results.rpath,
self.results.runpath
);

match self.no_color {
true => write!(f, "{}", remove_color(&results)),
false => write!(f, "{}", results),
}
}
}

pub struct CustomPeCheckSecResults(pub PECheckSecResults);
// PE results new type
pub struct CustomPeCheckSecResults {
pub results: PECheckSecResults,
pub no_color: bool,
}

// Custom PE string format
// https://github.com/etke/checksec.rs/blob/3ef5573ac400c5d4aa5cd63cfcaab7db53f08b02/src/pe.rs#L339
// https://github.com/etke/checksec.rs/blob/0ad2d5fbcd4dd0c3c37e7773301ccb017c2d33c9/src/pe.rs#L355
impl fmt::Display for CustomPeCheckSecResults {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
let results = format!(
"\tASLR: {}\n\tAuthenticode: {}\n\tCFG: {}\n\tCLR: {}\n\tDEP: {}\n\t\
Dynamic Base: {}\n\tForce Integrity: {}\n\tGS: {}\n\t\
High Entropy VA: {}\n\tIsolation: {}\n\tRFG: {}\n\tSafeSEH: {}\n\tSEH: {}",
self.0.aslr,
colorize_bool!(self.0.authenticode),
colorize_bool!(self.0.cfg),
colorize_bool!(self.0.clr),
colorize_bool!(self.0.dep),
colorize_bool!(self.0.dynamic_base),
colorize_bool!(self.0.force_integrity),
colorize_bool!(self.0.gs),
colorize_bool!(self.0.high_entropy_va),
colorize_bool!(self.0.isolation),
colorize_bool!(self.0.rfg),
colorize_bool!(self.0.safeseh),
colorize_bool!(self.0.seh)
)
self.results.aslr,
colorize_bool!(self.results.authenticode),
colorize_bool!(self.results.cfg),
colorize_bool!(self.results.clr),
colorize_bool!(self.results.dep),
colorize_bool!(self.results.dynamic_base),
colorize_bool!(self.results.force_integrity),
colorize_bool!(self.results.gs),
colorize_bool!(self.results.high_entropy_va),
colorize_bool!(self.results.isolation),
colorize_bool!(self.results.rfg),
colorize_bool!(self.results.safeseh),
colorize_bool!(self.results.seh)
);

match self.no_color {
true => write!(f, "{}", remove_color(&results)),
false => write!(f, "{}", results),
}
}
}

// Mach-O results new type
pub struct CustomMachOCheckSecResults {
pub results: MachOCheckSecResults,
pub no_color: bool,
}

// Custom Mach-O string format
// https://github.com/etke/checksec.rs/blob/0ad2d5fbcd4dd0c3c37e7773301ccb017c2d33c9/src/macho.rs#L82
impl fmt::Display for CustomMachOCheckSecResults {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let results = format!(
// TODO: enable once Mach-O fortify support lands on crates.io
/*
"\tARC: {}\n\tCanary: {}\n\tCode Signature: {}\n\tEncryption: {}\n\t\
Fortify: {}\n\tFortified {:2}\n\tNX Heap: {}\n\t\
NX Stack: {}\n\tPIE: {}\n\tRestrict: {}\n\tRPath: {}",
*/
"\tARC: {}\n\tCanary: {}\n\tCode Signature: {}\n\tEncryption: {}\n\t\
NX Heap: {}\n\t\
NX Stack: {}\n\tPIE: {}\n\tRestrict: {}\n\tRPath: {}",
colorize_bool!(self.results.arc),
colorize_bool!(self.results.canary),
colorize_bool!(self.results.code_signature),
colorize_bool!(self.results.encrypted),
//colorize_bool!(self.results.fortify),
//self.results.fortified,
colorize_bool!(self.results.nx_heap),
colorize_bool!(self.results.nx_stack),
colorize_bool!(self.results.pie),
colorize_bool!(self.results.restrict),
colorize_bool!(self.results.rpath)
);

match self.no_color {
true => write!(f, "{}", remove_color(&results)),
false => write!(f, "{}", results),
}
}
}
Loading

0 comments on commit d717656

Please sign in to comment.