use clap::Parser;
use colored::control::set_override;
use core::panic;
use iced_x86::{FormatterOutput, FormatterTextKind};
use rayon::prelude::*;
use regex::Regex;
use ropr::{
binary::Binary, disassembler::Disassembly, formatter::ColourFormatter, gadgets::Gadget,
};
use rustc_hash::FxHashMap;
use std::{
error::Error,
io::{stdout, BufWriter, Write},
path::PathBuf,
time::Instant,
};
#[derive(Parser)]
#[clap(version)]
struct Opt {
#[clap(short = 'n', long)]
noisy: bool,
#[clap(short = 'c', long)]
colour: Option<bool>,
#[clap(short = 'r', long)]
norop: bool,
#[clap(short = 's', long)]
nosys: bool,
#[clap(short = 'j', long)]
nojop: bool,
#[clap(short = 'p', long)]
stack_pivot: bool,
#[clap(short = 'b', long)]
base_pivot: bool,
#[clap(short, long, default_value = "6")]
max_instr: u8,
#[clap(short = 'R', long)]
regex: Vec<String>,
#[clap(long)]
raw: Option<bool>,
#[clap(long)]
range: Vec<String>,
#[clap(short = 'u', long)]
nouniq: bool,
binary: PathBuf,
}
fn write_gadgets(mut w: impl Write, gadgets: &[(Gadget, usize)]) {
let mut output = ColourFormatter::new();
for (gadget, address) in gadgets {
output.clear();
output.write(&format!("{:#010x}: ", address), FormatterTextKind::Function);
gadget.format_instruction(&mut output);
match writeln!(w, "{}", output) {
Ok(_) => (),
Err(_) => return, }
}
}
fn main() -> Result<(), Box<dyn Error>> {
let start = Instant::now();
let opts = Opt::parse();
let b = opts.binary;
let b = Binary::new(b)?;
let sections = b.sections(opts.raw)?;
let noisy = opts.noisy;
let colour = opts.colour;
let rop = !opts.norop;
let sys = !opts.nosys;
let jop = !opts.nojop;
let uniq = !opts.nouniq;
let stack_pivot = opts.stack_pivot;
let base_pivot = opts.base_pivot;
let max_instructions_per_gadget = opts.max_instr as usize;
if max_instructions_per_gadget == 0 {
panic!("Max instructions must be >0");
}
let ranges = opts
.range
.iter()
.filter_map(|s| s.split_once('-'))
.filter_map(|(mut from, mut to)| {
if from.starts_with("0x") {
from = &from[2..];
}
if to.starts_with("0x") {
to = &to[2..];
}
let from = usize::from_str_radix(from, 16).ok()?;
let to = usize::from_str_radix(to, 16).ok()?;
Some((from, to))
})
.collect::<Vec<_>>();
let regices = opts
.regex
.into_iter()
.map(|r| Regex::new(&r))
.collect::<Result<Vec<_>, _>>()?;
let gadget_to_addr = sections
.iter()
.filter_map(Disassembly::new)
.flat_map(|dis| {
(0..dis.bytes().len())
.into_par_iter()
.filter(|offset| dis.is_tail_at(*offset, rop, sys, jop, noisy))
.flat_map_iter(|tail| {
dis.gadgets_from_tail(tail, max_instructions_per_gadget, noisy, uniq)
})
.collect::<Vec<_>>()
})
.filter(|&(_, address)| {
if ranges.is_empty() {
return true;
}
ranges
.iter()
.any(|(from, to)| -> bool { *from <= address && address <= *to })
})
.collect::<FxHashMap<_, _>>();
let mut gadgets = gadget_to_addr
.into_iter()
.filter(|(g, _)| {
let mut formatted = String::new();
g.format_instruction(&mut formatted);
regices.iter().all(|r| r.is_match(&formatted))
})
.filter(|(g, _)| !stack_pivot | g.is_stack_pivot())
.filter(|(g, _)| !base_pivot | g.is_base_pivot())
.collect::<Vec<_>>();
gadgets.sort_unstable_by(|(_, addr1), (_, addr2)| addr1.cmp(addr2));
let gadget_count = gadgets.len();
let elapsed = Instant::now() - start;
let mut stdout = BufWriter::new(stdout());
if let Some(colour) = colour {
set_override(colour);
}
write_gadgets(&mut stdout, &gadgets);
drop(stdout);
eprintln!(
"\n==> Found {} gadgets in {:.3} seconds",
gadget_count,
elapsed.as_secs_f32()
);
Ok(())
}