├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── goodfile └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "1.3.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 27 | 28 | [[package]] 29 | name = "cfg-if" 30 | version = "1.0.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 33 | 34 | [[package]] 35 | name = "clap" 36 | version = "3.2.23" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" 39 | dependencies = [ 40 | "atty", 41 | "bitflags", 42 | "clap_derive", 43 | "clap_lex", 44 | "indexmap", 45 | "once_cell", 46 | "strsim", 47 | "termcolor", 48 | "textwrap", 49 | ] 50 | 51 | [[package]] 52 | name = "clap_derive" 53 | version = "3.2.18" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" 56 | dependencies = [ 57 | "heck", 58 | "proc-macro-error", 59 | "proc-macro2", 60 | "quote", 61 | "syn", 62 | ] 63 | 64 | [[package]] 65 | name = "clap_lex" 66 | version = "0.2.4" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 69 | dependencies = [ 70 | "os_str_bytes", 71 | ] 72 | 73 | [[package]] 74 | name = "either" 75 | version = "1.8.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 78 | 79 | [[package]] 80 | name = "hashbrown" 81 | version = "0.12.3" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 84 | 85 | [[package]] 86 | name = "heck" 87 | version = "0.4.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 90 | 91 | [[package]] 92 | name = "hermit-abi" 93 | version = "0.1.19" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 96 | dependencies = [ 97 | "libc", 98 | ] 99 | 100 | [[package]] 101 | name = "hex" 102 | version = "0.4.3" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 105 | 106 | [[package]] 107 | name = "indexmap" 108 | version = "1.9.2" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 111 | dependencies = [ 112 | "autocfg", 113 | "hashbrown", 114 | ] 115 | 116 | [[package]] 117 | name = "itertools" 118 | version = "0.10.5" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 121 | dependencies = [ 122 | "either", 123 | ] 124 | 125 | [[package]] 126 | name = "libc" 127 | version = "0.2.139" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 130 | 131 | [[package]] 132 | name = "memoffset" 133 | version = "0.7.1" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 136 | dependencies = [ 137 | "autocfg", 138 | ] 139 | 140 | [[package]] 141 | name = "nix" 142 | version = "0.26.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" 145 | dependencies = [ 146 | "bitflags", 147 | "cfg-if", 148 | "libc", 149 | "memoffset", 150 | "pin-utils", 151 | "static_assertions", 152 | ] 153 | 154 | [[package]] 155 | name = "num-traits" 156 | version = "0.2.15" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 159 | dependencies = [ 160 | "autocfg", 161 | ] 162 | 163 | [[package]] 164 | name = "once_cell" 165 | version = "1.17.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 168 | 169 | [[package]] 170 | name = "os_str_bytes" 171 | version = "6.4.1" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 174 | 175 | [[package]] 176 | name = "pin-utils" 177 | version = "0.1.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 180 | 181 | [[package]] 182 | name = "proc-macro-error" 183 | version = "1.0.4" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 186 | dependencies = [ 187 | "proc-macro-error-attr", 188 | "proc-macro2", 189 | "quote", 190 | "syn", 191 | "version_check", 192 | ] 193 | 194 | [[package]] 195 | name = "proc-macro-error-attr" 196 | version = "1.0.4" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 199 | dependencies = [ 200 | "proc-macro2", 201 | "quote", 202 | "version_check", 203 | ] 204 | 205 | [[package]] 206 | name = "proc-macro2" 207 | version = "1.0.49" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" 210 | dependencies = [ 211 | "unicode-ident", 212 | ] 213 | 214 | [[package]] 215 | name = "quote" 216 | version = "1.0.23" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 219 | dependencies = [ 220 | "proc-macro2", 221 | ] 222 | 223 | [[package]] 224 | name = "static_assertions" 225 | version = "1.1.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 228 | 229 | [[package]] 230 | name = "strsim" 231 | version = "0.10.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 234 | 235 | [[package]] 236 | name = "syn" 237 | version = "1.0.107" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 240 | dependencies = [ 241 | "proc-macro2", 242 | "quote", 243 | "unicode-ident", 244 | ] 245 | 246 | [[package]] 247 | name = "termcolor" 248 | version = "1.1.3" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 251 | dependencies = [ 252 | "winapi-util", 253 | ] 254 | 255 | [[package]] 256 | name = "textwrap" 257 | version = "0.16.0" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" 260 | 261 | [[package]] 262 | name = "unicode-ident" 263 | version = "1.0.6" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 266 | 267 | [[package]] 268 | name = "version_check" 269 | version = "0.9.4" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 272 | 273 | [[package]] 274 | name = "winapi" 275 | version = "0.3.9" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 278 | dependencies = [ 279 | "winapi-i686-pc-windows-gnu", 280 | "winapi-x86_64-pc-windows-gnu", 281 | ] 282 | 283 | [[package]] 284 | name = "winapi-i686-pc-windows-gnu" 285 | version = "0.4.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 288 | 289 | [[package]] 290 | name = "winapi-util" 291 | version = "0.1.5" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 294 | dependencies = [ 295 | "winapi", 296 | ] 297 | 298 | [[package]] 299 | name = "winapi-x86_64-pc-windows-gnu" 300 | version = "0.4.0" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 303 | 304 | [[package]] 305 | name = "yaxpeax-arch" 306 | version = "0.2.7" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "f1ba5c2f163fa2f866c36750c6c931566c6d93231ae9410083b0738953b609d5" 309 | dependencies = [ 310 | "num-traits", 311 | ] 312 | 313 | [[package]] 314 | name = "yaxpeax-eval" 315 | version = "0.0.1" 316 | dependencies = [ 317 | "clap", 318 | "hex", 319 | "itertools", 320 | "libc", 321 | "nix", 322 | "num-traits", 323 | "yaxpeax-arch", 324 | "yaxpeax-x86", 325 | ] 326 | 327 | [[package]] 328 | name = "yaxpeax-x86" 329 | version = "1.1.5" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "934a0186cc9f96af563264382d03946c95d8393e8e03f18cbbadd2efa8830b53" 332 | dependencies = [ 333 | "cfg-if", 334 | "num-traits", 335 | "yaxpeax-arch", 336 | ] 337 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yaxpeax-eval" 3 | version = "0.0.1" 4 | authors = ["iximeow "] 5 | license = "0BSD" 6 | edition = "2021" 7 | keywords = ["disassembly", "disassembler"] 8 | repository = "https://git.iximeow.net/yaxpeax-eval/about/" 9 | description = "batch eval tool for machine code" 10 | readme = "README.md" 11 | 12 | [[bin]] 13 | name = "yaxeval" 14 | path = "src/main.rs" 15 | 16 | [dependencies] 17 | nix = { version = "0.26.1", features = ["mman", "process", "ptrace"] } 18 | clap = { version = "3", features = ["derive"] } 19 | hex = "0.4.0" 20 | num-traits = "0.2.10" 21 | itertools = "0.10.1" 22 | libc = "0.2.139" 23 | 24 | # common interfaces for all yaxpeax decoders 25 | yaxpeax-arch = { version = "0.2.4" , default-features = false, features = ["std"] } 26 | 27 | yaxpeax-x86 = { version = "1.1.5", default-features = false, features = ["fmt", "std"] } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## yaxpeax-eval 2 | 3 | [![crate](https://img.shields.io/crates/v/yaxpeax-eval.svg?logo=rust)](https://crates.io/crates/yaxpeax-eval) 4 | 5 | `yaxpeax-eval` is the repo providing `yaxeval`, a tool to execute machine code with preconditions and report state at exit. 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | ## usage 12 | 13 | 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 ` is a good starting point: 14 | 15 | ``` 16 | yaxpeax-eval> ./target/release/yaxeval b878563412 17 | loaded code... 18 | 00007f774b497000: mov eax, 0x12345678 19 | 00007f774b497005: 🏁 (int 0x3) 20 | running... 21 | rax: 0000000000000000 22 | to -> 0000000012345678 23 | rip: 00007f774b497000 24 | to -> 00007f774b497006 25 | ``` 26 | 27 | initial register state is generally zeroes, with exception of `rip`, which by default points to whatever address an unrestricted `mmap` could find. 28 | 29 | inital register values, including `rip`, can be specified explicitly: 30 | 31 | ``` 32 | yaxpeax-eval> ./target/release/yaxeval --regs rax=4,rcx=5,rip=0x123456789a,eflags=0x246 03c133c9 33 | loaded code... 34 | 000000123456789a: add eax, ecx 35 | 000000123456789c: xor ecx, ecx 36 | 000000123456789e: 🏁 (int 0x3) 37 | running... 38 | rax: 0000000000000004 39 | to -> 0000000000000009 40 | rcx: 0000000000000005 41 | to -> 0000000000000000 42 | rip: 000000123456789a 43 | to -> 000000123456789f 44 | ``` 45 | 46 | and if the provided code disastrously crashes, `yaxeval` will try to say a bit about what occurred: 47 | 48 | ``` 49 | yaxpeax-eval> ./target/release/yaxeval --regs rax=4,rcx=5,rip=0x123456789a,eflags=0x246 0000 50 | loaded code... 51 | 000000123456789a: add byte [rax], al 52 | 000000123456789c: 🏁 (int 0x3) 53 | running... 54 | eflags: 00000246 55 | to -> 00010246 56 | sigsegv at unexpected address: 000000123456789a 57 | ``` 58 | 59 | ## aspirations 60 | 61 | * accept some config to map memory regions other than the implicitly-initialized code region 62 | * machine-friendly input/output formats 63 | * mode to single-step through provided code? 64 | -------------------------------------------------------------------------------- /goodfile: -------------------------------------------------------------------------------- 1 | -- build/test/evaluation via https://git.iximeow.net/build-o-tron 2 | 3 | if not Build.environment.has("rustup") 4 | then 5 | Build.error("i don't know i want to handle dependencies yet") 6 | end 7 | 8 | Build.run({"cargo", "build"}, {step="build"}) 9 | Build.run({"cargo", "test"}, {step="test", name="test stdlib/fmt"}) 10 | Build.run({"cargo", "run", "--", "--regs", "rax=4,rcx=5,rip=0x123456789a,eflags=0x206", "03c133c9"}) 11 | 12 | -- how long does it take to actually run this thing? 13 | bench_start = Build.now_ms() 14 | 15 | Build.run({"./yaxeval", "--regs", "rax=4,rcx=5,rip=0x123456789a,eflags=0x206", "03c133c9"}, {cwd="target/debug"}) 16 | 17 | bench_end = Build.now_ms() 18 | Build.metric("simple runtime (ms)", bench_end - bench_start) 19 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use libc::pid_t; 3 | use nix::sys::mman::{MapFlags, ProtFlags}; 4 | use nix::sys::ptrace::Request::*; 5 | use nix::unistd::ForkResult; 6 | use std::ffi::c_void; 7 | use std::fs; 8 | use std::num::NonZeroUsize; 9 | use std::path::PathBuf; 10 | use std::sync::atomic::{AtomicBool, Ordering}; 11 | use yaxpeax_arch::LengthedInstruction; 12 | 13 | #[derive(Parser)] 14 | #[clap(about, version, author)] 15 | struct Args { 16 | /// file of bytes to execute 17 | #[clap(short, long, parse(from_os_str), conflicts_with = "code")] 18 | file: Option, 19 | 20 | /// hex bytes to execute. for example, try `33c0` 21 | #[clap(required_unless_present = "file")] 22 | code: Option, 23 | 24 | /// initial register state. registers not specified here may be initialized to random values. 25 | #[clap(short, long)] 26 | regs: Option, 27 | } 28 | 29 | fn parse_number(v: &str) -> Result { 30 | let res = if v.starts_with("0x") { 31 | u64::from_str_radix(&v[2..], 16) 32 | } else if v.starts_with("0b") { 33 | u64::from_str_radix(&v[2..], 2) 34 | } else if v.starts_with("0o") { 35 | u64::from_str_radix(&v[2..], 8) 36 | } else { 37 | u64::from_str_radix(v, 10) 38 | }; 39 | 40 | res.map_err(|e| format!("{}", e)) 41 | } 42 | 43 | fn main() { 44 | let args = Args::parse(); 45 | 46 | let buf: Vec = match args.code { 47 | Some(code) => match hex::decode(code) { 48 | Ok(buf) => buf, 49 | Err(e) => { 50 | eprintln!("invalid input, {}. expected a sequence of bytes as hex", e); 51 | return; 52 | } 53 | }, 54 | None => { 55 | let name = args.file.unwrap(); 56 | match fs::read(&name) { 57 | Ok(v) => v, 58 | Err(e) => { 59 | eprintln!("error reading {}: {}", name.display(), e); 60 | return; 61 | } 62 | } 63 | } 64 | }; 65 | 66 | let initial_rip: Option = args.regs.as_ref() 67 | .and_then(|regs| regs.split(",").find(|x| x.starts_with("rip="))) 68 | .map(|assign| { 69 | match assign.split("=").collect::>().as_slice() { 70 | ["rip", value] => parse_number(value), 71 | [_other, _value] => Err(format!("found `rip=` but then string didn't start with `rip=`?")), 72 | other => Err(format!("string was not a simple reg=value format: {:?}", other)) 73 | } 74 | }) 75 | .transpose() 76 | .unwrap(); 77 | 78 | // implement `initial_rip`, if present, as follows: 79 | // `initial_rip / 4096` is implemented by an mmap base address, 80 | // `initial_rip % 4096` is implemented by offsetting in that mmap ("before" initial_rip is 81 | // implicitly all 0) 82 | let required_len = initial_rip.map(|x| x as usize & 0xfff).unwrap_or(0) + buf.len() + 1; 83 | let rounded_len = (required_len + 0xfff) & !0xfff; 84 | 85 | let sync: *const AtomicBool = { 86 | let shared_ptr = unsafe { 87 | nix::sys::mman::mmap( 88 | None, 89 | NonZeroUsize::new(4096).unwrap(), 90 | ProtFlags::PROT_EXEC | ProtFlags::PROT_WRITE | ProtFlags::PROT_READ, 91 | MapFlags::MAP_SHARED | MapFlags::MAP_ANON, 92 | 0, 93 | 0, 94 | ).unwrap() 95 | }; 96 | let ptr = shared_ptr as *mut AtomicBool; 97 | unsafe { std::ptr::write(ptr, AtomicBool::new(false)) }; 98 | ptr as *const AtomicBool 99 | }; 100 | 101 | let mut map_flags = MapFlags::MAP_SHARED | MapFlags::MAP_ANON; 102 | if initial_rip.is_some() { 103 | map_flags |= MapFlags::MAP_FIXED; 104 | } 105 | 106 | let code = unsafe { 107 | nix::sys::mman::mmap( 108 | initial_rip.map(|x| NonZeroUsize::new(x as usize - (x as usize % 0x1000)).unwrap()), 109 | NonZeroUsize::new(rounded_len).unwrap(), 110 | ProtFlags::PROT_EXEC | ProtFlags::PROT_WRITE | ProtFlags::PROT_READ, 111 | map_flags, 112 | 0, 113 | 0, 114 | ).unwrap() 115 | }; 116 | 117 | let initial_rip = (code as u64) + initial_rip.map(|x| x & 0xfff).unwrap_or(0); 118 | 119 | let slice: &mut [u8] = unsafe { 120 | std::slice::from_raw_parts_mut( 121 | initial_rip as *mut u8, 122 | required_len 123 | ) 124 | }; 125 | (&mut slice[..buf.len()]).copy_from_slice(buf.as_slice()); 126 | slice[buf.len()] = 0xcc; 127 | 128 | println!("loaded code..."); 129 | let mut offset = 0; 130 | while offset < buf.len() + 1 { 131 | match yaxpeax_x86::long_mode::InstDecoder::default().decode_slice(&slice[offset..]) { 132 | Ok(inst) => { 133 | if offset < buf.len() { 134 | println!(" {:016x}: {}", offset + initial_rip as usize, inst); 135 | } else { 136 | println!(" {:016x}: 🏁 ({})", offset + initial_rip as usize, inst); 137 | } 138 | offset += inst.len().to_const() as usize 139 | } 140 | Err(e) => { 141 | println!(" {:016x}: {}", offset + initial_rip as usize, e); 142 | println!(" (offset {:x})", offset); 143 | break; 144 | } 145 | } 146 | } 147 | println!("running..."); 148 | 149 | let target = match unsafe { nix::unistd::fork().unwrap() } { 150 | ForkResult::Parent { child } => PtraceEvalTarget::attach(child.as_raw(), sync), 151 | ForkResult::Child => unsafe { setup(sync) }, 152 | }; 153 | 154 | unsafe { 155 | target.clear_regs(); 156 | if let Some(regs) = args.regs.as_ref() { 157 | target.apply_regs(regs); 158 | } 159 | target.set_rip(initial_rip); 160 | 161 | let regs = target.get_regs(); 162 | let status = target.run(); 163 | // println!("status: {}", status); 164 | 165 | let exit_regs = target.get_regs(); 166 | 167 | print_diff(®s, &exit_regs); 168 | 169 | if status & 0xff == 0x7f { 170 | let signal = status >> 8; 171 | if signal == libc::SIGTRAP { 172 | if exit_regs.rip == (initial_rip + buf.len() as u64 + 1) { 173 | // code completed normally 174 | } else { 175 | println!("sigtrap at atypical address: {:016x}", exit_regs.rip); 176 | std::process::exit(1); 177 | } 178 | } else if signal == libc::SIGSEGV { 179 | println!("sigsegv at unexpected address: {:016x}", exit_regs.rip); 180 | std::process::exit(1); 181 | } else { 182 | println!("signal {} at unexpected address: {:016x}", signal, exit_regs.rip); 183 | } 184 | } else if status <= 255 { 185 | println!("exited with signal: {}", status & 0xff); 186 | std::process::exit(1); 187 | } else { 188 | println!("unknown stop status? {}", status); 189 | std::process::exit(1); 190 | } 191 | } 192 | } 193 | 194 | fn print_diff(from: &libc::user_regs_struct, to: &libc::user_regs_struct) { 195 | if from.rax != to.rax { 196 | println!(" rax: {:016x}", from.rax); 197 | println!(" to -> {:016x}", to.rax); 198 | } 199 | if from.rcx != to.rcx { 200 | println!(" rcx: {:016x}", from.rcx); 201 | println!(" to -> {:016x}", to.rcx); 202 | } 203 | if from.rdx != to.rdx { 204 | println!(" rdx: {:016x}", from.rdx); 205 | println!(" to -> {:016x}", to.rdx); 206 | } 207 | if from.rbx != to.rbx { 208 | println!(" rbx: {:016x}", from.rbx); 209 | println!(" to -> {:016x}", to.rbx); 210 | } 211 | if from.rbp != to.rbp { 212 | println!(" rbp: {:016x}", from.rbp); 213 | println!(" to -> {:016x}", to.rbp); 214 | } 215 | if from.rsp != to.rsp { 216 | println!(" rsp: {:016x}", from.rsp); 217 | println!(" to -> {:016x}", to.rsp); 218 | } 219 | if from.rsi != to.rsi { 220 | println!(" rsi: {:016x}", from.rsi); 221 | println!(" to -> {:016x}", to.rsi); 222 | } 223 | if from.rdi != to.rdi { 224 | println!(" rdi: {:016x}", from.rdi); 225 | println!(" to -> {:016x}", to.rdi); 226 | } 227 | if from.r8 != to.r8 { 228 | println!(" r8: {:016x}", from.r8); 229 | println!(" to -> {:016x}", to.r8); 230 | } 231 | if from.r9 != to.r9 { 232 | println!(" r9: {:016x}", from.r9); 233 | println!(" to -> {:016x}", to.r9); 234 | } 235 | if from.r10 != to.r10 { 236 | println!(" r10: {:016x}", from.r10); 237 | println!(" to -> {:016x}", to.r10); 238 | } 239 | if from.r11 != to.r11 { 240 | println!(" r11: {:016x}", from.r11); 241 | println!(" to -> {:016x}", to.r11); 242 | } 243 | if from.r12 != to.r12 { 244 | println!(" r12: {:016x}", from.r12); 245 | println!(" to -> {:016x}", to.r12); 246 | } 247 | if from.r13 != to.r13 { 248 | println!(" r13: {:016x}", from.r13); 249 | println!(" to -> {:016x}", to.r13); 250 | } 251 | if from.r14 != to.r14 { 252 | println!(" r14: {:016x}", from.r14); 253 | println!(" to -> {:016x}", to.r14); 254 | } 255 | if from.r15 != to.r15 { 256 | println!(" r15: {:016x}", from.r15); 257 | println!(" to -> {:016x}", to.r15); 258 | } 259 | if from.rip != to.rip { 260 | println!(" rip: {:016x}", from.rip); 261 | println!(" to -> {:016x}", to.rip); 262 | } 263 | if from.eflags != to.eflags { 264 | println!(" eflags: {:08x}", from.eflags); 265 | println!(" to -> {:08x}", to.eflags); 266 | } 267 | } 268 | 269 | unsafe fn setup(sync: *const AtomicBool) -> ! { 270 | assert_eq!(libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL), 0); 271 | sync.as_ref().unwrap().store(true, Ordering::SeqCst); 272 | loop {} 273 | } 274 | 275 | struct PtraceEvalTarget { 276 | pid: pid_t, 277 | } 278 | 279 | impl PtraceEvalTarget { 280 | fn attach(pid: pid_t, sync: *const AtomicBool) -> Self { 281 | // wait until the child process signals it's ready to be attached to. this solves a small 282 | // race where if we attach and die before the child sets `prctl(PR_SET_PDEATHSIG)`, the 283 | // child process can become an orphan. the default config leaves the child process in an 284 | // infinite loop in such a case. 285 | let syncref = unsafe { sync.as_ref().unwrap() }; 286 | while !syncref.load(Ordering::SeqCst) { 287 | std::thread::sleep(std::time::Duration::from_millis(10)); 288 | } 289 | if unsafe { libc::ptrace(PTRACE_ATTACH as u32, pid) } != 0 { 290 | panic!("ptrace: {}", nix::errno::errno()); 291 | } 292 | if unsafe { libc::waitpid(pid, std::ptr::null_mut(), 0) } == -1 { 293 | panic!("waitpid: {}", nix::errno::errno()); 294 | } 295 | 296 | Self { pid } 297 | } 298 | 299 | unsafe fn apply_regs(&self, regs: &str) { 300 | let mut state = self.get_regs(); 301 | 302 | let parts = regs.split(","); 303 | for part in parts { 304 | let kv = part.split("=").collect::>(); 305 | match kv.as_slice() { 306 | [reg, value] => { 307 | let value: u64 = parse_number(value).unwrap(); 308 | match *reg { 309 | "rax" => { state.rax = value }, 310 | "rcx" => { state.rcx = value }, 311 | "rdx" => { state.rdx = value }, 312 | "rbx" => { state.rbx = value }, 313 | "rbp" => { state.rbp = value }, 314 | "rsp" => { state.rsp = value }, 315 | "rsi" => { state.rsi = value }, 316 | "rdi" => { state.rdi = value }, 317 | "r8" => { state.r8 = value }, 318 | "r9" => { state.r9 = value }, 319 | "r10" => { state.r10 = value }, 320 | "r11" => { state.r11 = value }, 321 | "r12" => { state.r12 = value }, 322 | "r13" => { state.r13 = value }, 323 | "r14" => { state.r14 = value }, 324 | "r15" => { state.r15 = value }, 325 | "eflags" => { state.eflags = value }, 326 | "rip" => { /* handled elsewhere */ }, 327 | other => { panic!("unknown register {}", other) } 328 | } 329 | }, 330 | other => { 331 | eprintln!("register assignment was not of the form `regname=value`? {:?}", other); 332 | } 333 | } 334 | } 335 | 336 | self.set_regs(&mut state); 337 | } 338 | 339 | unsafe fn clear_regs(&self) { 340 | let mut regs = self.get_regs(); 341 | regs.rax = 0; 342 | regs.rcx = 0; 343 | regs.rdx = 0; 344 | regs.rbx = 0; 345 | regs.rbp = 0; 346 | regs.rsp = 0; 347 | regs.rsi = 0; 348 | regs.rdi = 0; 349 | regs.r8 = 0; 350 | regs.r9 = 0; 351 | regs.r10 = 0; 352 | regs.r11 = 0; 353 | regs.r12 = 0; 354 | regs.r13 = 0; 355 | regs.r14 = 0; 356 | regs.r15 = 0; 357 | regs.eflags = 0; 358 | self.set_regs(&mut regs); 359 | } 360 | 361 | unsafe fn get_regs(&self) -> libc::user_regs_struct { 362 | let mut regs: libc::user_regs_struct = 363 | std::mem::transmute([0u8; std::mem::size_of::()]); 364 | 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 { 365 | panic!("ptrace(getregs): {}", nix::errno::errno()); 366 | } 367 | 368 | regs 369 | } 370 | 371 | unsafe fn set_regs(&self, regs: &mut libc::user_regs_struct) { 372 | if libc::ptrace(PTRACE_SETREGS as u32, self.pid, std::ptr::null_mut::<*const c_void>(), regs as *mut libc::user_regs_struct) != 0 { 373 | panic!("ptrace(setregs): {}", nix::errno::errno()); 374 | } 375 | } 376 | 377 | unsafe fn set_rip(&self, rip: u64) { 378 | let mut regs = self.get_regs(); 379 | regs.rip = rip; 380 | 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 { 381 | panic!("ptrace(setregs): {}", nix::errno::errno()); 382 | } 383 | } 384 | 385 | unsafe fn run(&self) -> i32 { 386 | if libc::ptrace(PTRACE_CONT as u32, self.pid, 0, 0) != 0 { 387 | panic!("ptrace(cont): {}", nix::errno::errno()); 388 | } 389 | 390 | let mut status = 0; 391 | if libc::waitpid(self.pid, &mut status as *mut i32, 0) == -1 { 392 | panic!("waitpid: {}", nix::errno::errno()); 393 | } 394 | status 395 | } 396 | } 397 | --------------------------------------------------------------------------------