[feat] plt hook inject
This commit is contained in:
		
							
								
								
									
										12
									
								
								01/project-hbj-hook/src/asm.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								01/project-hbj-hook/src/asm.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  |  | ||||||
|  | use iced_x86::code_asm::asm_traits::CodeAsmJmp; | ||||||
|  | use iced_x86::{Instruction, code_asm::*}; | ||||||
|  |  | ||||||
|  | pub fn assemble<F>(addr: u64, op: F) -> Result<Vec<u8>, Box<dyn std::error::Error>> | ||||||
|  | where | ||||||
|  |     F: Fn(&mut CodeAssembler) -> Result<(), Box<dyn std::error::Error>>, | ||||||
|  | { | ||||||
|  |     let mut asm = CodeAssembler::new(64)?; | ||||||
|  |     _ = op(&mut asm); | ||||||
|  |     Ok(asm.assemble(addr)?) | ||||||
|  | } | ||||||
| @@ -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 goblin::elf::{Elf, ProgramHeader, Sym, program_header::PT_DYNAMIC, program_header::PT_LOAD, reloc::R_X86_64_JUMP_SLOT, Reloc}; | ||||||
| use memmap2::Mmap; | use memmap2::Mmap; | ||||||
| use std::fs::File; | use std::fs::File; | ||||||
| @@ -108,4 +111,9 @@ impl ExecuteLinkFile { | |||||||
|  |  | ||||||
|         Ok(str.to_owned()) |         Ok(str.to_owned()) | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  |     pub fn get_e_type(&self) -> u16 | ||||||
|  |     { | ||||||
|  |         self.borrow_elf().header.e_type | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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::map::MemoryMap; | ||||||
| use crate::processes::{get_pid_by_name, Process}; | use crate::processes::{get_pid_by_name, Process}; | ||||||
| use anyhow::Context; |  | ||||||
| use nix::unistd::Pid; | 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 GREEN: &str = "\x1b[32m"; | ||||||
| const RESET: &str = "\x1b[0m"; | const RESET: &str = "\x1b[0m"; | ||||||
| @@ -12,8 +18,61 @@ mod disassembly; | |||||||
| mod elf; | mod elf; | ||||||
| mod map; | mod map; | ||||||
| mod processes; | mod processes; | ||||||
|  | mod asm; | ||||||
|  |  | ||||||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | pub fn plt_hook<F>(proc: &Process, map: &MemoryMap, symbol: &str, payload: F) | ||||||
|  |     -> Result<(), Box<dyn Error>> | ||||||
|  | where | ||||||
|  |     F: Fn(&Process, u64, u64) -> Vec<u8> | ||||||
|  | { | ||||||
|  |     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::<dyn Error>::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<dyn Error>> { | ||||||
|     // Find our target program |     // Find our target program | ||||||
|     let pid = Pid::from_raw(get_pid_by_name("target")?); |     let pid = Pid::from_raw(get_pid_by_name("target")?); | ||||||
|     let process = Process::new(pid)?; |     let process = Process::new(pid)?; | ||||||
| @@ -27,23 +86,64 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     let map = MemoryMap::new(&lines); |     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); |     let seg_x = map.first_exec_segment(&exe).context("Can't find first exec segment")?; | ||||||
|     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)? |     ptrace::attach(pid)?; | ||||||
|         .try_into() |     process.wait(); | ||||||
|         .map_err(|_| "Failed to convert Vec to array")?; |     ptrace::step(pid, None)?; | ||||||
|     let got_write_addr = u64::from_le_bytes(got_write_vec); |     process.wait(); | ||||||
|     let real_write_addr = |  | ||||||
|         process.find_remote_proc("/usr/lib/libc.so.6", "write").context("Failed to find write.")?; |  | ||||||
|  |  | ||||||
|     println!( |     // Save context | ||||||
|         "{GREEN}[memory map]{RESET} real_write_addr = {:#016x}, got_write_addr = {:#016x}", |     let regs = ptrace::getregs(pid)?; // Save current registers | ||||||
|         real_write_addr, got_write_addr |  | ||||||
|     ); |     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(()) |     Ok(()) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,3 +1,6 @@ | |||||||
|  |  | ||||||
|  | // map.rs | ||||||
|  |  | ||||||
| use crate::elf::ExecuteLinkFile; | use crate::elf::ExecuteLinkFile; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| @@ -81,15 +84,21 @@ impl MemoryMap { | |||||||
|             .map(|r| (r.start_addr, r.end_addr)) |             .map(|r| (r.start_addr, r.end_addr)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn module_base_address(&self, module: &str) -> Option<u64> { |  | ||||||
|  |     pub fn module_base_address( | ||||||
|  |         &self, | ||||||
|  |         module: &str // Full path of module, like '/usr/lib/libc.so.6' | ||||||
|  |     ) -> Option<u64> { | ||||||
|         let elf = ExecuteLinkFile::prase(&module).ok()?; |         let elf = ExecuteLinkFile::prase(&module).ok()?; | ||||||
|         let loads = elf.get_loads().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; |             return None; | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let Some(map_item) = self.regions.iter().find(|r| { |         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 { |         }) else { | ||||||
|             return None; |             return None; | ||||||
|         }; |         }; | ||||||
|   | |||||||
| @@ -1,3 +1,6 @@ | |||||||
|  |  | ||||||
|  | // processes.rs | ||||||
|  |  | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| use std::fs; | use std::fs; | ||||||
|  |  | ||||||
| @@ -6,10 +9,14 @@ use nix::unistd::Pid; | |||||||
| use std::error::Error; | use std::error::Error; | ||||||
| use std::ffi::CString; | use std::ffi::CString; | ||||||
| use std::io::{IoSliceMut, IoSlice}; | 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 nix::sys::ptrace; | ||||||
|  |  | ||||||
| use libc::{dlsym, RTLD_NEXT}; | use libc::{dlsym, user_regs_struct, RTLD_NEXT}; | ||||||
| use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; | use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; | ||||||
|  | use crate::asm::assemble; | ||||||
| use crate::elf::ExecuteLinkFile; | use crate::elf::ExecuteLinkFile; | ||||||
| use crate::map::{MemoryMap}; | use crate::map::{MemoryMap}; | ||||||
|  |  | ||||||
| @@ -72,7 +79,7 @@ impl Process | |||||||
|  |  | ||||||
|         let copy_len = usize::min(word_size - head_offset, data.len()); |         let copy_len = usize::min(word_size - head_offset, data.len()); | ||||||
|         bytes[head_offset..head_offset + copy_len].copy_from_slice(&data[..copy_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)?; |         ptrace::write(pid, aligned_addr as *mut libc::c_void, new_word)?; | ||||||
|         Ok(copy_len) |         Ok(copy_len) | ||||||
| @@ -86,7 +93,7 @@ impl Process | |||||||
|     { |     { | ||||||
|         let mut arr = [0u8; size_of::<libc::c_long>()]; |         let mut arr = [0u8; size_of::<libc::c_long>()]; | ||||||
|         arr.copy_from_slice(data); |         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)?; |         ptrace::write(pid, addr as *mut libc::c_void, val)?; | ||||||
|         Ok(size_of::<libc::c_long>()) |         Ok(size_of::<libc::c_long>()) | ||||||
|     } |     } | ||||||
| @@ -101,7 +108,7 @@ impl Process | |||||||
|         let orig_word = ptrace::read(pid, addr as *mut libc::c_void)?; |         let orig_word = ptrace::read(pid, addr as *mut libc::c_void)?; | ||||||
|         let mut bytes = orig_word.to_ne_bytes(); |         let mut bytes = orig_word.to_ne_bytes(); | ||||||
|         bytes[..data.len()].copy_from_slice(data); |         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)?; |         ptrace::write(pid, addr as *mut libc::c_void, new_word)?; | ||||||
|         Ok(data.len()) |         Ok(data.len()) | ||||||
| @@ -238,15 +245,30 @@ impl Process | |||||||
|         Ok(written) |         Ok(written) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn find_remote_proc(&self, module: &str, symbol: &str) -> Option<u64> |     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<u64> | ||||||
|     { |     { | ||||||
|         let target_maps = fs::read_to_string(format!("/proc/{}/maps", self.pid)).ok()?; |  | ||||||
|         let base = MemoryMap::new(&target_maps.lines().collect::<Vec<&str>>()).module_base_address(module)?; |  | ||||||
|  |  | ||||||
|         let elf = ExecuteLinkFile::prase(module).ok()?; |         let elf = ExecuteLinkFile::prase(module).ok()?; | ||||||
|         let sym = elf.prase_dyn_sym(symbol).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::<Vec<&str>>()).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<u64> |     pub fn find_got_pointer_plt(&self, symbol: &str) -> Option<u64> | ||||||
| @@ -257,4 +279,28 @@ impl Process | |||||||
|         let r_sym = elf.get_rela_sym(symbol).ok()?; |         let r_sym = elf.get_rela_sym(symbol).ok()?; | ||||||
|         Some(r_sym.r_offset + self.map.module_base_address(&exe)?) |         Some(r_sym.r_offset + self.map.module_base_address(&exe)?) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub fn execute_once_inplace(&self, payload: &[u8]) -> Result<user_regs_struct, Box<dyn Error>> | ||||||
|  |     { | ||||||
|  |         // 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) | ||||||
|  |     } | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user
	 rootacite
					rootacite