Skip to content

Commit

Permalink
Finalize v0.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
tnballo committed Nov 11, 2020
1 parent 9fca435 commit 9181417
Show file tree
Hide file tree
Showing 13 changed files with 144 additions and 83 deletions.
16 changes: 8 additions & 8 deletions Cargo.toml
Expand Up @@ -22,26 +22,26 @@ include = [
[dependencies]
zydis = "3.1"
goblin = "0.2"
rayon = "1.4"
bitflags = "1.2"
rayon = "1"
bitflags = "1"
colored = "2"
rustc-hash = "1"
structopt = { version = "0.3", default-features = false, optional = true }
num_cpus = { version = "1.13", optional = true }
num_cpus = { version = "1", optional = true }
regex = { version = "1", optional = true }
strip-ansi-escapes = { version = "0.1", optional = true }
lazy_static = { version = "1.4.0", optional = true }
lazy_static = { version = "1.4", optional = true }
term_size = { version = "0.3.2", optional = true }
checksec = { version = "0.0.7", features = ["elf", "pe", "color"], optional = true }
memmap = { version = "0.7.0", optional = true }

[dev-dependencies]
criterion = "0.3"
rand = "0.7"
dirs = "3.0"
predicates = "1.0.5"
assert_cmd = "1.0.1"
tempfile = "3.1.0"
dirs = "3"
predicates = "1"
assert_cmd = "1"
tempfile = "3"

[features]
cli-bin = ["structopt", "num_cpus", "regex", "strip-ansi-escapes", "lazy_static", "term_size", "checksec", "memmap"]
Expand Down
13 changes: 7 additions & 6 deletions README.md
Expand Up @@ -59,7 +59,7 @@ let cross_reg_write_gadgets = xgadget::filter_stack_set_regs(&cross_gadgets);
Run `xgadget --help`:

```
xgadget v0.2.0
xgadget v0.3.0
About: Fast, parallel, cross-variant ROP/JOP gadget search for x86/x64 binaries.
Cores: 8 logical, 8 physical
Expand All @@ -84,9 +84,10 @@ FLAGS:
-V, --version Prints version information
OPTIONS:
-a, --arch <ARCH> For raw (no header) files: specify arch ('x8086', 'x86', or 'x64') [default: x64]
-l, --max-len <LEN> Gadgets up to LEN instrs long. If 0: all gadgets, any length [default: 5]
-f, --regex-filter <EXPR> Filter to gadgets matching a regular expression
-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 gadgets]
-l, --max-len <LEN> Gadgets up to LEN instrs long. If 0: all gadgets, any length [default: 5]
-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
Expand Down Expand Up @@ -117,11 +118,11 @@ 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, e.g. an older-gen consumer CPU) 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 26 seconds*!
On an i7-9700K (8C/8T, 3.6GHz base, 4.9 GHz max, e.g. an older-gen consumer CPU) 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 31 seconds*!

Note this is a statistical benchmark that samples from many iterations, and requires a lot of RAM (> 32GB). If you just want to run `xgadget` on the 10 kernels once, use `./benches/run_on_bench_kernels.sh`.

Searching all 10 kernels for *both* partial and full matches is still in beta, no benchmarks yet (implemented but not yet optimized). Because of the performance hit and the lower utility of partial gadget matches, this search option is disabled by default. It can be enabled with the `--partial-match` flag for the CLI, or via setting a configuration bit, e.g. `search_config |= xgadget::SearchConfig::PART`, for the library API. Conversely, removing default options improves performance: searching all 10 kernels for only ROP gadgets (ignoring JOP and syscall gadgets) takes just 17 seconds. `xgadget` is designed to scale for large binaries while being easily configurable.
Searching all 10 kernels for *both* partial and full matches is still in beta, no benchmarks yet (implemented but not yet optimized). Because of the performance hit and the lower utility of partial gadget matches, this search option is disabled by default. It can be enabled with the `--partial-match` flag for the CLI, or via setting a configuration bit, e.g. `search_config |= xgadget::SearchConfig::PART`, for the library API. Conversely, removing default options improves performance: searching all 10 kernels for only ROP gadgets (ignoring JOP and syscall gadgets) takes just 22 seconds. `xgadget` is designed to scale for large binaries while being easily configurable.

### Acknowledgements

Expand Down
18 changes: 5 additions & 13 deletions benches/bench_1_misc.rs
Expand Up @@ -27,21 +27,13 @@ fn pivot_bench(c: &mut Criterion) {

let readelf_bin = xgadget::Binary::from_path_str("/usr/bin/readelf").unwrap();
let bins = vec![readelf_bin];
let readelf_gadgets = xgadget::find_gadgets(
&bins,
MAX_GADGET_LEN,
xgadget::SearchConfig::DEFAULT,
)
.unwrap();
let readelf_gadgets =
xgadget::find_gadgets(&bins, MAX_GADGET_LEN, xgadget::SearchConfig::DEFAULT).unwrap();

let gdb_bin = xgadget::Binary::from_path_str("/usr/bin/gdb").unwrap();
let bins = vec![gdb_bin];
let gdb_gadgets = xgadget::find_gadgets(
&bins,
MAX_GADGET_LEN,
xgadget::SearchConfig::DEFAULT,
)
.unwrap();
let gdb_gadgets =
xgadget::find_gadgets(&bins, MAX_GADGET_LEN, xgadget::SearchConfig::DEFAULT).unwrap();

c.bench_function("readelf_pivot_filter_seq", |b| {
b.iter(|| filter_stack_pivot_sequential(&readelf_gadgets))
Expand All @@ -60,4 +52,4 @@ fn pivot_bench(c: &mut Criterion) {
// Runner --------------------------------------------------------------------------------------------------------------

criterion_group!(benches, pivot_bench);
criterion_main!(benches);
criterion_main!(benches);
14 changes: 2 additions & 12 deletions benches/bench_2_elf_userspace.rs
Expand Up @@ -9,22 +9,12 @@ fn elf_userspace_bench(c: &mut Criterion) {
let bins = vec![bin];
c.bench_function("readelf_search", |b| {
b.iter(|| {
xgadget::find_gadgets(
&bins,
MAX_GADGET_LEN,
xgadget::SearchConfig::DEFAULT,
)
.unwrap()
xgadget::find_gadgets(&bins, MAX_GADGET_LEN, xgadget::SearchConfig::DEFAULT).unwrap()
})
});
c.bench_function("gdb_search", |b| {
b.iter(|| {
xgadget::find_gadgets(
&bins,
MAX_GADGET_LEN,
xgadget::SearchConfig::DEFAULT,
)
.unwrap()
xgadget::find_gadgets(&bins, MAX_GADGET_LEN, xgadget::SearchConfig::DEFAULT).unwrap()
})
});
}
Expand Down
7 changes: 1 addition & 6 deletions benches/bench_3_elf_kernels.rs
Expand Up @@ -19,12 +19,7 @@ fn elf_kernel_bench(c: &mut Criterion) {

c.bench_function("10_kernel_search", |b| {
b.iter(|| {
xgadget::find_gadgets(
&bins,
MAX_GADGET_LEN,
xgadget::SearchConfig::DEFAULT,
)
.unwrap()
xgadget::find_gadgets(&bins, MAX_GADGET_LEN, xgadget::SearchConfig::DEFAULT).unwrap()
})
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/binary.rs
Expand Up @@ -5,8 +5,8 @@ use std::path::Path;
use std::str::FromStr;

//use hashbrown::HashSet;
use rustc_hash::FxHashSet as HashSet;
use rayon::prelude::*;
use rustc_hash::FxHashSet as HashSet;

// Segment -------------------------------------------------------------------------------------------------------------

Expand Down
62 changes: 62 additions & 0 deletions src/cli/checksec_fmt.rs
@@ -0,0 +1,62 @@
use std::fmt;

use checksec::colorize_bool;
use checksec::elf::ElfCheckSecResults;
use checksec::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.

pub struct CustomElfCheckSecResults(pub ElfCheckSecResults);

// Custom ELF string format
// https://github.com/etke/checksec.rs/blob/3ef5573ac400c5d4aa5cd63cfcaab7db53f08b02/src/elf.rs#L133
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\
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
)
}
}

pub struct CustomPeCheckSecResults(pub PECheckSecResults);

// Custom PE string format
// https://github.com/etke/checksec.rs/blob/3ef5573ac400c5d4aa5cd63cfcaab7db53f08b02/src/pe.rs#L339
impl fmt::Display for CustomPeCheckSecResults {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"\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)
)
}
}
21 changes: 15 additions & 6 deletions src/cli/cli.rs
@@ -1,12 +1,18 @@
use std::fmt;
use std::fs;
use std::time::Instant;

use checksec::elf::ElfCheckSecResults;
use checksec::pe::PECheckSecResults;
use colored::Colorize;
use goblin::Object;
use rayon::prelude::*;
use regex::Regex;
use structopt::StructOpt;

mod checksec_fmt;
use checksec_fmt::{CustomElfCheckSecResults, CustomPeCheckSecResults};

#[macro_use]
extern crate lazy_static;

Expand Down Expand Up @@ -204,14 +210,17 @@ impl CLIOpts {
let buf = fs::read(path).unwrap();
match Object::parse(&buf).unwrap() {
Object::Elf(elf) => {
println!("{:#?}", checksec::elf::ElfCheckSecResults::parse(&elf));
println!(
"{}",
CustomElfCheckSecResults(ElfCheckSecResults::parse(&elf))
);
}
Object::PE(pe) => {
let mm_buf =
unsafe { memmap::Mmap::map(&fs::File::open(path).unwrap()).unwrap() };
println!(
"{:#?}",
checksec::pe::PECheckSecResults::parse(&pe, &mm_buf)
"{}",
CustomPeCheckSecResults(PECheckSecResults::parse(&pe, &mm_buf))
);
}
_ => panic!("Only ELF and PE checksec currently supported!"),
Expand All @@ -220,8 +229,8 @@ impl CLIOpts {
}
}

impl std::fmt::Display for CLIOpts {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for CLIOpts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} [ search: {}, x_match: {}, max_len: {}, syntax: {}, regex_filter: {} ]",
Expand Down Expand Up @@ -291,7 +300,7 @@ fn main() {
let filter_regex = Regex::new(
&cli.usr_regex
.clone()
.unwrap_or("unused_but_initialized".to_string())
.unwrap_or_else(|| "unused_but_initialized".to_string())
.trim(),
)
.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/filters.rs
Expand Up @@ -115,7 +115,7 @@ pub fn filter_bad_addr_bytes<'a>(
.partial_matches
.iter()
.filter(|(addr, _)| addr.to_le_bytes().iter().all(|b| !bad_bytes.contains(b)))
.map(|(addr, bins)| (addr.clone(), bins.clone()))
.map(|(addr, bins)| (*addr, bins.clone()))
.collect();

g.partial_matches = tmp_map;
Expand Down
2 changes: 1 addition & 1 deletion src/gadget.rs
Expand Up @@ -80,4 +80,4 @@ impl Hash for Gadget<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.instrs.hash(state);
}
}
}
15 changes: 8 additions & 7 deletions src/lib.rs
Expand Up @@ -56,7 +56,7 @@
//!Run `xgadget --help`:
//!
//!```ignore
//!xgadget v0.2.0
//!xgadget v0.3.0
//!
//!About: Fast, parallel, cross-variant ROP/JOP gadget search for x86/x64 binaries.
//!Cores: 8 logical, 8 physical
Expand All @@ -81,9 +81,10 @@
//! -V, --version Prints version information
//!
//!OPTIONS:
//! -a, --arch <ARCH> For raw (no header) files: specify arch ('x8086', 'x86', or 'x64') [default: x64]
//! -l, --max-len <LEN> Gadgets up to LEN instrs long. If 0: all gadgets, any length [default: 5]
//! -f, --regex-filter <EXPR> Filter to gadgets matching a regular expression
//! -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 gadgets]
//! -l, --max-len <LEN> Gadgets up to LEN instrs long. If 0: all gadgets, any length [default: 5]
//! -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
Expand Down Expand Up @@ -114,11 +115,11 @@
//!* `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, e.g. an older-gen consumer CPU) 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 26 seconds*!
//!On an i7-9700K (8C/8T, 3.6GHz base, 4.9 GHz max, e.g. an older-gen consumer CPU) 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 31 seconds*!
//!
//!Note this is a statistical benchmark that samples from many iterations, and requires a lot of RAM (> 32GB). If you just want to run `xgadget` on the 10 kernels once, use `./benches/run_on_bench_kernels.sh`.
//!
//!Searching all 10 kernels for *both* partial and full matches is still in beta, no benchmarks yet (implemented but not yet optimized). Because of the performance hit and the lower utility of partial gadget matches, this search option is disabled by default. It can be enabled with the `--partial-match` flag for the CLI, or via setting a configuration bit, e.g. `search_config |= xgadget::SearchConfig::PART`, for the library API. Conversely, removing default options improves performance: searching all 10 kernels for only ROP gadgets (ignoring JOP and syscall gadgets) takes just 17 seconds. `xgadget` is designed to scale for large binaries while being easily configurable.
//!Searching all 10 kernels for *both* partial and full matches is still in beta, no benchmarks yet (implemented but not yet optimized). Because of the performance hit and the lower utility of partial gadget matches, this search option is disabled by default. It can be enabled with the `--partial-match` flag for the CLI, or via setting a configuration bit, e.g. `search_config |= xgadget::SearchConfig::PART`, for the library API. Conversely, removing default options improves performance: searching all 10 kernels for only ROP gadgets (ignoring JOP and syscall gadgets) takes just 22 seconds. `xgadget` is designed to scale for large binaries while being easily configurable.
//!
//!### Acknowledgements
//!
Expand Down Expand Up @@ -156,4 +157,4 @@ pub mod semantics;
pub use crate::semantics::*;

pub mod str_fmt;
pub use crate::str_fmt::*;
pub use crate::str_fmt::*;

0 comments on commit 9181417

Please sign in to comment.