diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 336 | ||||
-rw-r--r-- | Cargo.toml | 27 | ||||
-rw-r--r-- | README.md | 63 | ||||
-rw-r--r-- | src/main.rs | 396 |
5 files changed, 823 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1a26dff --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,336 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", + "static_assertions", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaxpeax-arch" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ba5c2f163fa2f866c36750c6c931566c6d93231ae9410083b0738953b609d5" +dependencies = [ + "num-traits", +] + +[[package]] +name = "yaxpeax-eval" +version = "0.0.1" +dependencies = [ + "clap", + "hex", + "itertools", + "libc", + "nix", + "num-traits", + "yaxpeax-arch", + "yaxpeax-x86", +] + +[[package]] +name = "yaxpeax-x86" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934a0186cc9f96af563264382d03946c95d8393e8e03f18cbbadd2efa8830b53" +dependencies = [ + "cfg-if", + "num-traits", + "yaxpeax-arch", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..98079c9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "yaxpeax-eval" +version = "0.0.1" +authors = ["iximeow <me@iximeow.net>"] +license = "0BSD" +edition = "2021" +keywords = ["disassembly", "disassembler"] +repository = "https://git.iximeow.net/yaxpeax-eval/about/" +description = "batch eval tool for machine code" +readme = "README.md" + +[[bin]] +name = "yaxeval" +path = "src/main.rs" + +[dependencies] +nix = { version = "0.26.1", features = ["mman", "process", "ptrace"] } +clap = { version = "3", features = ["derive"] } +hex = "0.4.0" +num-traits = "0.2.10" +itertools = "0.10.1" +libc = "0.2.139" + +# common interfaces for all yaxpeax decoders +yaxpeax-arch = { version = "0.2.4" , default-features = false, features = ["std"] } + +yaxpeax-x86 = { version = "1.1.5", default-features = false, features = ["fmt", "std"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..51a750e --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +## yaxpeax-eval + +[![crate](https://img.shields.io/crates/v/yaxpeax-eval.svg?logo=rust)](https://crates.io/crates/yaxpeax-eval) + +`yaxpeax-eval` is the repo providing `yaxeval`, a tool to execute machine code with preconditions and report state at exit. + +currently, `yaxeval` works by spawning a thread and executing the provided machine code on the local physical processor. there is some boring glue for architecture-dependent state setting and reporting. this means that `yaxeval` supports, or is close to supporting, whatever physical processor you would run it on. + +i am interested in using qemu-user as an alternate execution backend for cross-platform emulation. `yaxeval` should be able to use qemu-user just the same for setup and reporting by using qemu's gdbserver. + +## usage + +if you just want to build and use it, `cargo install yaxpeax-eval` should get you started. otherwise, clone this repo and a `cargo build` will work as well. `yaxeval <x86 machine code>` is a good starting point: + +``` +yaxpeax-eval> ./target/release/yaxeval b878563412 +loaded code... + 00007f774b497000: mov eax, 0x12345678 + 00007f774b497005: 🏁 (int 0x3) +running... + rax: 0000000000000000 + to -> 0000000012345678 + rip: 00007f774b497000 + to -> 00007f774b497006 +``` + +initial register state is generally zeroes, with exception of `rip`, which by default points to whatever address an unrestricted `mmap` could find. + +inital register values, including `rip`, can be specified explicitly: + +``` +yaxpeax-eval> ./target/release/yaxeval --regs rax=4,rcx=5,rip=0x123456789a,eflags=0x246 03c133c9 +loaded code... + 000000123456789a: add eax, ecx + 000000123456789c: xor ecx, ecx + 000000123456789e: 🏁 (int 0x3) +running... + rax: 0000000000000004 + to -> 0000000000000009 + rcx: 0000000000000005 + to -> 0000000000000000 + rip: 000000123456789a + to -> 000000123456789f +``` + +and if the provided code disastrously crashes, `yaxeval` will try to say a bit about what occurred: + +``` +yaxpeax-eval> ./target/release/yaxeval --regs rax=4,rcx=5,rip=0x123456789a,eflags=0x246 0000 +loaded code... + 000000123456789a: add byte [rax], al + 000000123456789c: 🏁 (int 0x3) +running... + eflags: 00000246 + to -> 00010246 +sigsegv at unexpected address: 000000123456789a +``` + +## aspirations + +* accept some config to map memory regions other than the implicitly-initialized code region +* machine-friendly input/output formats +* mode to single-step through provided code? 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 + } +} |