diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f7ce4ed --- /dev/null +++ b/src/main.rs @@ -0,0 +1,396 @@ +use clap::Parser; +use libc::pid_t; +use nix::sys::mman::{MapFlags, ProtFlags}; +use nix::sys::ptrace::Request::*; +use nix::unistd::ForkResult; +use std::ffi::c_void; +use std::fs; +use std::num::NonZeroUsize; +use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; +use yaxpeax_arch::LengthedInstruction; + +#[derive(Parser)] +#[clap(about, version, author)] +struct Args { + /// file of bytes to execute + #[clap(short, long, parse(from_os_str), conflicts_with = "code")] + file: Option<PathBuf>, + + /// hex bytes to execute. for example, try `33c0` + #[clap(required_unless_present = "file")] + code: Option<String>, + + /// initial register state. registers not specified here may be initialized to random values. + #[clap(short, long)] + regs: Option<String>, +} + +fn parse_number(v: &str) -> Result<u64, String> { + let res = if v.starts_with("0x") { + u64::from_str_radix(&v[2..], 16) + } else if v.starts_with("0b") { + u64::from_str_radix(&v[2..], 2) + } else if v.starts_with("0o") { + u64::from_str_radix(&v[2..], 8) + } else { + u64::from_str_radix(v, 10) + }; + + res.map_err(|e| format!("{}", e)) +} + +fn main() { + let args = Args::parse(); + + let buf: Vec<u8> = match args.code { + Some(code) => match hex::decode(code) { + Ok(buf) => buf, + Err(e) => { + eprintln!("invalid input, {}. expected a sequence of bytes as hex", e); + return; + } + }, + None => { + let name = args.file.unwrap(); + match fs::read(&name) { + Ok(v) => v, + Err(e) => { + eprintln!("error reading {}: {}", name.display(), e); + return; + } + } + } + }; + + let initial_rip: Option<u64> = args.regs.as_ref() + .and_then(|regs| regs.split(",").find(|x| x.starts_with("rip="))) + .map(|assign| { + match assign.split("=").collect::<Vec<_>>().as_slice() { + ["rip", value] => parse_number(value), + [_other, _value] => Err(format!("found `rip=` but then string didn't start with `rip=`?")), + other => Err(format!("string was not a simple reg=value format: {:?}", other)) + } + }) + .transpose() + .unwrap(); + + // implement `initial_rip`, if present, as follows: + // `initial_rip / 4096` is implemented by an mmap base address, + // `initial_rip % 4096` is implemented by offsetting in that mmap ("before" initial_rip is + // implicitly all 0) + let required_len = initial_rip.map(|x| x as usize & 0xfff).unwrap_or(0) + buf.len() + 1; + let rounded_len = (required_len + 0xfff) & !0xfff; + + let sync: *const AtomicBool = { + let shared_ptr = unsafe { + nix::sys::mman::mmap( + None, + NonZeroUsize::new(4096).unwrap(), + ProtFlags::PROT_EXEC | ProtFlags::PROT_WRITE | ProtFlags::PROT_READ, + MapFlags::MAP_SHARED | MapFlags::MAP_ANON, + 0, + 0, + ).unwrap() + }; + let ptr = shared_ptr as *mut AtomicBool; + unsafe { std::ptr::write(ptr, AtomicBool::new(false)) }; + ptr as *const AtomicBool + }; + + let mut map_flags = MapFlags::MAP_SHARED | MapFlags::MAP_ANON; + if initial_rip.is_some() { + map_flags |= MapFlags::MAP_FIXED; + } + + let code = unsafe { + nix::sys::mman::mmap( + initial_rip.map(|x| NonZeroUsize::new(x as usize - (x as usize % 0x1000)).unwrap()), + NonZeroUsize::new(rounded_len).unwrap(), + ProtFlags::PROT_EXEC | ProtFlags::PROT_WRITE | ProtFlags::PROT_READ, + map_flags, + 0, + 0, + ).unwrap() + }; + + let initial_rip = (code as u64) + initial_rip.map(|x| x & 0xfff).unwrap_or(0); + + let slice: &mut [u8] = unsafe { + std::slice::from_raw_parts_mut( + initial_rip as *mut u8, + required_len + ) + }; + (&mut slice[..buf.len()]).copy_from_slice(buf.as_slice()); + slice[buf.len()] = 0xcc; + + println!("loaded code..."); + let mut offset = 0; + while offset < buf.len() + 1 { + match yaxpeax_x86::long_mode::InstDecoder::default().decode_slice(&slice[offset..]) { + Ok(inst) => { + if offset < buf.len() { + println!(" {:016x}: {}", offset + initial_rip as usize, inst); + } else { + println!(" {:016x}: 🏁 ({})", offset + initial_rip as usize, inst); + } + offset += inst.len().to_const() as usize + } + Err(e) => { + println!(" {:016x}: {}", offset + initial_rip as usize, e); + println!(" (offset {:x})", offset); + break; + } + } + } + println!("running..."); + + let target = match unsafe { nix::unistd::fork().unwrap() } { + ForkResult::Parent { child } => PtraceEvalTarget::attach(child.as_raw(), sync), + ForkResult::Child => unsafe { setup(sync) }, + }; + + unsafe { + target.clear_regs(); + if let Some(regs) = args.regs.as_ref() { + target.apply_regs(regs); + } + target.set_rip(initial_rip); + + let regs = target.get_regs(); + let status = target.run(); + // println!("status: {}", status); + + let exit_regs = target.get_regs(); + + print_diff(®s, &exit_regs); + + if status & 0xff == 0x7f { + let signal = status >> 8; + if signal == libc::SIGTRAP { + if exit_regs.rip == (initial_rip + buf.len() as u64 + 1) { + // code completed normally + } else { + println!("sigtrap at atypical address: {:016x}", exit_regs.rip); + std::process::exit(1); + } + } else if signal == libc::SIGSEGV { + println!("sigsegv at unexpected address: {:016x}", exit_regs.rip); + std::process::exit(1); + } else { + println!("signal {} at unexpected address: {:016x}", signal, exit_regs.rip); + } + } else if status <= 255 { + println!("exited with signal: {}", status & 0xff); + std::process::exit(1); + } else { + println!("unknown stop status? {}", status); + std::process::exit(1); + } + } +} + +fn print_diff(from: &libc::user_regs_struct, to: &libc::user_regs_struct) { + if from.rax != to.rax { + println!(" rax: {:016x}", from.rax); + println!(" to -> {:016x}", to.rax); + } + if from.rcx != to.rcx { + println!(" rcx: {:016x}", from.rcx); + println!(" to -> {:016x}", to.rcx); + } + if from.rdx != to.rdx { + println!(" rdx: {:016x}", from.rdx); + println!(" to -> {:016x}", to.rdx); + } + if from.rbx != to.rbx { + println!(" rbx: {:016x}", from.rbx); + println!(" to -> {:016x}", to.rbx); + } + if from.rbp != to.rbp { + println!(" rbp: {:016x}", from.rbp); + println!(" to -> {:016x}", to.rbp); + } + if from.rsp != to.rsp { + println!(" rsp: {:016x}", from.rsp); + println!(" to -> {:016x}", to.rsp); + } + if from.rsi != to.rsi { + println!(" rsi: {:016x}", from.rsi); + println!(" to -> {:016x}", to.rsi); + } + if from.rdi != to.rdi { + println!(" rdi: {:016x}", from.rdi); + println!(" to -> {:016x}", to.rdi); + } + if from.r8 != to.r8 { + println!(" r8: {:016x}", from.r8); + println!(" to -> {:016x}", to.r8); + } + if from.r9 != to.r9 { + println!(" r9: {:016x}", from.r9); + println!(" to -> {:016x}", to.r9); + } + if from.r10 != to.r10 { + println!(" r10: {:016x}", from.r10); + println!(" to -> {:016x}", to.r10); + } + if from.r11 != to.r11 { + println!(" r11: {:016x}", from.r11); + println!(" to -> {:016x}", to.r11); + } + if from.r12 != to.r12 { + println!(" r12: {:016x}", from.r12); + println!(" to -> {:016x}", to.r12); + } + if from.r13 != to.r13 { + println!(" r13: {:016x}", from.r13); + println!(" to -> {:016x}", to.r13); + } + if from.r14 != to.r14 { + println!(" r14: {:016x}", from.r14); + println!(" to -> {:016x}", to.r14); + } + if from.r15 != to.r15 { + println!(" r15: {:016x}", from.r15); + println!(" to -> {:016x}", to.r15); + } + if from.rip != to.rip { + println!(" rip: {:016x}", from.rip); + println!(" to -> {:016x}", to.rip); + } + if from.eflags != to.eflags { + println!(" eflags: {:08x}", from.eflags); + println!(" to -> {:08x}", to.eflags); + } +} + +unsafe fn setup(sync: *const AtomicBool) -> ! { + assert_eq!(libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL), 0); + sync.as_ref().unwrap().store(true, Ordering::SeqCst); + loop {} +} + +struct PtraceEvalTarget { + pid: pid_t, +} + +impl PtraceEvalTarget { + fn attach(pid: pid_t, sync: *const AtomicBool) -> Self { + // wait until the child process signals it's ready to be attached to. this solves a small + // race where if we attach and die before the child sets `prctl(PR_SET_PDEATHSIG)`, the + // child process can become an orphan. the default config leaves the child process in an + // infinite loop in such a case. + let syncref = unsafe { sync.as_ref().unwrap() }; + while !syncref.load(Ordering::SeqCst) { + std::thread::sleep(std::time::Duration::from_millis(10)); + } + if unsafe { libc::ptrace(PTRACE_ATTACH as u32, pid) } != 0 { + panic!("ptrace: {}", nix::errno::errno()); + } + if unsafe { libc::waitpid(pid, std::ptr::null_mut(), 0) } == -1 { + panic!("waitpid: {}", nix::errno::errno()); + } + + Self { pid } + } + + unsafe fn apply_regs(&self, regs: &str) { + let mut state = self.get_regs(); + + let parts = regs.split(","); + for part in parts { + let kv = part.split("=").collect::<Vec<_>>(); + match kv.as_slice() { + [reg, value] => { + let value: u64 = parse_number(value).unwrap(); + match *reg { + "rax" => { state.rax = value }, + "rcx" => { state.rcx = value }, + "rdx" => { state.rdx = value }, + "rbx" => { state.rbx = value }, + "rbp" => { state.rbp = value }, + "rsp" => { state.rsp = value }, + "rsi" => { state.rsi = value }, + "rdi" => { state.rdi = value }, + "r8" => { state.r8 = value }, + "r9" => { state.r9 = value }, + "r10" => { state.r10 = value }, + "r11" => { state.r11 = value }, + "r12" => { state.r12 = value }, + "r13" => { state.r13 = value }, + "r14" => { state.r14 = value }, + "r15" => { state.r15 = value }, + "eflags" => { state.eflags = value }, + "rip" => { /* handled elsewhere */ }, + other => { panic!("unknown register {}", other) } + } + }, + other => { + eprintln!("register assignment was not of the form `regname=value`? {:?}", other); + } + } + } + + self.set_regs(&mut state); + } + + unsafe fn clear_regs(&self) { + let mut regs = self.get_regs(); + regs.rax = 0; + regs.rcx = 0; + regs.rdx = 0; + regs.rbx = 0; + regs.rbp = 0; + regs.rsp = 0; + regs.rsi = 0; + regs.rdi = 0; + regs.r8 = 0; + regs.r9 = 0; + regs.r10 = 0; + regs.r11 = 0; + regs.r12 = 0; + regs.r13 = 0; + regs.r14 = 0; + regs.r15 = 0; + regs.eflags = 0; + self.set_regs(&mut regs); + } + + unsafe fn get_regs(&self) -> libc::user_regs_struct { + let mut regs: libc::user_regs_struct = + std::mem::transmute([0u8; std::mem::size_of::<libc::user_regs_struct>()]); + if libc::ptrace(PTRACE_GETREGS as u32, self.pid, std::ptr::null_mut::<*const c_void>(), &mut regs as *mut libc::user_regs_struct) != 0 { + panic!("ptrace(getregs): {}", nix::errno::errno()); + } + + regs + } + + unsafe fn set_regs(&self, regs: &mut libc::user_regs_struct) { + if libc::ptrace(PTRACE_SETREGS as u32, self.pid, std::ptr::null_mut::<*const c_void>(), regs as *mut libc::user_regs_struct) != 0 { + panic!("ptrace(setregs): {}", nix::errno::errno()); + } + } + + unsafe fn set_rip(&self, rip: u64) { + let mut regs = self.get_regs(); + regs.rip = rip; + if libc::ptrace(PTRACE_SETREGS as u32, self.pid, std::ptr::null_mut::<*const c_void>(), &mut regs as *mut libc::user_regs_struct) != 0 { + panic!("ptrace(setregs): {}", nix::errno::errno()); + } + } + + unsafe fn run(&self) -> i32 { + if libc::ptrace(PTRACE_CONT as u32, self.pid, 0, 0) != 0 { + panic!("ptrace(cont): {}", nix::errno::errno()); + } + + let mut status = 0; + if libc::waitpid(self.pid, &mut status as *mut i32, 0) == -1 { + panic!("waitpid: {}", nix::errno::errno()); + } + status + } +} |