#![warn(clippy::all, clippy::pedantic, clippy::nursery, missing_docs)]
#![allow(
// Errors can happen for such a diverse set of reasons out of the user's
// control that listing them all in a form other than the variants of `Error`
// would not be feasible or useful.
clippy::missing_errors_doc,
// Register names like `rsi` and `rdi` break this.
clippy::similar_names,
)]
use eyre::{eyre, Context, Result};
use injection::Injection;
pub(crate) use libc_addresses::LibcAddrs;
pub use process::Process;
mod injection;
mod libc_addresses;
mod process;
pub struct Injector {
proc: Process,
tracer: pete::Ptracer,
}
impl Injector {
pub fn spawn(command: std::process::Command) -> Result<Self> {
let mut tracer = pete::Ptracer::new();
let child = tracer
.spawn(command)
.wrap_err("failed to spawn and trace command")?;
let proc =
Process::get(child.id()).wrap_err("failed to get newly spawned process by PID")?;
log::info!("Spawned process with PID {}", proc);
Ok(Self { proc, tracer })
}
pub fn attach(proc: Process) -> Result<Self> {
let mut tracer = pete::Ptracer::new();
tracer
.attach((&proc).into())
.wrap_err("failed to attach to given process")?;
log::trace!("Attached to process with PID {}", proc);
Ok(Self { proc, tracer })
}
fn attach_children(&mut self) -> Result<()> {
let threads = self
.proc
.thread_ids()
.wrap_err("couldn't get thread IDs of target to attach to them")?;
log::trace!("Attaching to {} child threads of target", threads.len() - 1);
threads
.iter()
.filter(|&tid| tid != &self.proc.pid())
.try_for_each(|&tid| {
self.tracer
.attach(pete::Pid::from_raw(tid))
.wrap_err_with(|| format!("failed to attach to child thread with TID {tid}"))?;
let actual_tid = self
.tracer
.wait()
.wrap_err("failed to wait for thread to stop")?
.ok_or_else(|| {
eyre!("a target thread exited quietly as soon as we started tracing it")
})?
.pid;
log::trace!("Attached to thread ID {actual_tid} of target process");
Ok(())
})
}
pub fn inject(&mut self, library: &std::path::Path) -> Result<()> {
self.attach_children()
.wrap_err("failed to attach to child threads")?;
let Some(tracee) = self.tracer.wait()? else {
return Err(eyre!("the target exited quietly as soon as we started tracing it"));
};
log::trace!("Attached to process with ID {}", tracee.pid);
let mut injection = Injection::inject(&self.proc, &mut self.tracer, tracee)
.wrap_err("failed to inject shellcode")?;
injection
.execute(library)
.wrap_err("failed to execute shellcode")?;
injection.remove().wrap_err("failed to remove shellcode")?;
log::info!(
"Successfully injected library {} into process with PID {}",
library.display(),
self.proc
);
Ok(())
}
}