[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