diff --git a/Cargo.toml b/Cargo.toml index 4be81cb..054b964 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ security-framework-sys = "1.0" # Dependencies specific to Linux [target.'cfg(target_os="linux")'.dependencies] -procfs = "0.8.0" +procfs = "0.9.0" [target.'cfg(target_os="windows")'.dependencies] winapi = { version = "0.3.9", features = ["winuser","processthreadsapi","winbase","minwinbase","debugapi","winnt","memoryapi","dbghelp"] } diff --git a/examples/gui.rs b/examples/gui.rs index 2a0e5c7..86138d6 100644 --- a/examples/gui.rs +++ b/examples/gui.rs @@ -19,8 +19,8 @@ mod example { glutin::window::WindowBuilder, Display, Surface, }; use headcrab::{ - symbol::DisassemblySource, symbol::RelocatedDwarf, target::LinuxTarget, target::UnixTarget, - CrabResult, + symbol::DisassemblySource, symbol::RelocatedDwarf, target::LinuxTarget, target::Registers, + target::UnixTarget, CrabResult, }; use imgui::{im_str, ClipboardBackend, Condition, FontSource, ImStr, ImString}; use imgui_glium_renderer::Renderer; @@ -210,7 +210,7 @@ mod example { .size([395.0, 390.0], Condition::FirstUseEver) .build(ui, || { if let Err(err) = (|| -> CrabResult<()> { - let ip = remote.read_regs()?.rip; + let ip = remote.read_regs()?.ip(); let mut code = [0; 64]; unsafe { remote.read().read(&mut code, ip as usize).apply()?; @@ -241,7 +241,12 @@ mod example { context.load_debuginfo_if_necessary()?; - let regs = context.remote.as_ref().unwrap().read_regs()?; + let regs = context + .remote + .as_ref() + .unwrap() + .main_thread()? + .read_regs()?; let mut stack: [usize; 1024] = [0; 1024]; unsafe { @@ -250,7 +255,7 @@ mod example { .as_ref() .unwrap() .read() - .read(&mut stack, regs.rsp as usize) + .read(&mut stack, regs.sp() as usize) .apply()?; } @@ -259,16 +264,16 @@ mod example { headcrab::symbol::unwind::frame_pointer_unwinder( context.debuginfo(), &stack[..], - regs.rip as usize, - regs.rsp as usize, - regs.rbp as usize, + regs.ip() as usize, + regs.sp() as usize, + regs.bp().unwrap() as usize, // TODO: `unwrap` is unsafe for non-x86 platforms ) .collect() } BacktraceType::Naive => headcrab::symbol::unwind::naive_unwinder( context.debuginfo(), &stack[..], - regs.rip as usize, + regs.ip() as usize, ) .collect(), }; diff --git a/examples/repl.rs b/examples/repl.rs index b57e46e..4631f69 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -15,7 +15,7 @@ mod example { use headcrab::{ symbol::{DisassemblySource, RelocatedDwarf, Snippet}, - target::{AttachOptions, LinuxTarget, UnixTarget}, + target::{AttachOptions, LinuxTarget, Registers, UnixTarget}, CrabResult, }; @@ -359,7 +359,7 @@ mod example { return show_backtrace(context, &sub_cmd); } ReplCommand::Disassemble(()) => { - let ip = context.remote()?.read_regs()?.rip; + let ip = context.remote()?.read_regs()?.ip(); let mut code = [0; 64]; unsafe { context @@ -475,8 +475,8 @@ mod example { } fn show_locals(context: &mut Context) -> CrabResult<()> { - let regs = context.remote()?.read_regs()?; - let func = regs.rip as usize; + let regs = context.remote()?.main_thread()?.read_regs()?; + let func = regs.ip() as usize; let res = context.debuginfo().with_addr_frames( func, |func, mut frames: headcrab::symbol::FrameIter| { @@ -512,9 +512,9 @@ mod example { .function_debuginfo() .ok_or_else(|| "No dwarf debuginfo for function".to_owned())?; - let mut eval_ctx = X86_64EvalContext { + let mut eval_ctx = EvalContext { frame_base: None, - regs, + regs: Box::new(regs), }; // FIXME handle DW_TAG_inlined_subroutine with DW_AT_frame_base in parent DW_TAG_subprogram @@ -531,7 +531,7 @@ mod example { match res[0].location { gimli::Location::Register { register: gimli::X86_64::RBP, - } => eval_ctx.frame_base = Some(regs.rbp), + } => eval_ctx.frame_base = regs.bp(), ref loc => unimplemented!("{:?}", loc), // FIXME } } @@ -564,14 +564,14 @@ mod example { fn get_call_stack(context: &mut Context, bt_type: &BacktraceType) -> CrabResult> { context.load_debuginfo_if_necessary()?; - let regs = context.remote()?.read_regs()?; + let regs = context.remote()?.main_thread()?.read_regs()?; let mut stack: [usize; 1024] = [0; 1024]; unsafe { context .remote()? .read() - .read(&mut stack, regs.rsp as usize) + .read(&mut stack, regs.sp() as usize) .apply()?; } @@ -579,15 +579,15 @@ mod example { BacktraceType::FramePtr => headcrab::symbol::unwind::frame_pointer_unwinder( context.debuginfo(), &stack[..], - regs.rip as usize, - regs.rsp as usize, - regs.rbp as usize, + regs.ip() as usize, + regs.sp() as usize, + regs.bp().unwrap() as usize, // TODO: fix `unwrap` for non-x86 platforms ) .collect(), BacktraceType::Naive => headcrab::symbol::unwind::naive_unwinder( context.debuginfo(), &stack[..], - regs.rip as usize, + regs.ip() as usize, ) .collect(), }; @@ -658,48 +658,23 @@ mod example { Ok(()) } - fn get_linux_x86_64_reg( - regs: libc::user_regs_struct, - ) -> impl Fn(gimli::Register, gimli::ValueType) -> gimli::Value { - move |reg, ty| { - let val = match reg { - gimli::X86_64::RAX => regs.rax, - gimli::X86_64::RBX => regs.rbx, - gimli::X86_64::RCX => regs.rcx, - gimli::X86_64::RDX => regs.rdx, - gimli::X86_64::RSI => regs.rsi, - gimli::X86_64::RDI => regs.rdi, - gimli::X86_64::RSP => regs.rsp, - gimli::X86_64::RBP => regs.rbp, - gimli::X86_64::R9 => regs.r9, - gimli::X86_64::R10 => regs.r10, - gimli::X86_64::R11 => regs.r11, - gimli::X86_64::R12 => regs.r12, - gimli::X86_64::R13 => regs.r13, - gimli::X86_64::R14 => regs.r14, - gimli::X86_64::R15 => regs.r15, - reg => unimplemented!("{:?}", reg), // FIXME - }; - match ty { - gimli::ValueType::Generic => gimli::Value::Generic(val), - gimli::ValueType::U64 => gimli::Value::U64(val), - _ => unimplemented!(), - } - } - } - - struct X86_64EvalContext { + struct EvalContext { frame_base: Option, - regs: libc::user_regs_struct, + regs: Box, } - impl headcrab::symbol::dwarf_utils::EvalContext for X86_64EvalContext { + impl headcrab::symbol::dwarf_utils::EvalContext for EvalContext { fn frame_base(&self) -> u64 { self.frame_base.unwrap() } fn register(&self, register: gimli::Register, base_type: gimli::ValueType) -> gimli::Value { - get_linux_x86_64_reg(self.regs)(register, base_type) + let val = self.regs.reg_for_dwarf(register).unwrap(); + match base_type { + gimli::ValueType::Generic => gimli::Value::Generic(val), + gimli::ValueType::U64 => gimli::Value::U64(val), + _ => unimplemented!(), + } } fn memory( @@ -715,7 +690,7 @@ mod example { fn show_local<'ctx>( kind: &str, - eval_ctx: &X86_64EvalContext, + eval_ctx: &EvalContext, local: headcrab::symbol::Local<'_, 'ctx>, ) -> CrabResult<()> { let value = match local.value() { @@ -778,20 +753,20 @@ mod example { run_function, stack ); - let orig_regs = inj_ctx.target().read_regs()?; - let regs = libc::user_regs_struct { - rip: run_function, - rsp: stack, - ..orig_regs - }; - inj_ctx.target().write_regs(regs)?; + // TODO: replace `main_thread` with the current thread when we'll have it. + let orig_regs = inj_ctx.target().main_thread()?.read_regs()?; + let mut regs = orig_regs.clone(); + regs.set_ip(run_function); + regs.set_sp(stack); + inj_ctx.target().main_thread()?.write_regs(regs)?; + let status = inj_ctx.target().unpause()?; println!( "{:?} at 0x{:016x}", status, - inj_ctx.target().read_regs()?.rip + inj_ctx.target().read_regs()?.ip() ); - inj_ctx.target().write_regs(orig_regs)?; + inj_ctx.target().main_thread()?.write_regs(orig_regs)?; Ok(()) } @@ -841,21 +816,21 @@ mod example { run_function, stack ); - let orig_regs = inj_ctx.target().read_regs()?; - println!("orig rip: {:016x}", orig_regs.rip); - let regs = libc::user_regs_struct { - rip: run_function, - rsp: stack, - ..orig_regs - }; - inj_ctx.target().write_regs(regs)?; + let orig_regs = inj_ctx.target().main_thread()?.read_regs()?; + println!("orig rip: {:016x}", orig_regs.ip()); + + let mut regs = orig_regs.clone(); + regs.set_ip(run_function); + regs.set_sp(stack); + inj_ctx.target().main_thread()?.write_regs(regs)?; + let status = inj_ctx.target().unpause()?; println!( "{:?} at 0x{:016x}", status, - inj_ctx.target().read_regs()?.rip + inj_ctx.target().main_thread()?.read_regs()?.ip() ); - inj_ctx.target().write_regs(orig_regs)?; + inj_ctx.target().main_thread()?.write_regs(orig_regs)?; Ok(()) } diff --git a/src/target.rs b/src/target.rs index 11acc9a..2289f33 100644 --- a/src/target.rs +++ b/src/target.rs @@ -18,8 +18,12 @@ mod windows; #[cfg(target_os = "windows")] pub use windows::*; +mod registers; mod thread; +pub use registers::Registers; +pub use thread::Thread; + #[derive(Debug)] pub struct MemoryMap { /// Start and end range of the mapped memory. diff --git a/src/target/linux.rs b/src/target/linux.rs index 080982c..b0fb1f6 100644 --- a/src/target/linux.rs +++ b/src/target/linux.rs @@ -4,8 +4,11 @@ mod readmem; mod software_breakpoint; mod writemem; -use crate::target::thread::Thread; -use crate::target::unix::{self, UnixTarget}; +use crate::target::{ + registers::Registers, + thread::Thread, + unix::{self, UnixTarget}, +}; use crate::CrabResult; use nix::sys::ptrace; use nix::sys::wait::{waitpid, WaitStatus}; @@ -42,6 +45,9 @@ const SUPPORTED_HARDWARE_BREAKPOINTS: usize = 4; #[cfg(not(target_arch = "x86_64"))] const SUPPORTED_HARDWARE_BREAKPOINTS: usize = 0; +#[cfg(target_arch = "x86_64")] +use crate::target::registers::RegistersX86_64; + struct LinuxThread { task: Task, } @@ -52,9 +58,23 @@ impl LinuxThread { } } -impl Thread for LinuxThread { +impl Thread for LinuxThread +where + Regs: Registers + From + Into, +{ type ThreadId = i32; + fn read_regs(&self) -> CrabResult { + let regs = nix::sys::ptrace::getregs(Pid::from_raw(self.task.tid))?; + Ok(Regs::from(regs)) + } + + fn write_regs(&self, regs: Regs) -> CrabResult<()> { + let regs = regs.into(); + nix::sys::ptrace::setregs(Pid::from_raw(self.task.tid), regs)?; + Ok(()) + } + fn name(&self) -> CrabResult> { match self.task.stat() { Ok(t_stat) => Ok(Some(t_stat.comm.clone())), @@ -71,6 +91,10 @@ impl Thread for LinuxThread { } } +/// Type alias to use for boxed threads. +#[cfg(target_arch = "x86_64")] +type BoxedLinuxThread = Box>; + /// This structure holds the state of a debuggee on Linux based systems /// You can use it to read & write debuggee's memory, pause it, set breakpoints, etc. pub struct LinuxTarget { @@ -102,10 +126,12 @@ impl UnixTarget for LinuxTarget { // We may have hit a user defined breakpoint if let WaitStatus::Stopped(_, nix::sys::signal::Signal::SIGTRAP) = status { + let regs = self.main_thread()?.read_regs()?; + if let Some(bp) = self .breakpoints .borrow_mut() - .get_mut(&(self.read_regs()?.rip as usize - 1)) + .get_mut(&(regs.ip() as usize - 1)) { // Register which breakpoint was hit self.hit_breakpoint.borrow_mut().replace(bp.clone()); @@ -141,7 +167,7 @@ impl LinuxTarget { let bp = { self.hit_breakpoint.borrow_mut().take() }; // if we have hit a breakpoint previously if let Some(mut bp) = bp { - let rip = self.read_regs()?.rip as usize; + let rip = self.main_thread()?.read_regs()?.ip() as usize; // if we are not a the right address, we simply set the breakpoint again, and // carry on if (rip == bp.addr) && bp.is_enabled() { @@ -202,16 +228,12 @@ impl LinuxTarget { } /// Reads the register values from the main thread of a debuggee process. - pub fn read_regs(&self) -> CrabResult { - nix::sys::ptrace::getregs(self.pid()).map_err(|err| err.into()) - } - - /// Writes the register values for the main thread of a debuggee process. - pub fn write_regs(&self, regs: libc::user_regs_struct) -> CrabResult<()> { - nix::sys::ptrace::setregs(self.pid(), regs).map_err(|err| err.into()) + pub fn read_regs(&self) -> CrabResult> { + Ok(Box::new(self.main_thread()?.read_regs()?)) } /// Let the debuggee process execute the specified syscall. + #[cfg(target_arch = "x86_64")] pub fn syscall( &self, num: libc::c_ulonglong, @@ -222,39 +244,45 @@ impl LinuxTarget { arg5: libc::c_ulonglong, arg6: libc::c_ulonglong, ) -> CrabResult { + use gimli::X86_64; + // Write arguments - let orig_regs = self.read_regs()?; + let orig_regs = self.main_thread()?.read_regs()?; + let mut new_regs = orig_regs.clone(); - new_regs.rax = num; - new_regs.rdi = arg1; - new_regs.rsi = arg2; - new_regs.rdx = arg3; - new_regs.r10 = arg4; - new_regs.r8 = arg5; - new_regs.r9 = arg6; - self.write_regs(new_regs)?; + + // unwraps are safe because we limit this impl to x86_64 + new_regs.set_reg_for_dwarf(X86_64::RAX, num).unwrap(); + new_regs.set_reg_for_dwarf(X86_64::RDI, arg1).unwrap(); + new_regs.set_reg_for_dwarf(X86_64::RSI, arg2).unwrap(); + new_regs.set_reg_for_dwarf(X86_64::RDX, arg3).unwrap(); + new_regs.set_reg_for_dwarf(X86_64::R10, arg4).unwrap(); + new_regs.set_reg_for_dwarf(X86_64::R8, arg5).unwrap(); + new_regs.set_reg_for_dwarf(X86_64::R9, arg6).unwrap(); + self.main_thread()?.write_regs(new_regs)?; // Write syscall instruction // FIXME search for an existing syscall instruction once instead - let old_inst = nix::sys::ptrace::read(self.pid(), new_regs.rip as *mut _)?; + let old_inst = nix::sys::ptrace::read(self.pid(), new_regs.ip() as *mut _)?; nix::sys::ptrace::write( self.pid(), - new_regs.rip as *mut _, + new_regs.ip() as *mut _, 0x050f/*x86_64 syscall*/ as *mut _, )?; // Perform syscall - ptrace::step(self.pid(), None)?; - waitpid(self.pid(), None)?; + nix::sys::ptrace::step(self.pid(), None)?; + nix::sys::wait::waitpid(self.pid(), None)?; // Read return value - let res = self.read_regs()?.rax; + let regs = self.read_regs()?; + let res = regs.reg_for_dwarf(X86_64::RAX); // Restore old code and registers - nix::sys::ptrace::write(self.pid(), new_regs.rip as *mut _, old_inst as *mut _)?; - self.write_regs(orig_regs)?; + nix::sys::ptrace::write(self.pid(), new_regs.ip() as *mut _, old_inst as *mut _)?; + self.main_thread()?.write_regs(orig_regs)?; - Ok(res) + Ok(res.unwrap()) } /// Let the debuggee process map memory. @@ -305,12 +333,20 @@ impl LinuxTarget { Ok(()) } + /// Returns the main process thread. + pub fn main_thread(&self) -> CrabResult { + match Process::new(self.pid.as_raw())?.task_main_thread() { + Ok(task) => Ok(Box::new(LinuxThread::new(task))), + Err(e) => Err(From::from(e)), + } + } + /// Returns the current snapshot view of this debuggee process threads. - pub fn threads(&self) -> CrabResult>>> { + pub fn threads(&self) -> CrabResult> { let tasks: Vec<_> = Process::new(self.pid.as_raw())? .tasks()? .flatten() - .map(|task| Box::new(LinuxThread::new(task)) as Box>) + .map(|task| Box::new(LinuxThread::new(task)) as _) .collect(); Ok(tasks) @@ -491,9 +527,9 @@ impl LinuxTarget { bp.unset()?; // rollback the P.C - let mut regs = self.read_regs()?; - regs.rip -= 1; - self.write_regs(regs)?; + let mut regs = self.main_thread()?.read_regs()?; + regs.set_ip(regs.ip() - 1); + self.main_thread()?.write_regs(regs)?; Ok(()) } diff --git a/src/target/macos.rs b/src/target/macos.rs index 4fa0b62..adf0c7e 100644 --- a/src/target/macos.rs +++ b/src/target/macos.rs @@ -2,7 +2,7 @@ mod readmem; mod vmmap; mod writemem; -use crate::target::thread::Thread; +use crate::target::{registers::Registers, thread::Thread}; use crate::CrabResult; use libc::pid_t; use mach::{ @@ -43,9 +43,59 @@ extern "C" { pub fn pthread_from_mach_thread_np(port: libc::c_uint) -> libc::pthread_t; } -impl Thread for OSXThread { +// TODO: implement `Registers` properly for macOS x86_64 +impl Registers for () { + fn ip(&self) -> u64 { + todo!() + } + fn set_ip(&mut self, ip: u64) { + todo!() + } + fn sp(&self) -> u64 { + todo!() + } + fn set_sp(&mut self, sp: u64) { + todo!() + } + fn bp(&self) -> Option { + todo!() + } + #[must_use] + fn set_bp(&mut self, bp: u64) -> Option<()> { + todo!() + } + fn reg_for_dwarf(&self, reg: gimli::Register) -> Option { + todo!() + } + #[must_use] + fn set_reg_for_dwarf(&mut self, reg: gimli::Register, val: u64) -> Option<()> { + todo!() + } + fn name_for_dwarf(reg: gimli::Register) -> Option<&'static str> + where + Self: Sized, + { + todo!() + } + fn dwarf_for_name(name: &str) -> Option + where + Self: Sized, + { + todo!() + } +} + +impl Thread<()> for OSXThread { type ThreadId = mach_port_t; + fn read_regs(&self) -> CrabResult<()> { + todo!() + } + + fn write_regs(&self, regs: ()) -> CrabResult<()> { + todo!() + } + fn name(&self) -> CrabResult> { if let Some(pt_id) = self.pthread_id { let mut name = [0 as libc::c_char; MAX_THREAD_NAME]; @@ -175,7 +225,7 @@ impl Target { } /// Returns the current snapshot view of this debuggee process threads. - pub fn threads(&self) -> CrabResult>>> { + pub fn threads(&self) -> CrabResult>>> { let mut threads: mach_types::thread_act_array_t = std::ptr::null_mut(); let mut tcount: mach_msg_type_number_t = 0; @@ -196,7 +246,7 @@ impl Target { port, pthread_id, task_port, - }) as Box>; + }) as Box>; osx_threads.push(thread); } diff --git a/src/target/registers.rs b/src/target/registers.rs new file mode 100644 index 0000000..24d9e9d --- /dev/null +++ b/src/target/registers.rs @@ -0,0 +1,177 @@ +//! Interfaces related to registers reading & writing. + +use std::fmt::Debug; + +/// Trait that can be used to read & write the target's registers. +pub trait Registers: Debug { + /// Returns a current instruction pointer. + fn ip(&self) -> u64; + + /// Sets an instruction pointer to the provided value. + fn set_ip(&mut self, ip: u64); + + /// Returns a current stack pointer. + fn sp(&self) -> u64; + + /// Sets a stack pointer to the provided value. + fn set_sp(&mut self, sp: u64); + + /// Returns a base pointer. + /// Returns `None` if the standard ABI on the platform has no base pointer. + fn bp(&self) -> Option; + + /// Sets a base pointer to the provided value. + /// Returns `None` if the standard ABI on the platform has no base pointer. + #[must_use] + fn set_bp(&mut self, bp: u64) -> Option<()>; + + /// Translates a DWARF register type into a value. + /// See [`gimli::Register`](https://docs.rs/gimli/*/gimli/struct.Register.html) definition for a list of + /// available registers. Returns `None` when a specified register doesn't exist. + fn reg_for_dwarf(&self, reg: gimli::Register) -> Option; + + /// Sets a DWARF register to the provided value. + /// See [`gimli::Register`](https://docs.rs/gimli/*/gimli/struct.Register.html) definition for a list of + /// available registers. Returns `None` when a specified register doesn't exist. + #[must_use] + fn set_reg_for_dwarf(&mut self, reg: gimli::Register, val: u64) -> Option<()>; + + /// Converts a DWARF register type into a lower-case string value (the register name). + /// Returns `None` when a register doesn't exist. + fn name_for_dwarf(reg: gimli::Register) -> Option<&'static str> + where + Self: Sized; + + /// Converts a name of a register into a corresponding DWARF register type. + /// Returns `None` when a register doesn't exist (e.g., a name is invalid). + fn dwarf_for_name(name: &str) -> Option + where + Self: Sized; +} + +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +pub use x86_64::Registers as RegistersX86_64; + +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +mod x86_64 { + use gimli::Register; + // This struct is available only on Linux. + use libc::user_regs_struct; + + #[derive(Copy, Clone, Debug)] + pub struct Registers { + regs: user_regs_struct, + } + + impl From for Registers { + fn from(regs: user_regs_struct) -> Registers { + Registers { regs } + } + } + + impl Into for Registers { + fn into(self) -> user_regs_struct { + self.regs + } + } + + impl super::Registers for Registers { + fn ip(&self) -> u64 { + self.regs.rip + } + + fn sp(&self) -> u64 { + self.regs.rsp + } + + fn bp(&self) -> Option { + Some(self.regs.rbp) + } + + fn set_ip(&mut self, ip: u64) { + self.regs.rip = ip; + } + + fn set_bp(&mut self, bp: u64) -> Option<()> { + self.regs.rbp = bp; + Some(()) + } + + fn set_sp(&mut self, sp: u64) { + self.regs.rsp = sp; + } + + fn set_reg_for_dwarf(&mut self, register: Register, val: u64) -> Option<()> { + use gimli::X86_64; + match register { + X86_64::RAX => self.regs.rax = val, + X86_64::RBX => self.regs.rbx = val, + X86_64::RCX => self.regs.rcx = val, + X86_64::RDX => self.regs.rdx = val, + X86_64::RSI => self.regs.rsi = val, + X86_64::RDI => self.regs.rdi = val, + X86_64::RSP => self.regs.rsp = val, + X86_64::RBP => self.regs.rbp = val, + X86_64::R8 => self.regs.r8 = val, + X86_64::R9 => self.regs.r9 = val, + X86_64::R10 => self.regs.r10 = val, + X86_64::R11 => self.regs.r11 = val, + X86_64::R12 => self.regs.r12 = val, + X86_64::R13 => self.regs.r13 = val, + X86_64::R14 => self.regs.r14 = val, + X86_64::R15 => self.regs.r15 = val, + X86_64::CS => self.regs.cs = val, + X86_64::SS => self.regs.ss = val, + X86_64::DS => self.regs.ds = val, + X86_64::GS => self.regs.gs = val, + X86_64::ES => self.regs.es = val, + X86_64::FS => self.regs.fs = val, + X86_64::FS_BASE => self.regs.fs_base = val, + X86_64::GS_BASE => self.regs.gs_base = val, + X86_64::RFLAGS => self.regs.eflags = val, + reg => unimplemented!("{:?}", reg), // FIXME + } + Some(()) + } + + fn dwarf_for_name(_name: &str) -> Option { + unimplemented!() + } + + fn name_for_dwarf(register: Register) -> Option<&'static str> { + gimli::X86_64::register_name(register) + } + + fn reg_for_dwarf(&self, register: Register) -> Option { + use gimli::X86_64; + match register { + X86_64::RAX => Some(self.regs.rax), + X86_64::RBX => Some(self.regs.rbx), + X86_64::RCX => Some(self.regs.rcx), + X86_64::RDX => Some(self.regs.rdx), + X86_64::RSI => Some(self.regs.rsi), + X86_64::RDI => Some(self.regs.rdi), + X86_64::RSP => Some(self.regs.rsp), + X86_64::RBP => Some(self.regs.rbp), + X86_64::R8 => Some(self.regs.r8), + X86_64::R9 => Some(self.regs.r9), + X86_64::R10 => Some(self.regs.r10), + X86_64::R11 => Some(self.regs.r11), + X86_64::R12 => Some(self.regs.r12), + X86_64::R13 => Some(self.regs.r13), + X86_64::R14 => Some(self.regs.r14), + X86_64::R15 => Some(self.regs.r15), + X86_64::CS => Some(self.regs.cs), + X86_64::SS => Some(self.regs.ss), + X86_64::DS => Some(self.regs.ds), + X86_64::GS => Some(self.regs.gs), + X86_64::ES => Some(self.regs.es), + X86_64::FS => Some(self.regs.fs), + X86_64::FS_BASE => Some(self.regs.fs_base), + X86_64::GS_BASE => Some(self.regs.gs_base), + X86_64::RFLAGS => Some(self.regs.eflags), + reg => unimplemented!("{:?}", reg), // FIXME + } + } + } +} diff --git a/src/target/thread.rs b/src/target/thread.rs index 83ced30..4ec83de 100644 --- a/src/target/thread.rs +++ b/src/target/thread.rs @@ -1,6 +1,21 @@ -pub trait Thread { +use super::registers::Registers; +use crate::CrabResult; + +pub trait Thread +where + Reg: Registers, +{ type ThreadId; - fn name(&self) -> crate::CrabResult>; + /// Return a thread name. + fn name(&self) -> CrabResult>; + + /// Return a thread ID. fn thread_id(&self) -> Self::ThreadId; + + /// Return CPU registers structure for this thread. + fn read_regs(&self) -> CrabResult; + + /// Write CPU registers for this thread. + fn write_regs(&self, registers: Reg) -> CrabResult<()>; } diff --git a/tests/disassemble.rs b/tests/disassemble.rs index 5378ff0..1caaa42 100644 --- a/tests/disassemble.rs +++ b/tests/disassemble.rs @@ -25,7 +25,7 @@ fn disassemble() -> headcrab::CrabResult<()> { // First breakpoint target.unpause()?; - let ip = target.read_regs()?.rip; + let ip = target.read_regs()?.ip(); println!("{:08x}", ip); assert_eq!( debuginfo.get_address_symbol_name(ip as usize).as_deref(), diff --git a/tests/fixed_breakpoint.rs b/tests/fixed_breakpoint.rs index 551bf9e..2c171ad 100644 --- a/tests/fixed_breakpoint.rs +++ b/tests/fixed_breakpoint.rs @@ -28,7 +28,7 @@ fn fixed_breakpoint() -> CrabResult<()> { // First breakpoint target.unpause()?; - let ip = target.read_regs()?.rip; + let ip = target.read_regs()?.ip(); assert_eq!( debuginfo.get_address_symbol_name(ip as usize).as_deref(), Some("main") @@ -36,7 +36,7 @@ fn fixed_breakpoint() -> CrabResult<()> { // Second breakpoint target.unpause()?; - let ip = target.read_regs()?.rip; + let ip = target.read_regs()?.ip(); assert_eq!( debuginfo.get_address_symbol_name(ip as usize).as_deref(), Some("main") diff --git a/tests/read_locals.rs b/tests/read_locals.rs index fc3d706..2d254d9 100644 --- a/tests/read_locals.rs +++ b/tests/read_locals.rs @@ -5,7 +5,7 @@ mod test_utils; #[cfg(target_os = "linux")] use headcrab::{ symbol::{LocalValue, RelocatedDwarf}, - target::UnixTarget, + target::{Registers, UnixTarget}, CrabResult, }; @@ -31,22 +31,22 @@ fn read_locals() -> CrabResult<()> { // Breakpoint test_utils::patch_breakpoint(&target, &debuginfo); target.unpause()?; - let ip = target.read_regs()?.rip; + let ip = target.read_regs()?.ip(); assert_eq!( debuginfo.get_address_symbol_name(ip as usize).as_deref(), Some("breakpoint") ); while debuginfo - .get_address_symbol_name(target.read_regs()?.rip as usize) + .get_address_symbol_name(target.read_regs()?.ip() as usize) .as_deref() == Some("breakpoint") { target.step()?; } - let regs = target.read_regs()?; - let ip = regs.rip; + let regs = target.main_thread()?.read_regs()?; + let ip = regs.ip(); assert!(debuginfo .get_address_symbol_name(ip as usize) .as_deref() @@ -76,7 +76,7 @@ fn read_locals() -> CrabResult<()> { frame_base, &X86_64EvalContext { frame_base: None, - regs, + regs: Box::new(regs), }, )?; assert_eq!(res.len(), 1); @@ -85,14 +85,17 @@ fn read_locals() -> CrabResult<()> { Some(match res[0].location { gimli::Location::Register { register: gimli::X86_64::RBP, - } => regs.rbp, + } => regs.bp().unwrap(), ref loc => unimplemented!("{:?}", loc), // FIXME }) } else { None }; - let eval_ctx = X86_64EvalContext { frame_base, regs }; + let eval_ctx = X86_64EvalContext { + frame_base, + regs: Box::new(regs), + }; frame.each_argument(&eval_ctx, ip as u64, |local| { panic!("Main should not have any arguments, but it has {:?}", local); @@ -142,7 +145,7 @@ fn read_locals() -> CrabResult<()> { #[cfg(target_os = "linux")] struct X86_64EvalContext { frame_base: Option, - regs: libc::user_regs_struct, + regs: Box, } #[cfg(target_os = "linux")] @@ -152,7 +155,12 @@ impl headcrab::symbol::dwarf_utils::EvalContext for X86_64EvalContext { } fn register(&self, register: gimli::Register, base_type: gimli::ValueType) -> gimli::Value { - get_linux_x86_64_reg(self.regs)(register, base_type) + let val = self.regs.reg_for_dwarf(register).unwrap(); + match base_type { + gimli::ValueType::Generic => gimli::Value::Generic(val), + gimli::ValueType::U64 => gimli::Value::U64(val), + _ => unimplemented!(), + } } fn memory( @@ -165,23 +173,3 @@ impl headcrab::symbol::dwarf_utils::EvalContext for X86_64EvalContext { todo!() } } - -#[cfg(target_os = "linux")] -fn get_linux_x86_64_reg( - regs: libc::user_regs_struct, -) -> impl Fn(gimli::Register, gimli::ValueType) -> gimli::Value { - move |reg, ty| { - let val = match reg { - gimli::X86_64::RAX => regs.rax, - gimli::X86_64::RBX => regs.rbx, - gimli::X86_64::RDI => regs.rdi, - gimli::X86_64::RBP => regs.rbp, - reg => unimplemented!("{:?}", reg), // FIXME - }; - match ty { - gimli::ValueType::Generic => gimli::Value::Generic(val), - gimli::ValueType::U64 => gimli::Value::U64(val), - _ => unimplemented!(), - } - } -} diff --git a/tests/readmem.rs b/tests/readmem.rs index e04b456..4354853 100644 --- a/tests/readmem.rs +++ b/tests/readmem.rs @@ -67,7 +67,7 @@ fn read_memory() -> CrabResult<()> { // Wait for the breakpoint to get hit. target.unpause().unwrap(); - let ip = target.read_regs().unwrap().rip; + let ip = target.read_regs().unwrap().ip(); assert_eq!( debuginfo.get_address_symbol_name(ip as usize).as_deref(), Some("breakpoint") diff --git a/tests/readregs.rs b/tests/readregs.rs index a495cb8..6d9aa56 100644 --- a/tests/readregs.rs +++ b/tests/readregs.rs @@ -7,48 +7,54 @@ static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hell #[cfg(all(target_os = "linux", target_arch = "x86_64"))] #[test] fn read_regs() -> headcrab::CrabResult<()> { + use gimli::X86_64; + use headcrab::target::Registers; + test_utils::ensure_testees(); let target = test_utils::launch(BIN_PATH); - let regs = target.read_regs()?; + let regs = target.main_thread()?.read_regs()?; // Assert that the register values match the expected initial values on Linux - assert_eq!(regs.r15, 0); - assert_eq!(regs.r14, 0); - assert_eq!(regs.r13, 0); - assert_eq!(regs.r12, 0); - assert_eq!(regs.rbp, 0); - assert_eq!(regs.rbx, 0); - assert_eq!(regs.r11, 0); - assert_eq!(regs.r10, 0); - assert_eq!(regs.r9, 0); - assert_eq!(regs.r8, 0); - assert_eq!(regs.rax, 0); - assert_eq!(regs.rcx, 0); - assert_eq!(regs.rdx, 0); - assert_eq!(regs.rsi, 0); - assert_eq!(regs.rdi, 0); + assert_eq!(regs.reg_for_dwarf(X86_64::R15).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::R14).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::R13).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::R12).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::RBP).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::RBX).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::R11).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::R10).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::R9).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::R8).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::RAX).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::RCX).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::RDX).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::RSI).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::RDI).unwrap(), 0); // https://github.com/torvalds/linux/blob/f359287765c04711ff54fbd11645271d8e5ff763/arch/x86/entry/syscalls/syscall_64.tbl#L70 + let user_regs: libc::user_regs_struct = regs.into(); + const X86_64_SYSCALL_EXECVE: u64 = 59; - assert_eq!(regs.orig_rax, X86_64_SYSCALL_EXECVE); + assert_eq!(user_regs.orig_rax, X86_64_SYSCALL_EXECVE); //assert_eq!(regs.rip, 140188621074576); // non-deterministic - assert_eq!(regs.cs, 51); + assert_eq!(regs.reg_for_dwarf(X86_64::CS).unwrap(), 51); // IF=EI: interrupt enable flag = enable interrupts const EFLAGS_EI: u64 = 0x0200; - assert_eq!(regs.eflags, EFLAGS_EI); + assert_eq!(regs.reg_for_dwarf(X86_64::RFLAGS).unwrap(), EFLAGS_EI); //assert_eq!(regs.rsp, 140735406980896); // non-deterministic - assert_eq!(regs.ss, 43); - assert_eq!(regs.fs_base, 0); - assert_eq!(regs.gs_base, 0); - assert_eq!(regs.ds, 0); - assert_eq!(regs.es, 0); - assert_eq!(regs.fs, 0); - assert_eq!(regs.gs, 0); + assert_eq!(regs.reg_for_dwarf(X86_64::SS).unwrap(), 43); + assert_eq!(regs.reg_for_dwarf(X86_64::FS_BASE).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::GS_BASE).unwrap(), 0); + + assert_eq!(regs.reg_for_dwarf(X86_64::DS).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::ES).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::FS).unwrap(), 0); + assert_eq!(regs.reg_for_dwarf(X86_64::GS).unwrap(), 0); test_utils::continue_to_end(&target); diff --git a/tests/runtime_breakpoint.rs b/tests/runtime_breakpoint.rs index cfba23b..2597e84 100644 --- a/tests/runtime_breakpoint.rs +++ b/tests/runtime_breakpoint.rs @@ -6,7 +6,11 @@ mod test_utils; #[cfg(target_os = "linux")] -use headcrab::{symbol::RelocatedDwarf, target::UnixTarget, CrabResult}; +use headcrab::{ + symbol::RelocatedDwarf, + target::{Registers, UnixTarget}, + CrabResult, +}; static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hello"); static LOOPING_BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/loop"); @@ -38,7 +42,7 @@ fn runtime_breakpoint() -> CrabResult<()> { // run the program target.unpause()?; // have we hit the breakpoint ? - let ip = target.read_regs()?.rip; + let ip = target.read_regs()?.ip(); assert_eq!(ip as usize, main_addr); let status = target.step()?; assert_eq!(status, test_utils::ws_sigtrap(&target)); @@ -67,18 +71,19 @@ fn multiple_breakpoints() -> CrabResult<()> { // make sure we hit the breakpoint let status = target.unpause()?; assert_eq!(status, test_utils::ws_sigtrap(&target)); - let mut regs = target.read_regs()?; - assert_eq!(regs.rip as usize, main_addr); + + let mut regs = target.main_thread()?.read_regs()?; + assert_eq!(regs.ip() as usize, main_addr); // Let's go a few instructions back and see if disabling the breakpoint works - regs.rip -= 3; + regs.set_ip(regs.ip() - 3); - target.write_regs(regs)?; + target.main_thread()?.write_regs(regs)?; breakpoint2.disable()?; assert!(!breakpoint.is_armed()); - regs.rip += 3; - target.write_regs(regs)?; + regs.set_ip(regs.ip() + 3); + target.main_thread()?.write_regs(regs)?; // Same, let's check that creating a new breakpoint and unsetting it right away // disarms the trap @@ -111,7 +116,7 @@ fn looping_breakpoint() -> CrabResult<()> { assert_eq!(status, test_utils::ws_sigtrap(&target)); let regs = target.read_regs()?; - assert_eq!(regs.rip as usize, bp_addr); + assert_eq!(regs.ip() as usize, bp_addr); assert!(!breakpoint.is_armed()); } test_utils::continue_to_end(&target); diff --git a/tests/source.rs b/tests/source.rs index 8326a21..9a029cd 100644 --- a/tests/source.rs +++ b/tests/source.rs @@ -27,7 +27,7 @@ fn source() -> CrabResult<()> { // First breakpoint target.unpause()?; - let ip = target.read_regs()?.rip; + let ip = target.read_regs()?.ip(); println!("{:08x}", ip); assert_eq!( debuginfo.get_address_symbol_name(ip as usize).as_deref(), diff --git a/tests/test_utils.rs b/tests/test_utils.rs index efab4f2..f6ce8cf 100644 --- a/tests/test_utils.rs +++ b/tests/test_utils.rs @@ -72,7 +72,7 @@ pub fn patch_breakpoint(target: &LinuxTarget, debuginfo: &RelocatedDwarf) { #[cfg(target_os = "linux")] #[allow(dead_code)] pub fn current_ip(target: &LinuxTarget) -> u64 { - target.read_regs().expect("could not read registers").rip + target.read_regs().expect("could not read registers").ip() } #[cfg(target_os = "linux")] diff --git a/tests/unwind_stack.rs b/tests/unwind_stack.rs index 907b947..dc7ef4a 100644 --- a/tests/unwind_stack.rs +++ b/tests/unwind_stack.rs @@ -35,11 +35,11 @@ fn unwind_stack() -> CrabResult<()> { // Read stack let mut stack: [usize; 256] = [0; 256]; unsafe { - target.read().read(&mut stack, regs.rsp as usize).apply()?; + target.read().read(&mut stack, regs.sp() as usize).apply()?; } let call_stack: Vec<_> = - headcrab::symbol::unwind::naive_unwinder(&debuginfo, &stack[..], regs.rip as usize) + headcrab::symbol::unwind::naive_unwinder(&debuginfo, &stack[..], regs.ip() as usize) .map(|func| { debuginfo .get_address_symbol_name(func) @@ -54,9 +54,9 @@ fn unwind_stack() -> CrabResult<()> { let call_stack: Vec<_> = headcrab::symbol::unwind::frame_pointer_unwinder( &debuginfo, &stack[..], - regs.rip as usize, - regs.rsp as usize, - regs.rbp as usize, + regs.ip() as usize, + regs.sp() as usize, + regs.bp().unwrap() as usize, ) .map(|func| { debuginfo