From e9d96712b2b1e404d7d5e1496189d5fb2f50f1bb Mon Sep 17 00:00:00 2001 From: Audrey Dutcher Date: Thu, 11 Apr 2024 15:29:18 -0700 Subject: [PATCH] wip --- .gitignore | 1 + Cargo.lock | 63 ++++++++++++++++ Cargo.toml | 10 +++ src/main.rs | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 281 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs 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..b946c78 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,63 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-personality" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1a976577555db53f0a98e9b236e1ebff1a0a1793227ed1b88d7b9a03dda93" +dependencies = [ + "bitflags 0.7.0", + "libc", +] + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "ontology" +version = "0.1.0" +dependencies = [ + "linux-personality", + "nix", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..140662d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "ontology" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nix = { version = "0.28.0", features = ["ptrace", "process"] } +linux-personality = "1.0.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6d0cc45 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,207 @@ +use linux_personality::{personality, ADDR_NO_RANDOMIZE}; +use nix::{ + sys::{ + ptrace, + signal::Signal, + wait::{wait, WaitStatus}, + }, + unistd::{fork, ForkResult, Pid}, +}; +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, + env::args, + ffi::{CStr, CString}, + os::unix::{fs::FileExt, process::CommandExt}, + process::Command, +}; + +#[derive(Debug)] +pub enum Event { + Fork { child: Pid }, + Exec { prog: CString }, + Exit { code: i32 }, +} + +#[derive(Clone, Debug)] +pub struct Identifier { + machine: i32, + pid: Pid, +} + +#[derive(Debug)] +pub struct LogEntry { + ident: Identifier, + event: Event, +} + +impl Event { + pub fn log(self, ident: Identifier, event_log: &mut Vec) { + event_log.push(LogEntry { ident, event: self }); + } +} + +fn run_child() { + ptrace::traceme().unwrap(); + personality(ADDR_NO_RANDOMIZE).unwrap(); + let mut args = args(); + args.next().unwrap(); + let argv0 = args.next().unwrap(); + panic!("exec: {}", Command::new(argv0).args(args).exec()); +} + +fn run_parent(first_child: Pid) { + //ptrace::attach(first_child).unwrap(); + //assert!(matches!(dbg!(wait().unwrap()), WaitStatus::Stopped(c, s) if c == first_child && s as i32 == nix::libc::SIGSTOP)); + //ptrace::cont(first_child, None).unwrap(); + assert!( + matches!((wait().unwrap()), WaitStatus::Stopped(c, s) if c == first_child && s as i32 == nix::libc::SIGTRAP) + ); + ptrace::setoptions( + first_child, + ptrace::Options::PTRACE_O_TRACESYSGOOD + | ptrace::Options::PTRACE_O_TRACEFORK + | ptrace::Options::PTRACE_O_TRACEVFORK + | ptrace::Options::PTRACE_O_TRACECLONE + | ptrace::Options::PTRACE_O_TRACEEXEC + | ptrace::Options::PTRACE_O_TRACEEXIT, + ) + .unwrap(); + ptrace::syscall(first_child, None).unwrap(); + + let events = RefCell::new(vec![]); + let fdcache = RefCell::new(HashMap::new()); + let mut pending_execve = HashMap::new(); + #[derive(PartialEq, Debug)] + enum ForkState { + SeenFork, + SeenStop, + } + let mut suppress_stops = HashMap::new(); + + fn root_pid(pid: Pid) -> Identifier { + Identifier { machine: 0, pid } + } + let log = |event: Event, ident: Identifier| { + events.borrow_mut().push(dbg!(LogEntry { event, ident })); + }; + let readstr = |pid: Pid, addr: u64| -> std::io::Result { + let mut fdcache = fdcache.borrow_mut(); + let fp = fdcache + .entry(pid) + .or_insert_with(|| std::fs::File::open(format!("/proc/{pid}/mem")).unwrap()); + let mut buf = vec![]; + loop { + buf.extend_from_slice(&[0; 1024]); + let len = buf.len(); + fp.read_at(&mut buf[len - 1024..], addr)?; + match CStr::from_bytes_until_nul(&buf) { + Ok(cstr) => return Ok(cstr.to_owned()), + Err(_) => continue, + } + } + }; + + enum ResumeType { + Cont(Option), + Syscall(Option), + Hold, + } + + loop { + let waited = dbg!(wait()); + let signal = match waited { + Ok(WaitStatus::PtraceEvent(pid, _sig, event)) + if event == ptrace::Event::PTRACE_EVENT_VFORK as i32 + || event == ptrace::Event::PTRACE_EVENT_FORK as i32 + || event == ptrace::Event::PTRACE_EVENT_CLONE as i32 => + { + let newpid = Pid::from_raw(ptrace::getevent(pid).unwrap() as i32); + log(Event::Fork { child: newpid }, root_pid(pid)); + if suppress_stops.insert(newpid, ForkState::SeenFork) == Some(ForkState::SeenStop) { + suppress_stops.remove(&newpid); + ResumeType::Syscall(None) + } else { + ResumeType::Hold + } + } + Ok(WaitStatus::PtraceEvent(pid, _sig, event)) + if event == ptrace::Event::PTRACE_EVENT_EXEC as i32 => + { + log( + Event::Exec { + prog: pending_execve.remove(&pid).unwrap(), + }, + root_pid(pid), + ); + ResumeType::Syscall(None) + } + Ok(WaitStatus::PtraceSyscall(pid)) => { + let regs = ptrace::getregs(pid).unwrap(); + match (dbg!(regs.orig_rax)) as i64 { + nix::libc::SYS_execve => { + let Ok(name) = (readstr(pid, regs.rdi)) else { + continue; + }; + pending_execve.insert(pid, name); + } + nix::libc::SYS_execveat => { + let Ok(name) = (readstr(pid, regs.rsi)) else { + continue; + }; + pending_execve.insert(pid, name); + } + _ => {} + } + ResumeType::Syscall(None) + GROUP STOP???? + } + Ok(WaitStatus::Exited(pid, code)) => { + fdcache.borrow_mut().remove(&pid); + log(Event::Exit { code }, root_pid(pid)); + continue; + } + + Ok(WaitStatus::Stopped(pid, signal)) => { + if signal == Signal::SIGSTOP { + if suppress_stops.insert(pid, ForkState::SeenStop) == Some(ForkState::SeenFork) + { + suppress_stops.remove(&pid); + ResumeType::Syscall(None) + } else { + ResumeType::Hold + } + } else if signal == Signal::SIGTRAP { + panic!("does this happen?"); + ResumeType::Syscall(None) + } else { + ResumeType::Cont(Some(signal)) + } + } + + Err(nix::errno::Errno::ECHILD) => { + break; + } + _ => todo!(), + }; + if let Some(pid) = waited.unwrap().pid() { + match signal { + ResumeType::Cont(signal) => ptrace::cont(pid, signal).unwrap(), + ResumeType::Syscall(signal) => ptrace::syscall(pid, signal).unwrap(), + ResumeType::Hold => {} + } + } + } +} + +fn main() { + match unsafe { fork() } { + Ok(ForkResult::Child) => { + run_child(); + } + Ok(ForkResult::Parent { child }) => { + run_parent(child); + } + Err(e) => panic!("fork: {e}"), + } +}