diff --git a/01/project-hbj-attacker/Cargo.lock b/01/project-hbj-attacker/Cargo.lock index c965703..eaff9f6 100644 --- a/01/project-hbj-attacker/Cargo.lock +++ b/01/project-hbj-attacker/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "bitflags" version = "2.10.0" @@ -90,6 +102,23 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "goblin" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51876e3748c4a347fe65b906f2b1ae46a1e55a497b22c94c1f4f2c469ff7673a" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "iced-x86" version = "1.21.0" @@ -121,6 +150,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + [[package]] name = "memmap2" version = "0.9.9" @@ -142,6 +177,36 @@ dependencies = [ "libc", ] +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -173,16 +238,33 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + [[package]] name = "project-hbj-attacker" version = "0.1.0" dependencies = [ + "anyhow", "ctor", "dynasmrt", + "goblin", "iced-x86", "libc", "libloading", + "memmap2", "nix", + "ouroboros", ] [[package]] @@ -194,6 +276,32 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "2.0.107" @@ -211,8 +319,20 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/01/project-hbj-attacker/Cargo.toml b/01/project-hbj-attacker/Cargo.toml index 594bca9..7bbaf5b 100644 --- a/01/project-hbj-attacker/Cargo.toml +++ b/01/project-hbj-attacker/Cargo.toml @@ -10,10 +10,14 @@ libc = "0.2.177" nix = { version = "0.30.1", features = ["ptrace", "uio", "signal"] } ctor = "0.6.0" libloading = "0.8.9" +goblin = "0.10.3" +memmap2 = "0.9.9" +ouroboros = "0.18.5" +anyhow = "1.0.100" [profile.dev] opt-level = 0 debug = true [lib] -crate-type = ["cdylib"] \ No newline at end of file +crate-type = ["cdylib"] diff --git a/01/project-hbj-attacker/src/helper.rs b/01/project-hbj-attacker/src/helper.rs index 922be4b..92942b8 100644 --- a/01/project-hbj-attacker/src/helper.rs +++ b/01/project-hbj-attacker/src/helper.rs @@ -1,15 +1,11 @@ mod map; mod processes; mod asm; +mod injectors; +mod elf; -pub use map::is_address_in_range; -pub use processes::get_pid_by_name; -pub use processes::read_memory_vm; -pub use processes::write_memory_vm; -pub use processes::write_memory_ptrace; -pub use map::first_rw_segment; -pub use map::module_base_address; -pub use map::first_exec_segment; +pub use map::*; pub use asm::assemble; -pub use processes::find_remote_proc; -pub use processes::wait; \ No newline at end of file +pub use processes::*; +pub use injectors::*; +pub use elf::*; \ No newline at end of file diff --git a/01/project-hbj-attacker/src/helper/elf.rs b/01/project-hbj-attacker/src/helper/elf.rs new file mode 100644 index 0000000..7a23355 --- /dev/null +++ b/01/project-hbj-attacker/src/helper/elf.rs @@ -0,0 +1,111 @@ +use anyhow::{Context, bail}; +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; +use std::ops::Deref; +use ouroboros::self_referencing; + +fn open_mem_map(path: &str) -> Result> { + let file = File::open(path)?; + unsafe { Ok(Mmap::map(&file)?) } +} + +#[self_referencing] +pub struct ExecuteLinkFile { + data: Vec, + + #[borrows(data)] + #[covariant] + elf: Elf<'this> +} + +impl ExecuteLinkFile { + pub fn prase(path: &str) -> Result> { + let data = open_mem_map(path)?.deref().to_owned(); + let s = ExecuteLinkFileTryBuilder { + data, + elf_builder: |data_ref| { + Elf::parse(&data_ref) + } + }.try_build()?; + + Ok(s) + } + + pub fn get_loads(&self) -> Result, Box> { + let loads = self.borrow_elf() + .program_headers + .iter() + .filter_map(|ph| match ph.p_type { + PT_LOAD => Some(ph.to_owned()), + _ => None, + }) + .collect::>(); + + Ok(loads) + } + + pub fn get_dynamic(&self) -> Result> { + let dynamic = self.borrow_elf() + .program_headers + .iter() + .find(|ph| ph.p_type == PT_DYNAMIC) + .context("No PT_DYNAMIC segment found")?; + + Ok(dynamic.clone()) + } + + pub fn get_rela_sym(&self, name: &str) -> Result> { + let rela_plt = self.borrow_elf().pltrelocs.iter(); + + let sym = rela_plt + .filter(|rela| { + matches!(rela.r_type, R_X86_64_JUMP_SLOT) // R_X86_64_JUMP_SLOT + }) + .filter_map(|rela| { + let sym_index = rela.r_sym; + let Ok(sym) = self.get_dyn_sym(sym_index) else { + return None; + }; + let Ok(sym_name) = self.get_dyn_str(sym.st_name) else { + return None; + }; + + if sym_name == name { Some(rela) } else { None } + }) + .collect::>(); + + let first = sym + .first() + .context(format!("No symbol found with name {}", name))?; + + Ok(first.clone()) + } + + pub fn get_dyn_sym(&self, location: usize) -> Result> { + let dyn_sym = self.borrow_elf() + .dynsyms + .get(location) + .context(format!("No symbol found at location {}", location))?; + + Ok(dyn_sym.clone()) + } + + pub fn prase_dyn_sym(&self, name: &str) -> Result> { + let dyn_sym = self.borrow_elf() + .dynsyms.iter() + .find(|sym| self.get_dyn_str(sym.st_name).ok().as_deref() == Some(name)) + .context(format!("No symbol found with name {}", name))?; + + Ok(dyn_sym.clone()) + } + + pub fn get_dyn_str(&self, location: usize) -> Result> { + let str = self.borrow_elf() + .dynstrtab + .get_at(location) + .context(format!("Could not get dynstr at location {}", location))?; + + Ok(str.to_owned()) + } +} diff --git a/01/project-hbj-attacker/src/helper/injectors.rs b/01/project-hbj-attacker/src/helper/injectors.rs new file mode 100644 index 0000000..a033775 --- /dev/null +++ b/01/project-hbj-attacker/src/helper/injectors.rs @@ -0,0 +1,248 @@ +use std::ffi::CString; +use std::fs; +use iced_x86::code_asm::{eax, r10, r8, r9, rax, rbp, rcx, rdi, rdx, rsi, rsp}; +use nix::sys::ptrace; +use nix::sys::wait::{waitpid, WaitPidFlag}; +use nix::unistd::Pid; +use crate::helper::{assemble, Process}; + + +const GREEN: &str = "\x1b[32m"; +const RESET: &str = "\x1b[0m"; + +pub fn inject1(proc: &Process, seg_rw: (u64, u64)) -> Result<(), Box> // Simple injection +{ + let regs = ptrace::getregs(proc.get_pid())?; + let injected_data = "You are injected. \r\n"; + proc.write_memory_vm(seg_rw.0 as usize, &injected_data.as_bytes())?; + println!( + "{GREEN}[trace]{RESET} write \"{}\" to {:#016x}", + injected_data, seg_rw.0 + ); + + let injected_inst = assemble(regs.rip as u64, |asm| { + asm.mov(rax, 1u64)?; // Syscall 1 (write) + asm.mov(rdi, 1u64)?; // Fd 1 (STDOUT) + asm.mov(rsi, seg_rw.0)?; // Buffer pointer (Here is rw segment in target) + asm.mov(rdx, injected_data.as_bytes().len() as u64)?; // Buffer length + asm.syscall()?; // Syscall interrupt + asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow + Ok(()) + })?; + + proc.write_memory_ptrace(regs.rip as usize, &injected_inst)?; + println!( + "{GREEN}[trace]{RESET} write instructions to {:#016x}", + regs.rip + ); + + // Continue target + ptrace::cont(proc.get_pid(), None)?; + println!("{GREEN}[trace]{RESET} continue from {:#016x}", regs.rip); + proc.wait(); + + let regs = ptrace::getregs(proc.get_pid())?; + println!("{GREEN}[trace]{RESET} int3 at {:#016x}", regs.rip); + + Ok(()) +} + +pub fn inject2(proc: &Process, seg_rw: (u64, u64)) -> Result<(), Box> // ld injection +{ + let regs = ptrace::getregs(proc.get_pid())?; + // Get the absolute path to our shared library + let lib_path = fs::canonicalize("./target/debug/libproject_hbj_attacker.so")? + .to_string_lossy() + .into_owned(); + + // Start Inject + let c_lib_path = CString::new(lib_path).unwrap(); + proc.write_memory_vm(seg_rw.0 as usize, c_lib_path.as_bytes_with_nul())?; + println!( + "{GREEN}[trace]{RESET} write {} to {:#016x}", + &c_lib_path.to_string_lossy(), + seg_rw.0 + ); + + let Some(target_dlopen_addr) = proc.find_remote_proc("/usr/lib/libc.so.6", "dlopen") else { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + "first rw segment not found", + ))); + }; + + let injected_inst = assemble(regs.rip as u64, |asm| { + asm.mov(rdi, seg_rw.0)?; // Param 1: Filename + asm.mov(rsi, 2u64)?; // Param 2: Flag, 2(RTLD_NOW) + asm.call(target_dlopen_addr)?; // Syscall interrupt + asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow + Ok(()) + })?; + + proc.write_memory_ptrace(regs.rip as usize, &injected_inst)?; + println!( + "{GREEN}[trace]{RESET} write instructions to {:#016x}", + regs.rip + ); + + // Continue target + ptrace::cont(proc.get_pid(), None)?; + println!("{GREEN}[trace]{RESET} continue from {:#016x}", regs.rip); + proc.wait(); + + let regs = ptrace::getregs(proc.get_pid())?; + println!("{GREEN}[trace]{RESET} int3 at {:#016x}", regs.rip); + + Ok(()) +} + +pub fn inject3(proc: &Process, seg_rw: (u64, u64)) -> Result> // thread inject +{ + let regs = ptrace::getregs(proc.get_pid())?; + // Alloc rwx memory + + let injected_inst = assemble(regs.rip as u64, |asm| { + asm.mov(rax, 9u64)?; // Syscall 9 (mmap) + + asm.mov(rdi, 0u64)?; // Addr + asm.mov(rsi, 4096u64)?; // Length, we alloc a page (4K) + asm.mov( + rdx, + (libc::PROT_READ | libc::PROT_WRITE | 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 + asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow + Ok(()) + })?; + + proc.write_memory_ptrace(regs.rip as usize, &injected_inst)?; + println!( + "{GREEN}[trace]{RESET} write instructions to {:#016x}", + regs.rip + ); + + // Continue target + ptrace::cont(proc.get_pid(), None)?; + println!("{GREEN}[trace]{RESET} continue from {:#016x}", regs.rip); + proc.wait(); + + println!( + "{GREEN}[trace]{RESET} int3 at {:#016x}", + ptrace::getregs(proc.get_pid())?.rip + ); + + let regs_after_map = ptrace::getregs(proc.get_pid())?; + let page_addr = regs_after_map.rax as usize; + println!( + "{GREEN}[trace]{RESET} allocated page is at {:#016x}", + page_addr + ); + + let injected_data = "[%d] I am the injected thread, I am running... \r\n"; + proc.write_memory_vm( + page_addr + 0x200, + &CString::new(injected_data).unwrap().as_bytes_with_nul(), + )?; + proc.write_memory_vm(page_addr + 0x300, &1i64.to_le_bytes())?; + proc.write_memory_vm(page_addr + 0x308, &0u64.to_le_bytes())?; + + let printf = proc.find_remote_proc("/usr/lib/libc.so.6", "printf").unwrap(); + + // Construct inject payload + let injected_payload = assemble(page_addr as u64, |asm| { + let mut target_label = asm.create_label(); + + asm.mov(rbp, rsp)?; + asm.mov(rcx, 0u64)?; + asm.set_label(&mut target_label)?; + + asm.add(rcx, 1i32)?; + asm.push(rcx)?; + + asm.mov(rdi, (page_addr + 0x200) as u64)?; + asm.mov(rsi, rcx)?; + asm.call(printf)?; + + asm.mov(rax, 35u64)?; // Syscall 35 (nano sleep) + asm.mov(rdi, (page_addr + 0x300) as u64)?; // Req + asm.mov(rsi, 0u64)?; //Rem + asm.syscall()?; // Syscall interrupt + + asm.pop(rcx)?; + asm.jmp(target_label)?; // Jmp back to loop + Ok(()) + })?; + proc.write_memory_vm(page_addr, &injected_payload)?; + println!("{GREEN}[trace]{RESET} write payload to {:#016x}", page_addr); + + // Start Trigger + // let regs = ptrace::getregs(proc.get_pid())?; + ptrace::setregs(proc.get_pid(), regs)?; + + let injected_trigger = assemble(regs.rip as u64, |asm| { + asm.mov(rax, 56u64)?; // Syscall 56 (clone) + + asm.mov( + rdi, + (libc::CLONE_VM + | libc::CLONE_FS + | libc::CLONE_FILES + | libc::CLONE_SIGHAND + | libc::CLONE_THREAD) as u64, + )?; // Flags + asm.mov(rsi, (page_addr + 0x1000) as u64)?; // Stack top + + asm.mov(rdx, 0u64)?; // parent_tid = NULL + asm.mov(r10, 0u64)?; // child_tid = NULL + asm.mov(r8, 0u64)?; // tls = NULL + + asm.syscall()?; // Syscall interrupt + asm.test(eax, eax)?; // Syscall returns zero? + asm.jz(page_addr as u64)?; + + asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow + + Ok(()) + })?; + + proc.write_memory_ptrace(regs.rip as usize, &injected_trigger)?; + println!("{GREEN}[trace]{RESET} write trigger to {:#016x}", regs.rip); + + ptrace::cont(proc.get_pid(), None)?; + println!("{GREEN}[trace]{RESET} continue from {:#016x}", regs.rip); + proc.wait(); + + let regs = ptrace::getregs(proc.get_pid())?; + let pid_new_thread = Pid::from_raw(regs.rax as i32); + println!("{GREEN}[trace]{RESET} int3 at {:#016x}", regs.rip); + println!( + "{GREEN}[trace]{RESET} new thread is {}, which will be suspend.", + pid_new_thread + ); + + ptrace::attach(pid_new_thread)?; + waitpid(pid_new_thread, Some(WaitPidFlag::WUNTRACED))?; + println!("{GREEN}[trace]{RESET} attached new thread."); + + loop { + let regs = ptrace::getregs(pid_new_thread)?; + println!( + "{GREEN}[trace]{RESET} rip in new thread is {:#016x}.", + regs.rip + ); + + if regs.rip >= page_addr as u64 && regs.rip < (page_addr + 0x1000) as u64 { + println!("{GREEN}[trace]{RESET} rip in new thread return to inject payload."); + break; + } + + ptrace::step(pid_new_thread, None)?; + waitpid(pid_new_thread, Some(WaitPidFlag::WUNTRACED))?; + } + + Ok(pid_new_thread.as_raw()) +} diff --git a/01/project-hbj-attacker/src/helper/map.rs b/01/project-hbj-attacker/src/helper/map.rs index 8684720..83550da 100644 --- a/01/project-hbj-attacker/src/helper/map.rs +++ b/01/project-hbj-attacker/src/helper/map.rs @@ -1,90 +1,109 @@ -pub fn is_address_in_range(addr: u64, range_strings: &Vec<&str>) -> bool { - for range_str in range_strings { - if let Some((start, end)) = parse_address_range(range_str) { - if addr >= start && addr < end { - return true; - } - } - } - false +use crate::helper::ExecuteLinkFile; + +#[derive(Debug, Clone)] +pub struct MemoryRegion { + pub start_addr: u64, + pub end_addr: u64, + pub perms: String, + pub offset: Option, + pub dev: Option, + pub inode: Option, + pub pathname: Option, } -fn parse_address_range(range_str: &str) -> Option<(u64, u64)> { - let parts: Vec<&str> = range_str.split_whitespace().collect(); - if parts.is_empty() { - return None; - } - - let range_part = parts[0]; - - let range_parts: Vec<&str> = range_part.split('-').collect(); - if range_parts.len() != 2 { - return None; - } - - let start_addr = u64::from_str_radix(range_parts[0], 16).ok()?; - let end_addr = u64::from_str_radix(range_parts[1], 16).ok()?; - - Some((start_addr, end_addr)) -} - -pub fn first_rw_segment(range_strings: &Vec<&str>) -> Option<(u64, u64)> { - for range_str in range_strings { - let parts: Vec<&str> = range_str.split_whitespace().collect(); +impl MemoryRegion { + pub fn parse(line: &str) -> Option { + let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() < 2 { - continue; + return None; } - let perms = parts[1]; - if perms.starts_with("rw") { - if let Some((start, end)) = parse_address_range(range_str) { - return Some((start, end)); - } + let range_part = parts[0]; + let range_parts: Vec<&str> = range_part.split('-').collect(); + if range_parts.len() != 2 { + return None; } - } - None -} + let start_addr = u64::from_str_radix(range_parts[0], 16).ok()?; + let end_addr = u64::from_str_radix(range_parts[1], 16).ok()?; -pub fn first_exec_segment(range_strings: &Vec<&str>) -> Option<(u64, u64)> { - for range_str in range_strings { - let parts: Vec<&str> = range_str.split_whitespace().collect(); - if parts.len() < 2 { - continue; - } + let perms = parts[1].to_string(); - let perms = parts[1]; - if perms.contains('x') { - if let Some((start, end)) = parse_address_range(range_str) { - return Some((start, end)); - } - } - } - None -} + let offset = parts.get(2).and_then(|s| u64::from_str_radix(s, 16).ok()); + let dev = parts.get(3).map(|s| s.to_string()); + let inode = parts.get(4).and_then(|s| s.parse::().ok()); + let pathname = parts.get(5).map(|s| s.to_string()); -pub fn module_base_address(range_strings: &Vec<&str>, module_name: &str) -> Option { - let mut base_addr: Option = None; - - for range_str in range_strings { - let parts: Vec<&str> = range_str.split_whitespace().collect(); - if parts.len() < 6 { - continue; - } - - let path = parts.last().unwrap(); - - if let Some(filename) = std::path::Path::new(path).file_name().and_then(|f| f.to_str()) { - if filename.contains(module_name) { - if let Some((start, _)) = parse_address_range(range_str) { - base_addr = match base_addr { - Some(current_min) => Some(current_min.min(start)), - None => Some(start), - }; - } - } - } + Some(Self { + start_addr, + end_addr, + perms, + offset, + dev, + inode, + pathname, + }) } - base_addr + pub fn is_read_write(&self) -> bool { + self.perms.starts_with("rw") + } + + pub fn is_executable(&self) -> bool { + self.perms.contains('x') + } +} + +#[derive(Debug)] +pub struct MemoryMap { + regions: Vec, +} + +impl MemoryMap { + pub fn new(lines: &Vec<&str>) -> Self { + let regions = lines + .iter() + .filter_map(|line| MemoryRegion::parse(line)) + .collect(); + Self { regions } + } + + pub fn first_rw_segment(&self, module: &str) -> Option<(u64, u64)> { + self.regions + .iter() + .find(|r| r.is_read_write() && r.pathname.as_deref() == Some(module)) + .map(|r| (r.start_addr, r.end_addr)) + } + + pub fn first_exec_segment(&self, module: &str) -> Option<(u64, u64)> { + self.regions + .iter() + .find(|r| r.is_executable() && r.pathname.as_deref() == Some(module)) + .map(|r| (r.start_addr, r.end_addr)) + } + + pub fn module_base_address(&self, module: &str) -> Option { + let elf = ExecuteLinkFile::prase(&module).ok()?; + let loads = elf.get_loads().ok()?; + let Some(first_load) = loads.first() 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) + }) 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-attacker/src/helper/processes.rs b/01/project-hbj-attacker/src/helper/processes.rs index 2280a13..f716a7a 100644 --- a/01/project-hbj-attacker/src/helper/processes.rs +++ b/01/project-hbj-attacker/src/helper/processes.rs @@ -7,41 +7,16 @@ use std::error::Error; use std::ffi::CString; use std::io::{IoSliceMut, IoSlice}; use nix::sys::ptrace; -use std::mem; + use libc::{dlsym, RTLD_NEXT}; -use crate::helper::module_base_address; use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; +use crate::helper::ExecuteLinkFile; +use crate::helper::map::MemoryMap; + const GREEN: &str = "\x1b[32m"; const RESET: &str = "\x1b[0m"; -pub fn wait(pid: Pid) -> Option -{ - let f = waitpid(pid, Some(WaitPidFlag::WUNTRACED)).ok()?; - - match f { - WaitStatus::Stopped(stopped_pid, signal) => { - println!("[DEBUG] PID {} stopped by signal: {:?}", stopped_pid, signal); - } - WaitStatus::Exited(exited_pid, status) => { - println!("[DEBUG] PID {} exited with status: {}", exited_pid, status); - } - WaitStatus::Signaled(signaled_pid, signal, core_dump) => { - println!("[DEBUG] PID {} killed by signal: {:?} (core dump: {})", - signaled_pid, signal, core_dump); - } - WaitStatus::Continued(continued_pid) => { - println!("[DEBUG] PID {} continued", continued_pid); - } - WaitStatus::StillAlive => { - println!("[DEBUG] PID {} still alive", pid); - } - _ => {} - } - - Some(f) -} - fn list_processes() -> Result, std::io::Error> { let mut processes = HashMap::::new(); @@ -64,7 +39,6 @@ fn list_processes() -> Result, std::io::Error> if let Some(name) = name_path.split("/").last() { processes.insert(name.to_string(), pid); - println!("{} -> {}", name, dir); } } @@ -77,169 +51,211 @@ pub fn get_pid_by_name(name: &str) -> Result Ok(ps[name]) } -pub fn read_memory_vm(pid: Pid, start_addr: usize, size: usize) -> Result, Box> { - let mut buffer = vec![0u8; size]; - - let mut local_iov = [IoSliceMut::new(&mut buffer)]; - - let remote_iov = [RemoteIoVec { - base: start_addr, - len: size, - }]; - - let bytes_read = process_vm_readv(pid, &mut local_iov, &remote_iov)?; - - if bytes_read == size { - Ok(buffer) - } else { - buffer.truncate(bytes_read); - Ok(buffer) - } +pub struct Process +{ + pid: Pid, + map: MemoryMap, } -pub fn write_memory_vm(pid: Pid, mut start_addr: usize, mut data: &[u8]) -> Result> { - let mut total_written = 0usize; - while !data.is_empty() { - let local_iov = [IoSlice::new(data)]; - let remote_iov = [RemoteIoVec { - base: start_addr, - len: data.len(), - }]; +impl Process +{ + fn write_unaligned_head( + pid: Pid, + addr: usize, + data: &[u8], + word_size: usize, + ) -> Result> + { + let head_offset = addr % word_size; + let aligned_addr = addr - head_offset; + let orig_word = ptrace::read(pid, aligned_addr as *mut libc::c_void)?; + let mut bytes = orig_word.to_ne_bytes(); - let written = process_vm_writev(pid, &local_iov, &remote_iov)?; + 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); - if written == 0 { - return Err(format!("process_vm_writev returned 0 (no progress) after writing {} bytes", total_written).into()); + ptrace::write(pid, aligned_addr as *mut libc::c_void, new_word)?; + Ok(copy_len) + } + + fn write_full_word( + pid: Pid, + addr: usize, + data: &[u8] + ) -> Result> + { + let mut arr = [0u8; size_of::()]; + arr.copy_from_slice(data); + let val = libc::c_long::from_ne_bytes(arr); + ptrace::write(pid, addr as *mut libc::c_void, val)?; + Ok(size_of::()) + } + + fn write_unaligned_tail( + pid: Pid, + addr: usize, + data: &[u8], + _word_size: usize, + ) -> Result> + { + 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); + + ptrace::write(pid, addr as *mut libc::c_void, new_word)?; + Ok(data.len()) + } + + pub fn new(pid: Pid) -> Result> + { + let maps = fs::read_to_string(format!("/proc/{}/maps", pid))?; + let map = MemoryMap::new(&maps.lines().filter(|&line| !line.is_empty()).collect::>()); + + Ok(Self { pid, map, }) + } + + pub fn wait(&self) -> Option + { + let f = waitpid(self.pid, Some(WaitPidFlag::WUNTRACED)).ok()?; + + match f { + WaitStatus::Stopped(stopped_pid, signal) => { + println!("[DEBUG] PID {} stopped by signal: {:?}", stopped_pid, signal); + } + WaitStatus::Exited(exited_pid, status) => { + println!("[DEBUG] PID {} exited with status: {}", exited_pid, status); + } + WaitStatus::Signaled(signaled_pid, signal, core_dump) => { + println!("[DEBUG] PID {} killed by signal: {:?} (core dump: {})", + signaled_pid, signal, core_dump); + } + WaitStatus::Continued(continued_pid) => { + println!("[DEBUG] PID {} continued", continued_pid); + } + WaitStatus::StillAlive => { + println!("[DEBUG] PID {} still alive", self.pid); + } + _ => {} } - total_written += written; - start_addr = start_addr.wrapping_add(written); - data = &data[written..]; + Some(f) } - Ok(total_written) -} + pub fn get_pid(&self) -> Pid { self.pid.clone() } -pub fn write_memory_ptrace(pid: Pid, start_addr: usize, data: &[u8]) -> Result> { - let word_size = size_of::(); - if word_size == 0 { - return Err("invalid word size".into()); + pub fn get_exe(&self) -> Result> + { + let r = fs::read_link(format!("/proc/{}/exe", self.pid))? + .to_string_lossy() + .into_owned(); + + Ok(r) } - let mut addr = start_addr; - let mut remaining = data; - let mut written = 0usize; + pub fn get_map_str(&self) -> Result> + { + let r = fs::read_to_string(format!("/proc/{}/maps", self.pid))?; - if addr % word_size != 0 && !remaining.is_empty() { - let n = write_unaligned_head(pid, addr, remaining, word_size)?; - addr += n; - remaining = &remaining[n..]; - written += n; + Ok(r) } - while remaining.len() >= word_size { - let n = write_full_word(pid, addr, &remaining[..word_size])?; - addr += n; - remaining = &remaining[n..]; - written += n; + pub fn read_memory_vm(&self, start_addr: usize, size: usize) -> Result, Box> + { + let mut buffer = vec![0u8; size]; + + let mut local_iov = [IoSliceMut::new(&mut buffer)]; + + let remote_iov = [RemoteIoVec { + base: start_addr, + len: size, + }]; + + let bytes_read = process_vm_readv(self.pid, &mut local_iov, &remote_iov)?; + + if bytes_read == size { + Ok(buffer) + } else { + buffer.truncate(bytes_read); + Ok(buffer) + } } - if !remaining.is_empty() { - let n = write_unaligned_tail(pid, addr, remaining, word_size)?; - written += n; + pub fn write_memory_vm(&self, mut start_addr: usize, mut data: &[u8]) -> Result> + { + let mut total_written = 0usize; + while !data.is_empty() { + let local_iov = [IoSlice::new(data)]; + let remote_iov = [RemoteIoVec { + base: start_addr, + len: data.len(), + }]; + + let written = process_vm_writev(self.pid, &local_iov, &remote_iov)?; + + if written == 0 { + return Err(format!("process_vm_writev returned 0 (no progress) after writing {} bytes", total_written).into()); + } + + total_written += written; + start_addr = start_addr.wrapping_add(written); + data = &data[written..]; + } + + Ok(total_written) } - Ok(written) -} + pub fn write_memory_ptrace(&self, start_addr: usize, data: &[u8]) -> Result> + { + let word_size = size_of::(); + if word_size == 0 { + return Err("invalid word size".into()); + } -fn write_unaligned_head( - pid: Pid, - addr: usize, - data: &[u8], - word_size: usize, -) -> Result> { - let head_offset = addr % word_size; - let aligned_addr = addr - head_offset; - let orig_word = ptrace::read(pid, aligned_addr as *mut libc::c_void)?; - let mut bytes = orig_word.to_ne_bytes(); + let mut addr = start_addr; + let mut remaining = data; + let mut written = 0usize; - 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); + if addr % word_size != 0 && !remaining.is_empty() { + let n = Self::write_unaligned_head(self.pid, addr, remaining, word_size)?; + addr += n; + remaining = &remaining[n..]; + written += n; + } - ptrace::write(pid, aligned_addr as *mut libc::c_void, new_word)?; - Ok(copy_len) -} + while remaining.len() >= word_size { + let n = Self::write_full_word(self.pid, addr, &remaining[..word_size])?; + addr += n; + remaining = &remaining[n..]; + written += n; + } -fn write_full_word(pid: Pid, addr: usize, data: &[u8]) -> Result> { - let mut arr = [0u8; size_of::()]; - arr.copy_from_slice(data); - let val = libc::c_long::from_ne_bytes(arr); - ptrace::write(pid, addr as *mut libc::c_void, val)?; - Ok(size_of::()) -} + if !remaining.is_empty() { + let n = Self::write_unaligned_tail(self.pid, addr, remaining, word_size)?; + written += n; + } -fn write_unaligned_tail( - pid: Pid, - addr: usize, - data: &[u8], - _word_size: usize, -) -> Result> { - 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); - - ptrace::write(pid, addr as *mut libc::c_void, new_word)?; - Ok(data.len()) -} - -pub fn find_remote_proc(module: &str, symbol: &str, pid: Pid) -> Option -{ - // Read our own process memory maps to find module base address - let self_maps = fs::read_to_string("/proc/self/maps").ok()?; - let self_map_lines = self_maps.lines().collect::>(); - let symbol_offset: u64; - - // Find module base address in our own process - let Some(module_base_local) = module_base_address(&self_map_lines, module) else { - return None; - }; - - println!("{GREEN}[local]{RESET} {module} base: {:#016x}", module_base_local); - - // Use dlsym to get the address of symbol in our own process - unsafe { - let symbol_addr_local = dlsym(RTLD_NEXT, CString::new(symbol).unwrap().as_bytes_with_nul().as_ptr() as *const _); - // Calculate offset of symbol from module base in our process - symbol_offset = symbol_addr_local as u64 - module_base_local; + Ok(written) } - println!( - "{GREEN}[local]{RESET} {symbol} offset = {:#016x}", - symbol_offset - ); + pub fn find_remote_proc(&self, module: &str, symbol: &str) -> 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)?; - // Read target process memory maps to find its module base address - let target_maps = fs::read_to_string(format!("/proc/{}/maps", pid)).ok()?; - let target_map_lines = target_maps.lines().collect::>(); + let elf = ExecuteLinkFile::prase(module).ok()?; + let sym = elf.prase_dyn_sym(symbol).ok()?; - // Find module base address in target process - let Some(module_base_target) = module_base_address(&target_map_lines, module) else { - return None; - }; + Some(sym.st_value + base) + } - println!( - "{GREEN}[trace]{RESET} {module} base = {:#016x}", - module_base_target - ); + pub fn find_got_pointer_plt(&self, symbol: &str) -> Option + { + let exe = self.get_exe().ok()?; + let elf = ExecuteLinkFile::prase(&exe).ok()?; - // Calculate symbol address in target process using the same offset - let target_symbol_addr = module_base_target + symbol_offset; - println!( - "{GREEN}[trace]{RESET} {symbol} address = {:#016x}", - target_symbol_addr - ); - - Some(target_symbol_addr) + let r_sym = elf.get_rela_sym(symbol).ok()?; + Some(r_sym.r_offset + self.map.module_base_address(&exe)?) + } } \ No newline at end of file diff --git a/01/project-hbj-attacker/src/main.rs b/01/project-hbj-attacker/src/main.rs index 564ac69..42f987f 100644 --- a/01/project-hbj-attacker/src/main.rs +++ b/01/project-hbj-attacker/src/main.rs @@ -18,269 +18,29 @@ use std::io::BufRead; const GREEN: &str = "\x1b[32m"; const RESET: &str = "\x1b[0m"; -fn inject1(pid: Pid, seg_rw: (u64, u64)) -> Result<(), Box> // Simple injection -{ - let regs = ptrace::getregs(pid)?; - let injected_data = "You are injected. \r\n"; - write_memory_vm(pid, seg_rw.0 as usize, &injected_data.as_bytes())?; - println!( - "{GREEN}[trace]{RESET} write \"{}\" to {:#016x}", - injected_data, seg_rw.0 - ); - - let injected_inst = assemble(regs.rip as u64, |asm| { - asm.mov(rax, 1u64)?; // Syscall 1 (write) - asm.mov(rdi, 1u64)?; // Fd 1 (STDOUT) - asm.mov(rsi, seg_rw.0)?; // Buffer pointer (Here is rw segment in target) - asm.mov(rdx, injected_data.as_bytes().len() as u64)?; // Buffer length - asm.syscall()?; // Syscall interrupt - asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow - Ok(()) - })?; - - write_memory_ptrace(pid, regs.rip as usize, &injected_inst)?; - println!( - "{GREEN}[trace]{RESET} write instructions to {:#016x}", - regs.rip - ); - - // Continue target - ptrace::cont(pid, None)?; - println!("{GREEN}[trace]{RESET} continue from {:#016x}", regs.rip); - wait(pid); - - let regs = ptrace::getregs(pid)?; - println!("{GREEN}[trace]{RESET} int3 at {:#016x}", regs.rip); - - Ok(()) -} - -fn inject2(pid: Pid, seg_rw: (u64, u64)) -> Result<(), Box> // ld injection -{ - let regs = ptrace::getregs(pid)?; - // Get the absolute path to our shared library - let lib_path = fs::canonicalize("./target/debug/libproject_hbj_attacker.so")? - .to_string_lossy() - .into_owned(); - - // Start Inject - let c_lib_path = CString::new(lib_path).unwrap(); - write_memory_vm(pid, seg_rw.0 as usize, c_lib_path.as_bytes_with_nul())?; - println!( - "{GREEN}[trace]{RESET} write {} to {:#016x}", - &c_lib_path.to_string_lossy(), - seg_rw.0 - ); - - let Some(target_dlopen_addr) = find_remote_proc("libc.so", "dlopen", pid) else { - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "first rw segment not found", - ))); - }; - - let injected_inst = assemble(regs.rip as u64, |asm| { - asm.mov(rdi, seg_rw.0)?; // Param 1: Filename - asm.mov(rsi, 2u64)?; // Param 2: Flag, 2(RTLD_NOW) - asm.call(target_dlopen_addr)?; // Syscall interrupt - asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow - Ok(()) - })?; - - write_memory_ptrace(pid, regs.rip as usize, &injected_inst)?; - println!( - "{GREEN}[trace]{RESET} write instructions to {:#016x}", - regs.rip - ); - - // Continue target - ptrace::cont(pid, None)?; - println!("{GREEN}[trace]{RESET} continue from {:#016x}", regs.rip); - wait(pid); - - let regs = ptrace::getregs(pid)?; - println!("{GREEN}[trace]{RESET} int3 at {:#016x}", regs.rip); - - Ok(()) -} - -fn inject3(pid: Pid, seg_rw: (u64, u64)) -> Result> // thread inject -{ - let regs = ptrace::getregs(pid)?; - // Alloc rwx memory - - let injected_inst = assemble(regs.rip as u64, |asm| { - asm.mov(rax, 9u64)?; // Syscall 9 (mmap) - - asm.mov(rdi, 0u64)?; // Addr - asm.mov(rsi, 4096u64)?; // Length, we alloc a page (4K) - asm.mov( - rdx, - (libc::PROT_READ | libc::PROT_WRITE | 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 - asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow - Ok(()) - })?; - - write_memory_ptrace(pid, regs.rip as usize, &injected_inst)?; - println!( - "{GREEN}[trace]{RESET} write instructions to {:#016x}", - regs.rip - ); - - // Continue target - ptrace::cont(pid, None)?; - println!("{GREEN}[trace]{RESET} continue from {:#016x}", regs.rip); - wait(pid); - - println!( - "{GREEN}[trace]{RESET} int3 at {:#016x}", - ptrace::getregs(pid)?.rip - ); - - let regs_after_map = ptrace::getregs(pid)?; - let page_addr = regs_after_map.rax as usize; - println!( - "{GREEN}[trace]{RESET} allocated page is at {:#016x}", - page_addr - ); - - let injected_data = "[%d] I am the injected thread, I am running... \r\n"; - write_memory_vm( - pid, - page_addr + 0x200, - &CString::new(injected_data).unwrap().as_bytes_with_nul(), - )?; - write_memory_vm(pid, page_addr + 0x300, &1i64.to_le_bytes())?; - write_memory_vm(pid, page_addr + 0x308, &0u64.to_le_bytes())?; - - let printf = find_remote_proc("libc.so", "printf", pid).unwrap(); - - // Construct inject payload - let injected_payload = assemble(page_addr as u64, |asm| { - let mut target_label = asm.create_label(); - - asm.mov(rbp, rsp)?; - asm.mov(rcx, 0u64)?; - asm.set_label(&mut target_label)?; - - asm.add(rcx, 1i32)?; - asm.push(rcx)?; - - asm.mov(rdi, (page_addr + 0x200) as u64)?; - asm.mov(rsi, rcx)?; - asm.call(printf)?; - - asm.mov(rax, 35u64)?; // Syscall 35 (nano sleep) - asm.mov(rdi, (page_addr + 0x300) as u64)?; // Req - asm.mov(rsi, 0u64)?; //Rem - asm.syscall()?; // Syscall interrupt - - asm.pop(rcx)?; - asm.jmp(target_label)?; // Jmp back to loop - Ok(()) - })?; - write_memory_vm(pid, page_addr, &injected_payload)?; - println!("{GREEN}[trace]{RESET} write payload to {:#016x}", page_addr); - - // Start Trigger - // let regs = ptrace::getregs(pid)?; - ptrace::setregs(pid, regs)?; - - let injected_trigger = assemble(regs.rip as u64, |asm| { - asm.mov(rax, 56u64)?; // Syscall 56 (clone) - - asm.mov( - rdi, - (libc::CLONE_VM - | libc::CLONE_FS - | libc::CLONE_FILES - | libc::CLONE_SIGHAND - | libc::CLONE_THREAD) as u64, - )?; // Flags - asm.mov(rsi, (page_addr + 0x1000) as u64)?; // Stack top - - asm.mov(rdx, 0u64)?; // parent_tid = NULL - asm.mov(r10, 0u64)?; // child_tid = NULL - asm.mov(r8, 0u64)?; // tls = NULL - - asm.syscall()?; // Syscall interrupt - asm.test(eax, eax)?; // Syscall returns zero? - asm.jz(page_addr as u64)?; - - asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow - - Ok(()) - })?; - - write_memory_ptrace(pid, regs.rip as usize, &injected_trigger)?; - println!("{GREEN}[trace]{RESET} write trigger to {:#016x}", regs.rip); - - ptrace::cont(pid, None)?; - println!("{GREEN}[trace]{RESET} continue from {:#016x}", regs.rip); - wait(pid); - - let regs = ptrace::getregs(pid)?; - let pid_new_thread = Pid::from_raw(regs.rax as i32); - println!("{GREEN}[trace]{RESET} int3 at {:#016x}", regs.rip); - println!( - "{GREEN}[trace]{RESET} new thread is {}, which will be suspend.", - pid_new_thread - ); - - ptrace::attach(pid_new_thread)?; - wait(pid_new_thread); - println!("{GREEN}[trace]{RESET} attached new thread."); - - loop { - let regs = ptrace::getregs(pid_new_thread)?; - println!( - "{GREEN}[trace]{RESET} rip in new thread is {:#016x}.", - regs.rip - ); - - if regs.rip >= page_addr as u64 && regs.rip < (page_addr + 0x1000) as u64 { - println!("{GREEN}[trace]{RESET} rip in new thread return to inject payload."); - break; - } - - ptrace::step(pid_new_thread, None)?; - wait(pid_new_thread); - } - - Ok(pid_new_thread.as_raw()) -} - fn main() -> Result<(), Box> { // Find our target program let pid = Pid::from_raw(get_pid_by_name("target")?); + let proc = Process::new(pid)?; - let target = fs::read_link(format!("/proc/{}/exe", pid))? - .to_string_lossy() - .into_owned(); - let content = fs::read_to_string(format!("/proc/{}/maps", pid))?; - let lines: Vec<&str> = content - .lines() - .filter(|&line| !line.is_empty() && line.contains(&target)) - .collect(); + let exe = proc.get_exe()?; + let maps = proc.get_map_str()?; + let lines: Vec<&str> = maps.lines().filter(|&line| !line.is_empty()).collect(); for line in &lines { println!("{GREEN}[memory map]{RESET} {}", line); } - let Some(seg_rw) = first_rw_segment(&lines) else { + let map = MemoryMap::new(&lines); + + let Some(seg_rw) = map.first_rw_segment(&exe) else { return Err(Box::new(std::io::Error::new( std::io::ErrorKind::Other, "first rw segment not found", ))); }; - let Some(seg_x) = first_exec_segment(&lines) else { + let Some(seg_x) = map.first_exec_segment(&exe) else { return Err(Box::new(std::io::Error::new( std::io::ErrorKind::Other, "first exec segment not found", @@ -288,18 +48,18 @@ fn main() -> Result<(), Box> { }; ptrace::attach(pid)?; - wait(pid); + proc.wait(); ptrace::step(pid, None)?; - wait(pid); + proc.wait(); // Save context let regs = ptrace::getregs(pid)?; // Save current registers - let buffer = read_memory_vm(pid, seg_x.0 as usize, 4096)?; // Save current memory context - let buffer_rw = read_memory_vm(pid, seg_rw.0 as usize, 4096)?; // Save current rw memory + let buffer = proc.read_memory_vm(seg_x.0 as usize, 4096)?; // Save current memory context + let buffer_rw = proc.read_memory_vm(seg_rw.0 as usize, 4096)?; // Save current rw memory println!("{GREEN}[trace]{RESET} Seg_x.0 is {:#016x}", seg_x.0); - write_memory_ptrace(pid, seg_x.0 as usize, &[0x90u8; 4096])?; + proc.write_memory_ptrace(seg_x.0 as usize, &[0x90u8; 4096])?; ptrace::setregs( pid, user_regs_struct { @@ -310,22 +70,17 @@ fn main() -> Result<(), Box> { // Do inject here - let c = inject3(pid, seg_rw)?; + let c = inject3(&proc, seg_rw)?; // End inject logics // Restore context ptrace::setregs(pid, regs)?; - write_memory_ptrace(pid, seg_x.0 as usize, &buffer)?; - write_memory_vm(pid, seg_rw.0 as usize, &buffer_rw)?; + proc.write_memory_ptrace(seg_x.0 as usize, &buffer)?; + proc.write_memory_vm(seg_rw.0 as usize, &buffer_rw)?; ptrace::detach(pid, None)?; ptrace::detach(Pid::from_raw(c), None)?; - let mut input = String::new(); - std::io::stdin() - .read_line(&mut input) - .expect("Failed to read line"); - Ok(()) } diff --git a/01/project-hbj-hook/.idea/.gitignore b/01/project-hbj-hook/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/01/project-hbj-hook/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/01/project-hbj-hook/.idea/modules.xml b/01/project-hbj-hook/.idea/modules.xml new file mode 100644 index 0000000..e8aaebf --- /dev/null +++ b/01/project-hbj-hook/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/01/project-hbj-hook/.idea/project-hbj-hook.iml b/01/project-hbj-hook/.idea/project-hbj-hook.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/01/project-hbj-hook/.idea/project-hbj-hook.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/01/project-hbj-hook/.idea/vcs.xml b/01/project-hbj-hook/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/01/project-hbj-hook/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/01/project-hbj-hook/Cargo.lock b/01/project-hbj-hook/Cargo.lock new file mode 100644 index 0000000..e60fa6e --- /dev/null +++ b/01/project-hbj-hook/Cargo.lock @@ -0,0 +1,302 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "capstone" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "015ef5d5ca1743e3f94af9509ba6bd2886523cfee46e48d15c2ef5216fd4ac9a" +dependencies = [ + "capstone-sys", + "libc", +] + +[[package]] +name = "capstone-sys" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2267cb8d16a1e4197863ec4284ffd1aec26fe7e57c58af46b02590a0235809a0" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "cc" +version = "1.2.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "ctor" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c9b8bdf64ee849747c1b12eb861d21aa47fa161564f48332f1afe2373bf899" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "dtor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "goblin" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51876e3748c4a347fe65b906f2b1ae46a1e55a497b22c94c1f4f2c469ff7673a" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "iced-x86" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + +[[package]] +name = "project-hbj-hook" +version = "0.1.0" +dependencies = [ + "anyhow", + "capstone", + "ctor", + "goblin", + "iced-x86", + "libc", + "memmap2", + "nix", + "ouroboros", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/01/project-hbj-hook/Cargo.toml b/01/project-hbj-hook/Cargo.toml new file mode 100644 index 0000000..cb925f7 --- /dev/null +++ b/01/project-hbj-hook/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "project-hbj-hook" +version = "0.1.0" +edition = "2024" + +[dependencies] +iced-x86 = { version = "1.21.0", features = ["code_asm"] } +libc = "0.2.177" +nix = { version = "0.30.1", features = ["ptrace", "uio", "signal"] } +ctor = "0.6.0" +goblin = "0.10.3" +memmap2 = "0.9.9" +capstone = "0.13.0" +anyhow = "1.0.100" +ouroboros = "0.18.5" diff --git a/01/project-hbj-hook/src/disassembly.rs b/01/project-hbj-hook/src/disassembly.rs new file mode 100644 index 0000000..bcefbb7 --- /dev/null +++ b/01/project-hbj-hook/src/disassembly.rs @@ -0,0 +1,16 @@ + +/* + let cs = Capstone::new() + .x86() + .mode(arch::x86::ArchMode::Mode64) + .syntax(arch::x86::ArchSyntax::Att) + .detail(true) + .build()?; + + let code = vec![0x55, 0x48, 0x8b, 0x05, 0xb8, 0x13, 0x00, 0x00]; + let insns = cs.disasm_all(&code, 0x1000)?; + + for insn in insns.iter() { + println!("{}", insn); + } + */ \ No newline at end of file diff --git a/01/project-hbj-hook/src/elf.rs b/01/project-hbj-hook/src/elf.rs new file mode 100644 index 0000000..7a23355 --- /dev/null +++ b/01/project-hbj-hook/src/elf.rs @@ -0,0 +1,111 @@ +use anyhow::{Context, bail}; +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; +use std::ops::Deref; +use ouroboros::self_referencing; + +fn open_mem_map(path: &str) -> Result> { + let file = File::open(path)?; + unsafe { Ok(Mmap::map(&file)?) } +} + +#[self_referencing] +pub struct ExecuteLinkFile { + data: Vec, + + #[borrows(data)] + #[covariant] + elf: Elf<'this> +} + +impl ExecuteLinkFile { + pub fn prase(path: &str) -> Result> { + let data = open_mem_map(path)?.deref().to_owned(); + let s = ExecuteLinkFileTryBuilder { + data, + elf_builder: |data_ref| { + Elf::parse(&data_ref) + } + }.try_build()?; + + Ok(s) + } + + pub fn get_loads(&self) -> Result, Box> { + let loads = self.borrow_elf() + .program_headers + .iter() + .filter_map(|ph| match ph.p_type { + PT_LOAD => Some(ph.to_owned()), + _ => None, + }) + .collect::>(); + + Ok(loads) + } + + pub fn get_dynamic(&self) -> Result> { + let dynamic = self.borrow_elf() + .program_headers + .iter() + .find(|ph| ph.p_type == PT_DYNAMIC) + .context("No PT_DYNAMIC segment found")?; + + Ok(dynamic.clone()) + } + + pub fn get_rela_sym(&self, name: &str) -> Result> { + let rela_plt = self.borrow_elf().pltrelocs.iter(); + + let sym = rela_plt + .filter(|rela| { + matches!(rela.r_type, R_X86_64_JUMP_SLOT) // R_X86_64_JUMP_SLOT + }) + .filter_map(|rela| { + let sym_index = rela.r_sym; + let Ok(sym) = self.get_dyn_sym(sym_index) else { + return None; + }; + let Ok(sym_name) = self.get_dyn_str(sym.st_name) else { + return None; + }; + + if sym_name == name { Some(rela) } else { None } + }) + .collect::>(); + + let first = sym + .first() + .context(format!("No symbol found with name {}", name))?; + + Ok(first.clone()) + } + + pub fn get_dyn_sym(&self, location: usize) -> Result> { + let dyn_sym = self.borrow_elf() + .dynsyms + .get(location) + .context(format!("No symbol found at location {}", location))?; + + Ok(dyn_sym.clone()) + } + + pub fn prase_dyn_sym(&self, name: &str) -> Result> { + let dyn_sym = self.borrow_elf() + .dynsyms.iter() + .find(|sym| self.get_dyn_str(sym.st_name).ok().as_deref() == Some(name)) + .context(format!("No symbol found with name {}", name))?; + + Ok(dyn_sym.clone()) + } + + pub fn get_dyn_str(&self, location: usize) -> Result> { + let str = self.borrow_elf() + .dynstrtab + .get_at(location) + .context(format!("Could not get dynstr at location {}", location))?; + + Ok(str.to_owned()) + } +} diff --git a/01/project-hbj-hook/src/main.rs b/01/project-hbj-hook/src/main.rs new file mode 100644 index 0000000..b412464 --- /dev/null +++ b/01/project-hbj-hook/src/main.rs @@ -0,0 +1,49 @@ +use crate::elf::ExecuteLinkFile; +use crate::map::MemoryMap; +use crate::processes::{get_pid_by_name, Process}; +use anyhow::Context; +use nix::unistd::Pid; +use std::fs; + +const GREEN: &str = "\x1b[32m"; +const RESET: &str = "\x1b[0m"; + +mod disassembly; +mod elf; +mod map; +mod processes; + +fn main() -> Result<(), Box> { + // Find our target program + let pid = Pid::from_raw(get_pid_by_name("target")?); + let process = Process::new(pid)?; + + let exe = process.get_exe()?; + let maps = process.get_map_str()?; + let lines: Vec<&str> = maps.lines().filter(|&line| !line.is_empty()).collect(); + + for line in &lines { + println!("{GREEN}[memory map]{RESET} {}", line); + } + + 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 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.")?; + + println!( + "{GREEN}[memory map]{RESET} real_write_addr = {:#016x}, got_write_addr = {:#016x}", + real_write_addr, got_write_addr + ); + + Ok(()) +} diff --git a/01/project-hbj-hook/src/map.rs b/01/project-hbj-hook/src/map.rs new file mode 100644 index 0000000..fe48098 --- /dev/null +++ b/01/project-hbj-hook/src/map.rs @@ -0,0 +1,108 @@ +use crate::elf::ExecuteLinkFile; + +#[derive(Debug, Clone)] +pub struct MemoryRegion { + pub start_addr: u64, + pub end_addr: u64, + pub perms: String, + pub offset: Option, + pub dev: Option, + pub inode: Option, + pub pathname: Option, +} + +impl MemoryRegion { + pub fn parse(line: &str) -> Option { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 2 { + return None; + } + + let range_part = parts[0]; + let range_parts: Vec<&str> = range_part.split('-').collect(); + if range_parts.len() != 2 { + return None; + } + let start_addr = u64::from_str_radix(range_parts[0], 16).ok()?; + let end_addr = u64::from_str_radix(range_parts[1], 16).ok()?; + + let perms = parts[1].to_string(); + + let offset = parts.get(2).and_then(|s| u64::from_str_radix(s, 16).ok()); + let dev = parts.get(3).map(|s| s.to_string()); + let inode = parts.get(4).and_then(|s| s.parse::().ok()); + let pathname = parts.get(5).map(|s| s.to_string()); + + Some(Self { + start_addr, + end_addr, + perms, + offset, + dev, + inode, + pathname, + }) + } + + pub fn is_read_write(&self) -> bool { + self.perms.starts_with("rw") + } + + pub fn is_executable(&self) -> bool { + self.perms.contains('x') + } +} + +#[derive(Debug)] +pub struct MemoryMap { + regions: Vec, +} + +impl MemoryMap { + pub fn new(lines: &Vec<&str>) -> Self { + let regions = lines + .iter() + .filter_map(|line| MemoryRegion::parse(line)) + .collect(); + Self { regions } + } + + pub fn first_rw_segment(&self, module: &str) -> Option<(u64, u64)> { + self.regions + .iter() + .find(|r| r.is_read_write() && r.pathname.as_deref() == Some(module)) + .map(|r| (r.start_addr, r.end_addr)) + } + + pub fn first_exec_segment(&self, module: &str) -> Option<(u64, u64)> { + self.regions + .iter() + .find(|r| r.is_executable() && r.pathname.as_deref() == Some(module)) + .map(|r| (r.start_addr, r.end_addr)) + } + + pub fn module_base_address(&self, module: &str) -> Option { + let elf = ExecuteLinkFile::prase(&module).ok()?; + let loads = elf.get_loads().ok()?; + let Some(first_load) = loads.first() 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) + }) 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 new file mode 100644 index 0000000..6f58321 --- /dev/null +++ b/01/project-hbj-hook/src/processes.rs @@ -0,0 +1,260 @@ +use std::collections::HashMap; +use std::fs; + +use nix::sys::uio::{process_vm_readv, RemoteIoVec, process_vm_writev}; +use nix::unistd::Pid; +use std::error::Error; +use std::ffi::CString; +use std::io::{IoSliceMut, IoSlice}; +use nix::sys::ptrace; + +use libc::{dlsym, RTLD_NEXT}; +use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; +use crate::elf::ExecuteLinkFile; +use crate::map::{MemoryMap}; + +const GREEN: &str = "\x1b[32m"; +const RESET: &str = "\x1b[0m"; + +fn list_processes() -> Result, std::io::Error> +{ + let mut processes = HashMap::::new(); + + let entries = fs::read_dir("/proc")?; + let dirs = entries.filter_map(|e| { + let e = e.ok()?; + let path = e.path(); + if path.is_dir() { + return path.file_name()?.to_str().map(|s| s.to_string()); + } + None:: + }).collect::>(); + + for dir in dirs { + let Ok(pid) = dir.parse::() else { continue }; + let pid_path = format!("/proc/{}/exe", dir); + let Ok(name_path) = fs::read_link(&pid_path) else { continue }; + let name_path = name_path.to_string_lossy().to_string(); + + if let Some(name) = name_path.split("/").last() { + processes.insert(name.to_string(), pid); + } + } + + Ok(processes) +} + +pub fn get_pid_by_name(name: &str) -> Result +{ + let ps = list_processes()?; + Ok(ps[name]) +} + +pub struct Process +{ + pid: Pid, + map: MemoryMap, +} + +impl Process +{ + fn write_unaligned_head( + pid: Pid, + addr: usize, + data: &[u8], + word_size: usize, + ) -> Result> + { + let head_offset = addr % word_size; + let aligned_addr = addr - head_offset; + let orig_word = ptrace::read(pid, aligned_addr as *mut libc::c_void)?; + let mut bytes = orig_word.to_ne_bytes(); + + 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); + + ptrace::write(pid, aligned_addr as *mut libc::c_void, new_word)?; + Ok(copy_len) + } + + fn write_full_word( + pid: Pid, + addr: usize, + data: &[u8] + ) -> Result> + { + let mut arr = [0u8; size_of::()]; + arr.copy_from_slice(data); + let val = libc::c_long::from_ne_bytes(arr); + ptrace::write(pid, addr as *mut libc::c_void, val)?; + Ok(size_of::()) + } + + fn write_unaligned_tail( + pid: Pid, + addr: usize, + data: &[u8], + _word_size: usize, + ) -> Result> + { + 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); + + ptrace::write(pid, addr as *mut libc::c_void, new_word)?; + Ok(data.len()) + } + + pub fn new(pid: Pid) -> Result> + { + let maps = fs::read_to_string(format!("/proc/{}/maps", pid))?; + let map = MemoryMap::new(&maps.lines().filter(|&line| !line.is_empty()).collect::>()); + + Ok(Self { pid, map, }) + } + + pub fn wait(&self) -> Option + { + let f = waitpid(self.pid, Some(WaitPidFlag::WUNTRACED)).ok()?; + + match f { + WaitStatus::Stopped(stopped_pid, signal) => { + println!("[DEBUG] PID {} stopped by signal: {:?}", stopped_pid, signal); + } + WaitStatus::Exited(exited_pid, status) => { + println!("[DEBUG] PID {} exited with status: {}", exited_pid, status); + } + WaitStatus::Signaled(signaled_pid, signal, core_dump) => { + println!("[DEBUG] PID {} killed by signal: {:?} (core dump: {})", + signaled_pid, signal, core_dump); + } + WaitStatus::Continued(continued_pid) => { + println!("[DEBUG] PID {} continued", continued_pid); + } + WaitStatus::StillAlive => { + println!("[DEBUG] PID {} still alive", self.pid); + } + _ => {} + } + + Some(f) + } + + pub fn get_pid(&self) -> Pid { self.pid.clone() } + + pub fn get_exe(&self) -> Result> + { + let r = fs::read_link(format!("/proc/{}/exe", self.pid))? + .to_string_lossy() + .into_owned(); + + Ok(r) + } + + pub fn get_map_str(&self) -> Result> + { + let r = fs::read_to_string(format!("/proc/{}/maps", self.pid))?; + + Ok(r) + } + + pub fn read_memory_vm(&self, start_addr: usize, size: usize) -> Result, Box> + { + let mut buffer = vec![0u8; size]; + + let mut local_iov = [IoSliceMut::new(&mut buffer)]; + + let remote_iov = [RemoteIoVec { + base: start_addr, + len: size, + }]; + + let bytes_read = process_vm_readv(self.pid, &mut local_iov, &remote_iov)?; + + if bytes_read == size { + Ok(buffer) + } else { + buffer.truncate(bytes_read); + Ok(buffer) + } + } + + pub fn write_memory_vm(&self, mut start_addr: usize, mut data: &[u8]) -> Result> + { + let mut total_written = 0usize; + while !data.is_empty() { + let local_iov = [IoSlice::new(data)]; + let remote_iov = [RemoteIoVec { + base: start_addr, + len: data.len(), + }]; + + let written = process_vm_writev(self.pid, &local_iov, &remote_iov)?; + + if written == 0 { + return Err(format!("process_vm_writev returned 0 (no progress) after writing {} bytes", total_written).into()); + } + + total_written += written; + start_addr = start_addr.wrapping_add(written); + data = &data[written..]; + } + + Ok(total_written) + } + + pub fn write_memory_ptrace(&self, start_addr: usize, data: &[u8]) -> Result> + { + let word_size = size_of::(); + if word_size == 0 { + return Err("invalid word size".into()); + } + + let mut addr = start_addr; + let mut remaining = data; + let mut written = 0usize; + + if addr % word_size != 0 && !remaining.is_empty() { + let n = Self::write_unaligned_head(self.pid, addr, remaining, word_size)?; + addr += n; + remaining = &remaining[n..]; + written += n; + } + + while remaining.len() >= word_size { + let n = Self::write_full_word(self.pid, addr, &remaining[..word_size])?; + addr += n; + remaining = &remaining[n..]; + written += n; + } + + if !remaining.is_empty() { + let n = Self::write_unaligned_tail(self.pid, addr, remaining, word_size)?; + written += n; + } + + Ok(written) + } + + pub fn find_remote_proc(&self, module: &str, symbol: &str) -> 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) + } + + pub fn find_got_pointer_plt(&self, symbol: &str) -> Option + { + let exe = self.get_exe().ok()?; + let elf = ExecuteLinkFile::prase(&exe).ok()?; + + let r_sym = elf.get_rela_sym(symbol).ok()?; + Some(r_sym.r_offset + self.map.module_base_address(&exe)?) + } +} \ No newline at end of file