diff --git a/01/project-hbj-hook/src/asm.rs b/01/project-hbj-hook/src/asm.rs new file mode 100644 index 0000000..96cf14e --- /dev/null +++ b/01/project-hbj-hook/src/asm.rs @@ -0,0 +1,12 @@ + +use iced_x86::code_asm::asm_traits::CodeAsmJmp; +use iced_x86::{Instruction, code_asm::*}; + +pub fn assemble(addr: u64, op: F) -> Result, Box> +where + F: Fn(&mut CodeAssembler) -> Result<(), Box>, +{ + let mut asm = CodeAssembler::new(64)?; + _ = op(&mut asm); + Ok(asm.assemble(addr)?) +} \ No newline at end of file diff --git a/01/project-hbj-hook/src/elf.rs b/01/project-hbj-hook/src/elf.rs index 7a23355..5f42557 100644 --- a/01/project-hbj-hook/src/elf.rs +++ b/01/project-hbj-hook/src/elf.rs @@ -1,4 +1,7 @@ -use anyhow::{Context, bail}; + +// elf.rs + +use anyhow::{Context}; use goblin::elf::{Elf, ProgramHeader, Sym, program_header::PT_DYNAMIC, program_header::PT_LOAD, reloc::R_X86_64_JUMP_SLOT, Reloc}; use memmap2::Mmap; use std::fs::File; @@ -108,4 +111,9 @@ impl ExecuteLinkFile { Ok(str.to_owned()) } + + pub fn get_e_type(&self) -> u16 + { + self.borrow_elf().header.e_type + } } diff --git a/01/project-hbj-hook/src/main.rs b/01/project-hbj-hook/src/main.rs index b412464..5c3b3b5 100644 --- a/01/project-hbj-hook/src/main.rs +++ b/01/project-hbj-hook/src/main.rs @@ -1,9 +1,15 @@ -use crate::elf::ExecuteLinkFile; +use std::error::Error; +use std::ffi::CString; +use anyhow::Context; +use iced_x86::code_asm::*; use crate::map::MemoryMap; use crate::processes::{get_pid_by_name, Process}; -use anyhow::Context; use nix::unistd::Pid; -use std::fs; + +use libc::user_regs_struct; +use nix::sys::ptrace; + +use crate::asm::assemble; const GREEN: &str = "\x1b[32m"; const RESET: &str = "\x1b[0m"; @@ -12,8 +18,61 @@ mod disassembly; mod elf; mod map; mod processes; +mod asm; -fn main() -> Result<(), Box> { +pub fn plt_hook(proc: &Process, map: &MemoryMap, symbol: &str, payload: F) + -> Result<(), Box> +where + F: Fn(&Process, u64, u64) -> Vec +{ + let bias = map.module_base_address(&proc.get_exe()?).unwrap_or(0); + let got_item_ptr = proc.find_got_pointer_plt(symbol).context("Unable to find symbol")?; + + println!("{GREEN}[memory map]{RESET} Bias is {:#016x}", bias); + + let got_item_byte: [u8; 8] = proc.read_memory_vm(got_item_ptr as usize, 8)? + .try_into() + .map_err(|_| "Failed to convert Vec to array")?; + let got_item = u64::from_le_bytes(got_item_byte); + println!( + "{GREEN}[memory map]{RESET} got_item = {:#016x}", got_item + ); + + // Alloc r-x private memory + let regs = ptrace::getregs(proc.get_pid())?; + let r = proc.execute_once_inplace(&assemble(regs.rip as u64, |asm| { + asm.mov(rax, 9u64)?; // Syscall 9 (mmap) + + asm.mov(rdi, 0u64)?; // Addr + asm.mov(rsi, 0x1000u64)?; // Length, we alloc a page (4K) + asm.mov( + rdx, + (libc::PROT_READ | libc::PROT_EXEC) as u64, + )?; // Set protect to rwx + asm.mov(r10, (libc::MAP_PRIVATE | libc::MAP_ANONYMOUS) as u64)?; // Private and anonymous + asm.mov(r8, 01i64)?; // Fd (-1 because we want anonymous) + asm.mov(r9, 0u64)?; // Offset + + asm.syscall()?; // Syscall interrupt + Ok(()) + })?)?; + + let page_addr = r.rax as u64; + println!("{GREEN}[trace]{RESET} allocated page is at {:#016x}", page_addr); + + let payload = payload(&proc, page_addr, got_item); + if payload.len() > 0x1000 { return Err(Box::::from("payload exceeds 0x1000 bytes")); } + + proc.write_memory_ptrace(page_addr as usize, &payload)?; + println!("{GREEN}[trace]{RESET} wrote {} bytes of instructions/data to {:#016x}", payload.len(), page_addr); + proc.write_memory_ptrace(got_item_ptr as usize, &page_addr.to_le_bytes())?; + println!("{GREEN}[trace]{RESET} rewrote got item at {:#016x} to {:#016x}, old value is {:#016x}", + got_item_ptr, page_addr, got_item); + + Ok(()) +} + +fn main() -> Result<(), Box> { // Find our target program let pid = Pid::from_raw(get_pid_by_name("target")?); let process = Process::new(pid)?; @@ -27,23 +86,64 @@ fn main() -> Result<(), Box> { } let map = MemoryMap::new(&lines); - let bias = map.module_base_address(&exe).unwrap_or(0); - let write_got = process.find_got_pointer_plt("write").unwrap_or(0); - println!("{GREEN}[memory map]{RESET} Bias is {:#016x}", bias); - println!("{GREEN}[memory map]{RESET} pointer to write is at {:#016x}", write_got); + let seg_x = map.first_exec_segment(&exe).context("Can't find first exec segment")?; - let got_write_vec: [u8; 8] = process.read_memory_vm(write_got as usize, 8)? - .try_into() - .map_err(|_| "Failed to convert Vec to array")?; - let got_write_addr = u64::from_le_bytes(got_write_vec); - let real_write_addr = - process.find_remote_proc("/usr/lib/libc.so.6", "write").context("Failed to find write.")?; + ptrace::attach(pid)?; + process.wait(); + ptrace::step(pid, None)?; + process.wait(); - println!( - "{GREEN}[memory map]{RESET} real_write_addr = {:#016x}, got_write_addr = {:#016x}", - real_write_addr, got_write_addr - ); + // Save context + let regs = ptrace::getregs(pid)?; // Save current registers + + ptrace::setregs( + pid, + user_regs_struct { + rip: seg_x.0, + ..regs + }, + )?; + + // Do inject here + + plt_hook(&process, &map, "write", |proc, addr, old| { + let inst = assemble(addr, |asm| { + asm.push(rdi)?; + asm.push(rsi)?; + asm.push(rdx)?; + asm.mov(rdi, 1u64)?; + asm.mov(rsi, addr + 0x800)?; + asm.mov(rdx, 7u64)?; + asm.call(old)?; + asm.pop(rdx)?; + asm.pop(rsi)?; + asm.pop(rdi)?; + + asm.call(old)?; + + asm.mov(rdi, 1u64)?; + asm.mov(rsi, addr + 0x810)?; + asm.mov(rdx, 2u64)?; + asm.call(old)?; + + asm.ret()?; + Ok(()) + }).unwrap(); + + let mut payload = Vec::from([0u8; 0x1000]); + payload.splice(0..inst.len(), inst); + payload.splice(0x800..(0x800 + 7), Vec::from("[hook] ".as_bytes())); + payload.splice(0x810..(0x810 + 2), Vec::from("\r\n".as_bytes())); + + return payload; + })?; + + // End inject logics + + // Restore context + ptrace::setregs(pid, regs)?; + ptrace::detach(pid, None)?; Ok(()) } diff --git a/01/project-hbj-hook/src/map.rs b/01/project-hbj-hook/src/map.rs index fe48098..e120905 100644 --- a/01/project-hbj-hook/src/map.rs +++ b/01/project-hbj-hook/src/map.rs @@ -1,3 +1,6 @@ + +// map.rs + use crate::elf::ExecuteLinkFile; #[derive(Debug, Clone)] @@ -81,28 +84,34 @@ impl MemoryMap { .map(|r| (r.start_addr, r.end_addr)) } - pub fn module_base_address(&self, module: &str) -> Option { + + pub fn module_base_address( + &self, + module: &str // Full path of module, like '/usr/lib/libc.so.6' + ) -> Option { let elf = ExecuteLinkFile::prase(&module).ok()?; let loads = elf.get_loads().ok()?; - let Some(first_load) = loads.first() else { + let Some(first_load) = loads.iter().find(|p| { + p.is_executable() + }) else { return None; }; let Some(map_item) = self.regions.iter().find(|r| { - r.offset.unwrap_or(0) == first_load.p_offset && r.pathname.as_deref() == Some(module) + r.offset.unwrap_or(0) == first_load.p_offset && r.pathname.as_deref() == Some(module) && r.is_executable() }) else { return None; }; Some(map_item.start_addr - first_load.p_vaddr) } - + pub fn collect_module(&self, module: &str) -> Vec { let r = self.regions.iter() .filter_map(|r| if r.pathname.as_deref() == Some(module) { Some(r.clone()) } else { None }) .collect::>(); - + r } } diff --git a/01/project-hbj-hook/src/processes.rs b/01/project-hbj-hook/src/processes.rs index 6f58321..6c44b32 100644 --- a/01/project-hbj-hook/src/processes.rs +++ b/01/project-hbj-hook/src/processes.rs @@ -1,3 +1,6 @@ + +// processes.rs + use std::collections::HashMap; use std::fs; @@ -6,10 +9,14 @@ use nix::unistd::Pid; use std::error::Error; use std::ffi::CString; use std::io::{IoSliceMut, IoSlice}; +use goblin::elf64::{header, section_header}; +use iced_x86::code_asm::{r10, r8, r9, rax, rdi, rdx, rsi}; +use iced_x86::Instruction; use nix::sys::ptrace; -use libc::{dlsym, RTLD_NEXT}; +use libc::{dlsym, user_regs_struct, RTLD_NEXT}; use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; +use crate::asm::assemble; use crate::elf::ExecuteLinkFile; use crate::map::{MemoryMap}; @@ -72,7 +79,7 @@ impl Process let copy_len = usize::min(word_size - head_offset, data.len()); bytes[head_offset..head_offset + copy_len].copy_from_slice(&data[..copy_len]); - let new_word = libc::c_long::from_ne_bytes(bytes); + let new_word = libc::c_long::from_le_bytes(bytes); ptrace::write(pid, aligned_addr as *mut libc::c_void, new_word)?; Ok(copy_len) @@ -86,7 +93,7 @@ impl Process { let mut arr = [0u8; size_of::()]; arr.copy_from_slice(data); - let val = libc::c_long::from_ne_bytes(arr); + let val = libc::c_long::from_le_bytes(arr); ptrace::write(pid, addr as *mut libc::c_void, val)?; Ok(size_of::()) } @@ -101,7 +108,7 @@ impl Process let orig_word = ptrace::read(pid, addr as *mut libc::c_void)?; let mut bytes = orig_word.to_ne_bytes(); bytes[..data.len()].copy_from_slice(data); - let new_word = libc::c_long::from_ne_bytes(bytes); + let new_word = libc::c_long::from_le_bytes(bytes); ptrace::write(pid, addr as *mut libc::c_void, new_word)?; Ok(data.len()) @@ -238,15 +245,30 @@ impl Process Ok(written) } - pub fn find_remote_proc(&self, module: &str, symbol: &str) -> Option + pub fn find_remote_proc( + &self, + module: &str, // Full path of module, like '/usr/lib/libc.so.6' + symbol: &str // Symbol name, like 'printf' + ) -> Option { - let target_maps = fs::read_to_string(format!("/proc/{}/maps", self.pid)).ok()?; - let base = MemoryMap::new(&target_maps.lines().collect::>()).module_base_address(module)?; - let elf = ExecuteLinkFile::prase(module).ok()?; let sym = elf.prase_dyn_sym(symbol).ok()?; - Some(sym.st_value + base) + let target_maps = fs::read_to_string(format!("/proc/{}/maps", self.pid)).ok()?; + let base = MemoryMap::new(&target_maps.lines().collect::>()).module_base_address(module)?; + + let is_undefined = sym.st_shndx == section_header::SHN_UNDEF as usize; + + if !is_undefined && sym.st_value != 0 { + return if elf.get_e_type() == header::ET_DYN { + Some(base + sym.st_value) + } else { + // ET_EXEC or others: assume st_value is absolute + Some(sym.st_value) + } + } + + None } pub fn find_got_pointer_plt(&self, symbol: &str) -> Option @@ -257,4 +279,28 @@ impl Process let r_sym = elf.get_rela_sym(symbol).ok()?; Some(r_sym.r_offset + self.map.module_base_address(&exe)?) } + + pub fn execute_once_inplace(&self, payload: &[u8]) -> Result> + { + // Save context + let regs = ptrace::getregs(self.pid)?; + let buffer = self.read_memory_vm(regs.rip as usize, payload.len() + 1)?; + let instruction = [payload, &[0xccu8]].concat(); + + + self.write_memory_ptrace(regs.rip as usize, &instruction)?; + println!("{GREEN}[trace]{RESET} write instructions to {:#016x}", regs.rip); + + // Continue target + ptrace::cont(self.pid, None)?; + println!("{GREEN}[trace]{RESET} continue from {:#016x}", regs.rip); + self.wait(); + + let r = ptrace::getregs(self.pid)?; + println!("{GREEN}[trace]{RESET} int3 at {:#016x}", r.rip); + + self.write_memory_ptrace(regs.rip as usize, &buffer)?; + ptrace::setregs(self.pid, regs)?; + Ok(r) + } } \ No newline at end of file