use crate::cli::Args; use crate::registers::RegistersData; use byteorder::{LittleEndian, WriteBytesExt}; use nix::{ sys::{ ptrace, signal::Signal, wait::{waitpid, WaitStatus}, }, unistd::Pid, }; use std::{ fs::File, io::{self, Write}, os::{raw::c_void, unix::process::CommandExt}, process::{Command, Stdio}, str, }; /// Exec the `command` value tracing it with `ptrace` lib pub fn exec(command: &str) -> anyhow::Result<()> { let params: Vec<&str> = command.split(' ').collect(); let mut command = Command::new(params[0]); command.args(params[1..].iter()); command.stdout(Stdio::null()); unsafe { command.pre_exec(|| ptrace::traceme().map_err(|e| e.into())); } command.exec(); Ok(()) } /// Attach a ptrace status to a `pid` pub fn attach(pid: Pid) -> anyhow::Result<()> { ptrace::attach(pid)?; Ok(()) } /// Trace a process with `pid` ID and returns a list of `RegistersData` pub fn trace(pid: Pid, args: &Args) -> anyhow::Result> { // First wait for the parent process _ = waitpid(pid, None)?; // FIXME: file writing on attachment // If `file_to_print` is not None, create a new file with that value for redirecting all the // output (also in stdout) let mut f = None; if let Some(filename) = &args.file_to_print { f = Some(File::create(filename)?); } let mut lines: Vec = Vec::new(); // Since you have to do 2 syscalls (start and end) you have to alternate the print value, // because it could be equals except for the `rax` register. let mut have_to_print = true; let filters: Vec<&str> = match &args.filter { Some(filter) => filter.split(',').collect::>(), None => vec![], }; while let Some(mut reg) = trace_next(pid)? { have_to_print ^= true; if have_to_print { if !filters.is_empty() && !filters.contains(®.name()) { continue; } if let Some(ref mut f) = f { writeln!(f, "{}", reg.output(pid))?; } if args.no_tui { writeln!(io::stdout(), "{}", reg.output(pid))?; } lines.push(reg); } } Ok(lines) } /// Read memory and returns a string. /// Thank you https://github.com/JakWai01/lurk/blob/e3a3d6c026bbf818fe1329f8d458be544c3c5ebc/src/arch/mod.rs#L66 pub fn read_memory(pid: Pid, address: u64) -> String { let mut string = String::new(); let mut count = 0; let word_size = 8; loop { let address = unsafe { (address as *mut c_void).offset(count) }; match ptrace::read(pid, address) { Ok(read) => { let mut bytes: Vec = vec![]; bytes.write_i64::(read).unwrap_or_else(|err| { panic!("Failed to write {read} as i64 LittleEndian: {err}"); }); if !bytes .iter() .filter(|&b| *b == 0x0) .collect::>() .is_empty() { break; } bytes.iter().for_each(|b| { string.push(*b as char); }); count += word_size; } Err(_) => break, }; } if string.len() > 24 { string = string[..24].to_string(); string.push_str("..."); } string = string.replace('\n', "\\n"); format!("\"{string}\"") } /// Get the next step for a ptrace process pub fn trace_next(pid: Pid) -> anyhow::Result> { ptrace::syscall(pid, None)?; let status = waitpid(pid, None).unwrap(); match status { // Match the stopped value for a process WaitStatus::Stopped(pid, signal) => { match signal { Signal::SIGTRAP => { let reg = RegistersData::new(ptrace::getregs(pid)?); return Ok(Some(reg)); } _ => {} }; } _ => {} }; Ok(None) } /// Kill a process traced by ptrace pub fn trace_kill(pid: Pid) -> anyhow::Result<()> { let _ = ptrace::kill(pid); Ok(()) }