diff --git a/src/needle/rce.rs b/src/needle/executors.rs similarity index 53% rename from src/needle/rce.rs rename to src/needle/executors.rs index 0b3516c..38587f4 100644 --- a/src/needle/rce.rs +++ b/src/needle/executors.rs @@ -1,28 +1,6 @@ use nix::{unistd::Pid, Result, libc::{PROT_READ, MAP_PRIVATE, MAP_ANON, PROT_WRITE}, sys::{ptrace, wait::waitpid}}; -use crate::{syscalls::RemoteMMap, operations::write_buffer, injector::RemoteOperation}; - -pub struct RemoteString { - pub ptr: Option, - pub txt: String, -} - -impl RemoteString { - pub fn new(txt: String) -> Self { - RemoteString { ptr: None, txt } - } -} - -impl RemoteOperation for RemoteString { - fn inject(&mut self, pid: Pid, syscall: usize) -> Result { - let ptr = RemoteMMap::args( - 0, self.txt.len(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0 - ).inject(pid, syscall)?; - write_buffer(pid, ptr as usize, self.txt.as_bytes(), 32)?; // TODO don't hardcode word size - self.ptr = Some(ptr as usize); - Ok(ptr) - } -} +use crate::{syscalls::RemoteMMap, senders::write_buffer, injector::RemoteOperation}; pub struct RemoteShellcode<'a> { code: &'a [u8], @@ -42,7 +20,7 @@ impl RemoteOperation for RemoteShellcode<'_> { ).inject(pid, syscall)?; let mut shellcode = self.code.to_vec(); shellcode.push(0xCC); // is this the debugger trap? - write_buffer(pid, ptr as usize, shellcode.as_slice(), 32)?; // TODO don't hardcode word size + write_buffer(pid, ptr as usize, shellcode.as_slice())?; let mut regs = original_regs.clone(); regs.rip = ptr; ptrace::setregs(pid, regs)?; diff --git a/src/needle/injector.rs b/src/needle/injector.rs index b9ea462..a1b24d8 100644 --- a/src/needle/injector.rs +++ b/src/needle/injector.rs @@ -1,5 +1,28 @@ -use nix::{Result, unistd::Pid}; +use std::ffi::c_void; + +use nix::{Result, unistd::Pid, sys::{ptrace, wait::waitpid}}; pub trait RemoteOperation { fn inject(&mut self, pid: Pid, syscall: usize) -> Result; } + +pub fn step_to_syscall(pid: Pid) -> Result { + let mut registers; + let mut addr; + let mut instructions; + + // seek to syscall + loop { + registers = ptrace::getregs(pid)?; + addr = registers.rip as usize; + instructions = ptrace::read(pid, addr as *mut c_void)?; + // println!("@ 0x{:X} [{:x}]", insn_addr, curr_instr); + + if instructions & 0xFFFF == 0x050F { + return Ok(addr); + } + + ptrace::step(pid, None)?; + waitpid(pid, None)?; + } +} diff --git a/src/needle/main.rs b/src/needle/main.rs index 94a77c0..bc69245 100644 --- a/src/needle/main.rs +++ b/src/needle/main.rs @@ -1,13 +1,14 @@ mod syscalls; -mod rce; -mod operations; +mod executors; +mod senders; mod injector; -use injector::RemoteOperation; +use injector::{RemoteOperation, step_to_syscall}; use nix::{Result, {sys::{ptrace, wait::waitpid}, unistd::Pid}}; use clap::Parser; -use operations::step_to_syscall; -use rce::{RemoteString, RemoteShellcode}; + +use executors::RemoteShellcode; +use senders::RemoteString; use syscalls::RemoteWrite; #[derive(Parser, Debug)] @@ -15,45 +16,33 @@ use syscalls::RemoteWrite; struct NeedleArgs { /// target process pid pid: i32, - - /// word size on OS (check with $ getconf WORD_BIT) - #[arg(long, default_value_t = 32)] - word: u32, } -pub fn nasty_stuff(pid: Pid, _word_size: usize) -> Result<()> { - let original_registers = ptrace::getregs(pid)?; +pub fn nasty_stuff(pid: Pid) -> Result<()> { let syscall_addr = step_to_syscall(pid)?; let mut msg = RemoteString::new("injected!\n\0".into()); msg.inject(pid, syscall_addr)?; RemoteWrite::args(1, msg).inject(pid, syscall_addr)?; RemoteShellcode::new(&[0u8]).inject(pid, syscall_addr)?; - ptrace::setregs(pid, original_registers)?; Ok(()) } -fn main() { +fn main() -> Result<()> { let args = NeedleArgs::parse(); let pid = Pid::from_raw(args.pid); - if let Err(e) = ptrace::attach(pid) { - eprintln!("Could not attach to process : {}", e); - return; - } - - if let Err(e) = waitpid(pid, None) { - eprintln!("Failed waiting for process to stop : {}", e); - } + ptrace::attach(pid)?; + waitpid(pid, None)?; println!("Attached to process #{}", args.pid); - if let Err(e) = nasty_stuff(pid, args.word as usize) { - eprintln!("Could not pwn : {}", e); - } + let regs = ptrace::getregs(pid)?; + nasty_stuff(pid)?; + ptrace::setregs(pid, regs)?; - if let Err(e) = ptrace::detach(pid, None) { - eprintln!("Could not resume process : {}", e); - } else { - println!("Released process #{}", args.pid); - } + ptrace::detach(pid, None)?; + + println!("Released process #{}", args.pid); + + Ok(()) } diff --git a/src/needle/operations.rs b/src/needle/operations.rs deleted file mode 100644 index 04a90f0..0000000 --- a/src/needle/operations.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::ffi::c_void; - -use nix::{Result, unistd::Pid, sys::{ptrace, wait::waitpid}}; - -#[allow(unused)] -pub fn read_buffer(pid: Pid, addr: usize, size: usize, word: u32) -> Result> { - let mut out = vec![]; - - for i in (0..size).step_by((word/8) as usize) { - let data = ptrace::read(pid, (addr + i) as *mut c_void)?; - for j in 0..(word/8) as usize { - out.push(((data >> (j * 8)) & 0xFF) as u8); - } - } - - Ok(out) -} - -pub fn write_buffer(pid: Pid, addr: usize, payload: &[u8], word:u32) -> Result<()> { - let step = word / 8; - let mut at = addr; - - for chunk in payload.chunks(step as usize) { - let mut buf : u64 = 0; - for (i, c) in chunk.iter().enumerate() { - buf |= (*c as u64) << (i * 8); - } - unsafe { ptrace::write(pid, at as *mut c_void, buf as *mut c_void)?; } - at += step as usize; - } - - Ok(()) -} - -pub fn step_to_syscall(pid: Pid) -> Result { - let mut registers; - let mut addr; - let mut instructions; - - // seek to syscall - loop { - registers = ptrace::getregs(pid)?; - addr = registers.rip as usize; - instructions = ptrace::read(pid, addr as *mut c_void)?; - // println!("@ 0x{:X} [{:x}]", insn_addr, curr_instr); - - if instructions & 0xFFFF == 0x050F { - return Ok(addr); - } - - ptrace::step(pid, None)?; - waitpid(pid, None)?; - } -} diff --git a/src/needle/senders.rs b/src/needle/senders.rs new file mode 100644 index 0000000..16463f9 --- /dev/null +++ b/src/needle/senders.rs @@ -0,0 +1,60 @@ +use std::ffi::c_void; + +use nix::{Result, unistd::Pid, sys::ptrace, libc::{PROT_READ, PROT_WRITE, MAP_PRIVATE, MAP_ANON}}; + +use crate::{injector::RemoteOperation, syscalls::RemoteMMap}; + +const WORD_SIZE : usize = 32; + +#[allow(unused)] +pub fn read_buffer(pid: Pid, addr: usize, size: usize) -> Result> { + let mut out = vec![]; + + for i in (0..size).step_by(WORD_SIZE/8) { + let data = ptrace::read(pid, (addr + i) as *mut c_void)?; + for j in 0..WORD_SIZE/8 { + out.push(((data >> (j * 8)) & 0xFF) as u8); + } + } + + Ok(out) +} + +pub fn write_buffer(pid: Pid, addr: usize, payload: &[u8]) -> Result<()> { + let step = WORD_SIZE / 8; + let mut at = addr; + + for chunk in payload.chunks(step as usize) { + let mut buf : u64 = 0; + for (i, c) in chunk.iter().enumerate() { + buf |= (*c as u64) << (i * 8); + } + unsafe { ptrace::write(pid, at as *mut c_void, buf as *mut c_void)?; } + at += step as usize; + } + + Ok(()) +} + +pub struct RemoteString { + pub ptr: Option, + pub txt: String, +} + +impl RemoteString { + pub fn new(txt: String) -> Self { + RemoteString { ptr: None, txt } + } +} + +impl RemoteOperation for RemoteString { + fn inject(&mut self, pid: Pid, syscall: usize) -> Result { + let ptr = RemoteMMap::args( + 0, self.txt.len(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0 + ).inject(pid, syscall)?; + write_buffer(pid, ptr as usize, self.txt.as_bytes())?; + self.ptr = Some(ptr as usize); + Ok(ptr) + } +} + diff --git a/src/needle/syscalls.rs b/src/needle/syscalls.rs index 885e7e7..4a1b313 100644 --- a/src/needle/syscalls.rs +++ b/src/needle/syscalls.rs @@ -1,6 +1,6 @@ use nix::{libc::user_regs_struct, Result, sys::{ptrace, wait::waitpid}, unistd::Pid}; -use crate::{rce::RemoteString, injector::RemoteOperation}; +use crate::{injector::RemoteOperation, senders::RemoteString}; pub trait RemoteSyscall { fn registers(&self, regs: &mut user_regs_struct); @@ -73,7 +73,7 @@ impl RemoteSyscall for RemoteOpen { pub struct RemoteWrite { fd: u64, - buf: RemoteString, // TODO make remote slice or remote bytes + buf: RemoteString, } impl RemoteWrite {