use crate::{LibcAddrs, Process};
use eyre::{eyre, Context, Result};
use std::os::unix::ffi::OsStringExt;
const SHELLCODE: [u8; 6] = [
0x90, 0x90,
0x41, 0xff, 0xd1, 0xcc, ];
#[derive(Debug)]
pub struct Injection<'a> {
saved_registers: pete::Registers,
saved_memory: Vec<u8>,
injected_at: u64,
libc: LibcAddrs,
tracer: &'a mut pete::Ptracer,
tracee: pete::Tracee,
removed: bool,
}
impl<'a> Injection<'a> {
pub(crate) fn inject(
proc: &Process,
tracer: &'a mut pete::Ptracer,
mut tracee: pete::Tracee,
) -> Result<Self> {
let injected_at = proc
.find_executable_space()
.wrap_err("couldn't find region to write shellcode")?;
log::debug!("Injecting shellcode at {injected_at:x}");
let saved_memory = tracee
.read_memory(injected_at, SHELLCODE.len())
.wrap_err("failed to read memory we were going to overwrite")?;
log::trace!("Read memory to overwrite: {saved_memory:x?}");
tracee
.write_memory(injected_at, &SHELLCODE)
.wrap_err("failed to write shellcode to tracee")?;
log::trace!("Written shellcode");
let saved_registers = tracee
.registers()
.wrap_err("failed to save original tracee registers")?;
log::trace!("Saved registers: {saved_registers:x?}");
let libc = LibcAddrs::for_process(proc)
.wrap_err("couldn't get libc function addresses for tracee")?;
log::trace!("Found libc addresses: {libc:x?}");
log::debug!("Injected shellcode into tracee");
Ok(Self {
saved_registers,
saved_memory,
injected_at,
libc,
tracer,
tracee,
removed: false,
})
}
pub(crate) fn execute(&mut self, filename: &std::path::Path) -> Result<()> {
let address = self
.write_filename(filename)
.wrap_err("couldn't write library filename to tracee address space")?;
self.open_library(address)
.wrap_err("failed to load library in tracee")?;
self.free_alloc(address)
.wrap_err("failed to free memory we stored the library filename in")?;
log::debug!("Executed injected shellcode to load library");
Ok(())
}
fn write_filename(&mut self, filename: &std::path::Path) -> Result<u64> {
let mut filename = std::fs::canonicalize(filename)
.wrap_err("couldn't get absolute path of given library")?
.into_os_string()
.into_vec();
filename.push(0);
let address = self
.call_function(self.libc.malloc, filename.len() as u64, 0)
.wrap_err("calling malloc in tracee failed")?;
if address == 0 {
return Err(eyre!("malloc within tracee returned NULL"));
}
log::trace!(
"Allocated {} bytes at {address:x} in tracee for library filename",
filename.len(),
);
self.tracee
.write_memory(address, &filename)
.wrap_err("writing library name to tracee failed")?;
log::debug!("Wrote library filename to tracee");
Ok(address)
}
fn open_library(&mut self, filename_address: u64) -> Result<()> {
let result = self
.call_function(self.libc.dlopen, filename_address, 1) .wrap_err("calling dlopen in tracee failed")?;
log::debug!("Called dlopen in tracee, result = {result:x}");
if result == 0 {
Err(eyre!("dlopen within tracee returned NULL"))
} else {
Ok(())
}
}
fn free_alloc(&mut self, address: u64) -> Result<()> {
let result = self
.call_function(self.libc.free, address, 0)
.wrap_err("calling free in tracee failed")?;
log::debug!("Freed memory in tracee, result = {result:x}");
Ok(())
}
fn call_function(&mut self, fn_address: u64, rdi: u64, rsi: u64) -> Result<u64> {
log::trace!("Calling function at {fn_address:x} with rdi = {rdi:x}, rsi = {rsi:x}");
self.tracee
.set_registers(pete::Registers {
rip: self.injected_at + 2,
r9: fn_address,
rdi,
rsi,
rsp: self.saved_registers.rsp & !0xf,
..self.saved_registers
})
.wrap_err("setting tracee registers to run shellcode failed")?;
self.run_until_trap()
.wrap_err("waiting for shellcode in tracee to trap failed")?;
let result = self
.tracee
.registers()
.wrap_err("reading shellcode call result from tracee registers failed")?
.rax;
log::trace!("Function returned {result:x}");
Ok(result)
}
fn run_until_trap(&mut self) -> Result<()> {
log::trace!("Running tracee until it receives a signal");
self.tracer
.restart(self.tracee, pete::Restart::Continue)
.wrap_err("resuming tracee to wait for trap failed")?;
while let Some(tracee) = self
.tracer
.wait()
.wrap_err("waiting for tracee trap failed")?
{
log::trace!("Tracee stopped with {:?}", tracee.stop);
match tracee.stop {
pete::Stop::SignalDelivery {
signal: pete::Signal::SIGTRAP,
} => {
self.tracee = tracee;
return Ok(());
}
pete::Stop::SignalDelivery { signal } | pete::Stop::Group { signal } => {
let rip = tracee.registers().unwrap().rip;
return Err(eyre!(
"shellcode running in tracee sent unexpected signal {signal:?} at rip={rip:x}",
));
}
_ => {
log::trace!("Not an interesting stop, continuing running tracee");
self.tracer
.restart(tracee, pete::Restart::Continue)
.wrap_err("re-resuming tracee to wait for trap failed")?;
}
};
}
Err(eyre!("tracee exited while we were waiting for trap"))
}
pub(crate) fn remove(mut self) -> Result<()> {
self._remove()
}
fn _remove(&mut self) -> Result<()> {
if self.removed {
log::trace!("Already removed injection, doing nothing");
return Ok(());
}
self.tracee
.write_memory(self.injected_at, &self.saved_memory)
.wrap_err("restoring original code to tracee failed")?;
log::trace!("Restored memory the injection overwrote");
self.tracee
.set_registers(self.saved_registers)
.wrap_err("restoring original registers to tracee failed")?;
log::trace!("Restored tracee registers");
self.tracer
.restart(self.tracee, pete::Restart::Continue)
.wrap_err("resuming tracee after restoring original state failed")?;
log::trace!("Restarted tracee");
log::debug!("Removed injection");
self.removed = true;
Ok(())
}
}
impl<'a> Drop for Injection<'a> {
fn drop(&mut self) {
if !self.removed {
log::warn!("Injection dropped without being removed, removing now");
}
self._remove()
.wrap_err("removing injection from drop impl failed")
.unwrap();
}
}