-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from entropic-security/v0.7.0
v0.7.0
- Loading branch information
Showing
11 changed files
with
584 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). | ||
|
@@ -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` | ||
|
@@ -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 | ||
|
@@ -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] | ||
|
@@ -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) | ||
|
@@ -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. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
} | ||
} | ||
} |
Oops, something went wrong.