[feat] more powerful modules

This commit is contained in:
rootacite
2025-10-28 00:19:20 +08:00
parent ea1821480f
commit 28253d6806
19 changed files with 1678 additions and 515 deletions

View File

@@ -2,6 +2,18 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 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]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.10.0" version = "2.10.0"
@@ -90,6 +102,23 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 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]] [[package]]
name = "iced-x86" name = "iced-x86"
version = "1.21.0" version = "1.21.0"
@@ -121,6 +150,12 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]] [[package]]
name = "memmap2" name = "memmap2"
version = "0.9.9" version = "0.9.9"
@@ -142,6 +177,36 @@ dependencies = [
"libc", "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]] [[package]]
name = "proc-macro-error-attr2" name = "proc-macro-error-attr2"
version = "2.0.0" version = "2.0.0"
@@ -173,16 +238,33 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "project-hbj-attacker" name = "project-hbj-attacker"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"ctor", "ctor",
"dynasmrt", "dynasmrt",
"goblin",
"iced-x86", "iced-x86",
"libc", "libc",
"libloading", "libloading",
"memmap2",
"nix", "nix",
"ouroboros",
] ]
[[package]] [[package]]
@@ -194,6 +276,32 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "syn" name = "syn"
version = "2.0.107" version = "2.0.107"
@@ -211,8 +319,20 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"

View File

@@ -10,10 +10,14 @@ libc = "0.2.177"
nix = { version = "0.30.1", features = ["ptrace", "uio", "signal"] } nix = { version = "0.30.1", features = ["ptrace", "uio", "signal"] }
ctor = "0.6.0" ctor = "0.6.0"
libloading = "0.8.9" libloading = "0.8.9"
goblin = "0.10.3"
memmap2 = "0.9.9"
ouroboros = "0.18.5"
anyhow = "1.0.100"
[profile.dev] [profile.dev]
opt-level = 0 opt-level = 0
debug = true debug = true
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["cdylib"]

View File

@@ -1,15 +1,11 @@
mod map; mod map;
mod processes; mod processes;
mod asm; mod asm;
mod injectors;
mod elf;
pub use map::is_address_in_range; pub use map::*;
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 asm::assemble; pub use asm::assemble;
pub use processes::find_remote_proc; pub use processes::*;
pub use processes::wait; pub use injectors::*;
pub use elf::*;

View File

@@ -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<Mmap, Box<dyn std::error::Error>> {
let file = File::open(path)?;
unsafe { Ok(Mmap::map(&file)?) }
}
#[self_referencing]
pub struct ExecuteLinkFile {
data: Vec<u8>,
#[borrows(data)]
#[covariant]
elf: Elf<'this>
}
impl ExecuteLinkFile {
pub fn prase(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
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<Vec<ProgramHeader>, Box<dyn std::error::Error>> {
let loads = self.borrow_elf()
.program_headers
.iter()
.filter_map(|ph| match ph.p_type {
PT_LOAD => Some(ph.to_owned()),
_ => None,
})
.collect::<Vec<ProgramHeader>>();
Ok(loads)
}
pub fn get_dynamic(&self) -> Result<ProgramHeader, Box<dyn std::error::Error>> {
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<Reloc, Box<dyn std::error::Error>> {
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::<Vec<Reloc>>();
let first = sym
.first()
.context(format!("No symbol found with name {}", name))?;
Ok(first.clone())
}
pub fn get_dyn_sym(&self, location: usize) -> Result<Sym, Box<dyn std::error::Error>> {
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<Sym, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
let str = self.borrow_elf()
.dynstrtab
.get_at(location)
.context(format!("Could not get dynstr at location {}", location))?;
Ok(str.to_owned())
}
}

View File

@@ -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<dyn std::error::Error>> // 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<dyn std::error::Error>> // 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<i32, Box<dyn std::error::Error>> // 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())
}

View File

@@ -1,90 +1,109 @@
pub fn is_address_in_range(addr: u64, range_strings: &Vec<&str>) -> bool { use crate::helper::ExecuteLinkFile;
for range_str in range_strings {
if let Some((start, end)) = parse_address_range(range_str) { #[derive(Debug, Clone)]
if addr >= start && addr < end { pub struct MemoryRegion {
return true; pub start_addr: u64,
} pub end_addr: u64,
} pub perms: String,
} pub offset: Option<u64>,
false pub dev: Option<String>,
pub inode: Option<u64>,
pub pathname: Option<String>,
} }
fn parse_address_range(range_str: &str) -> Option<(u64, u64)> { impl MemoryRegion {
let parts: Vec<&str> = range_str.split_whitespace().collect(); pub fn parse(line: &str) -> Option<Self> {
if parts.is_empty() { let parts: Vec<&str> = line.split_whitespace().collect();
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();
if parts.len() < 2 { if parts.len() < 2 {
continue; return None;
} }
let perms = parts[1]; let range_part = parts[0];
if perms.starts_with("rw") { let range_parts: Vec<&str> = range_part.split('-').collect();
if let Some((start, end)) = parse_address_range(range_str) { if range_parts.len() != 2 {
return Some((start, end)); return None;
}
} }
} let start_addr = u64::from_str_radix(range_parts[0], 16).ok()?;
None let end_addr = u64::from_str_radix(range_parts[1], 16).ok()?;
}
pub fn first_exec_segment(range_strings: &Vec<&str>) -> Option<(u64, u64)> { let perms = parts[1].to_string();
for range_str in range_strings {
let parts: Vec<&str> = range_str.split_whitespace().collect();
if parts.len() < 2 {
continue;
}
let perms = parts[1]; let offset = parts.get(2).and_then(|s| u64::from_str_radix(s, 16).ok());
if perms.contains('x') { let dev = parts.get(3).map(|s| s.to_string());
if let Some((start, end)) = parse_address_range(range_str) { let inode = parts.get(4).and_then(|s| s.parse::<u64>().ok());
return Some((start, end)); let pathname = parts.get(5).map(|s| s.to_string());
}
}
}
None
}
pub fn module_base_address(range_strings: &Vec<&str>, module_name: &str) -> Option<u64> { Some(Self {
let mut base_addr: Option<u64> = None; start_addr,
end_addr,
for range_str in range_strings { perms,
let parts: Vec<&str> = range_str.split_whitespace().collect(); offset,
if parts.len() < 6 { dev,
continue; inode,
} pathname,
})
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),
};
}
}
}
} }
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<MemoryRegion>,
}
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<u64> {
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<MemoryRegion>
{
let r = self.regions.iter()
.filter_map(|r| if r.pathname.as_deref() == Some(module) { Some(r.clone()) } else { None })
.collect::<Vec<MemoryRegion>>();
r
}
} }

View File

@@ -7,41 +7,16 @@ use std::error::Error;
use std::ffi::CString; use std::ffi::CString;
use std::io::{IoSliceMut, IoSlice}; use std::io::{IoSliceMut, IoSlice};
use nix::sys::ptrace; use nix::sys::ptrace;
use std::mem;
use libc::{dlsym, RTLD_NEXT}; use libc::{dlsym, RTLD_NEXT};
use crate::helper::module_base_address;
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
use crate::helper::ExecuteLinkFile;
use crate::helper::map::MemoryMap;
const GREEN: &str = "\x1b[32m"; const GREEN: &str = "\x1b[32m";
const RESET: &str = "\x1b[0m"; const RESET: &str = "\x1b[0m";
pub fn wait(pid: Pid) -> Option<WaitStatus>
{
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<HashMap<String, i32>, std::io::Error> fn list_processes() -> Result<HashMap<String, i32>, std::io::Error>
{ {
let mut processes = HashMap::<String, i32>::new(); let mut processes = HashMap::<String, i32>::new();
@@ -64,7 +39,6 @@ fn list_processes() -> Result<HashMap<String, i32>, std::io::Error>
if let Some(name) = name_path.split("/").last() { if let Some(name) = name_path.split("/").last() {
processes.insert(name.to_string(), pid); processes.insert(name.to_string(), pid);
println!("{} -> {}", name, dir);
} }
} }
@@ -77,169 +51,211 @@ pub fn get_pid_by_name(name: &str) -> Result<i32, std::io::Error>
Ok(ps[name]) Ok(ps[name])
} }
pub fn read_memory_vm(pid: Pid, start_addr: usize, size: usize) -> Result<Vec<u8>, Box<dyn Error>> { pub struct Process
let mut buffer = vec![0u8; size]; {
pid: Pid,
let mut local_iov = [IoSliceMut::new(&mut buffer)]; map: MemoryMap,
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 fn write_memory_vm(pid: Pid, mut start_addr: usize, mut data: &[u8]) -> Result<usize, Box<dyn Error>> { impl Process
let mut total_written = 0usize; {
while !data.is_empty() { fn write_unaligned_head(
let local_iov = [IoSlice::new(data)]; pid: Pid,
let remote_iov = [RemoteIoVec { addr: usize,
base: start_addr, data: &[u8],
len: data.len(), word_size: usize,
}]; ) -> Result<usize, Box<dyn Error>>
{
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 { ptrace::write(pid, aligned_addr as *mut libc::c_void, new_word)?;
return Err(format!("process_vm_writev returned 0 (no progress) after writing {} bytes", total_written).into()); Ok(copy_len)
}
fn write_full_word(
pid: Pid,
addr: usize,
data: &[u8]
) -> Result<usize, Box<dyn Error>>
{
let mut arr = [0u8; size_of::<libc::c_long>()];
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::<libc::c_long>())
}
fn write_unaligned_tail(
pid: Pid,
addr: usize,
data: &[u8],
_word_size: usize,
) -> Result<usize, Box<dyn Error>>
{
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<Self, Box<dyn Error>>
{
let maps = fs::read_to_string(format!("/proc/{}/maps", pid))?;
let map = MemoryMap::new(&maps.lines().filter(|&line| !line.is_empty()).collect::<Vec<&str>>());
Ok(Self { pid, map, })
}
pub fn wait(&self) -> Option<WaitStatus>
{
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; Some(f)
start_addr = start_addr.wrapping_add(written);
data = &data[written..];
} }
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<usize, Box<dyn Error>> { pub fn get_exe(&self) -> Result<String, Box<dyn Error>>
let word_size = size_of::<libc::c_long>(); {
if word_size == 0 { let r = fs::read_link(format!("/proc/{}/exe", self.pid))?
return Err("invalid word size".into()); .to_string_lossy()
.into_owned();
Ok(r)
} }
let mut addr = start_addr; pub fn get_map_str(&self) -> Result<String, Box<dyn Error>>
let mut remaining = data; {
let mut written = 0usize; let r = fs::read_to_string(format!("/proc/{}/maps", self.pid))?;
if addr % word_size != 0 && !remaining.is_empty() { Ok(r)
let n = write_unaligned_head(pid, addr, remaining, word_size)?;
addr += n;
remaining = &remaining[n..];
written += n;
} }
while remaining.len() >= word_size { pub fn read_memory_vm(&self, start_addr: usize, size: usize) -> Result<Vec<u8>, Box<dyn Error>>
let n = write_full_word(pid, addr, &remaining[..word_size])?; {
addr += n; let mut buffer = vec![0u8; size];
remaining = &remaining[n..];
written += n; 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() { pub fn write_memory_vm(&self, mut start_addr: usize, mut data: &[u8]) -> Result<usize, Box<dyn Error>>
let n = write_unaligned_tail(pid, addr, remaining, word_size)?; {
written += n; 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<usize, Box<dyn Error>>
} {
let word_size = size_of::<libc::c_long>();
if word_size == 0 {
return Err("invalid word size".into());
}
fn write_unaligned_head( let mut addr = start_addr;
pid: Pid, let mut remaining = data;
addr: usize, let mut written = 0usize;
data: &[u8],
word_size: usize,
) -> Result<usize, Box<dyn Error>> {
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()); if addr % word_size != 0 && !remaining.is_empty() {
bytes[head_offset..head_offset + copy_len].copy_from_slice(&data[..copy_len]); let n = Self::write_unaligned_head(self.pid, addr, remaining, word_size)?;
let new_word = libc::c_long::from_ne_bytes(bytes); addr += n;
remaining = &remaining[n..];
written += n;
}
ptrace::write(pid, aligned_addr as *mut libc::c_void, new_word)?; while remaining.len() >= word_size {
Ok(copy_len) 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<usize, Box<dyn Error>> { if !remaining.is_empty() {
let mut arr = [0u8; size_of::<libc::c_long>()]; let n = Self::write_unaligned_tail(self.pid, addr, remaining, word_size)?;
arr.copy_from_slice(data); written += n;
let val = libc::c_long::from_ne_bytes(arr); }
ptrace::write(pid, addr as *mut libc::c_void, val)?;
Ok(size_of::<libc::c_long>())
}
fn write_unaligned_tail( Ok(written)
pid: Pid,
addr: usize,
data: &[u8],
_word_size: usize,
) -> Result<usize, Box<dyn Error>> {
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<u64>
{
// 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::<Vec<&str>>();
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!( pub fn find_remote_proc(&self, module: &str, symbol: &str) -> Option<u64>
"{GREEN}[local]{RESET} {symbol} offset = {:#016x}", {
symbol_offset 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)?;
// Read target process memory maps to find its module base address let elf = ExecuteLinkFile::prase(module).ok()?;
let target_maps = fs::read_to_string(format!("/proc/{}/maps", pid)).ok()?; let sym = elf.prase_dyn_sym(symbol).ok()?;
let target_map_lines = target_maps.lines().collect::<Vec<&str>>();
// Find module base address in target process Some(sym.st_value + base)
let Some(module_base_target) = module_base_address(&target_map_lines, module) else { }
return None;
};
println!( pub fn find_got_pointer_plt(&self, symbol: &str) -> Option<u64>
"{GREEN}[trace]{RESET} {module} base = {:#016x}", {
module_base_target let exe = self.get_exe().ok()?;
); let elf = ExecuteLinkFile::prase(&exe).ok()?;
// Calculate symbol address in target process using the same offset let r_sym = elf.get_rela_sym(symbol).ok()?;
let target_symbol_addr = module_base_target + symbol_offset; Some(r_sym.r_offset + self.map.module_base_address(&exe)?)
println!( }
"{GREEN}[trace]{RESET} {symbol} address = {:#016x}",
target_symbol_addr
);
Some(target_symbol_addr)
} }

View File

@@ -18,269 +18,29 @@ use std::io::BufRead;
const GREEN: &str = "\x1b[32m"; const GREEN: &str = "\x1b[32m";
const RESET: &str = "\x1b[0m"; const RESET: &str = "\x1b[0m";
fn inject1(pid: Pid, seg_rw: (u64, u64)) -> Result<(), Box<dyn std::error::Error>> // 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<dyn std::error::Error>> // 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<i32, Box<dyn std::error::Error>> // 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<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::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 proc = Process::new(pid)?;
let target = fs::read_link(format!("/proc/{}/exe", pid))? let exe = proc.get_exe()?;
.to_string_lossy() let maps = proc.get_map_str()?;
.into_owned(); let lines: Vec<&str> = maps.lines().filter(|&line| !line.is_empty()).collect();
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();
for line in &lines { for line in &lines {
println!("{GREEN}[memory map]{RESET} {}", line); 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( return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other, std::io::ErrorKind::Other,
"first rw segment not found", "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( return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other, std::io::ErrorKind::Other,
"first exec segment not found", "first exec segment not found",
@@ -288,18 +48,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}; };
ptrace::attach(pid)?; ptrace::attach(pid)?;
wait(pid); proc.wait();
ptrace::step(pid, None)?; ptrace::step(pid, None)?;
wait(pid); proc.wait();
// Save context // Save context
let regs = ptrace::getregs(pid)?; // Save current registers 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 = proc.read_memory_vm(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_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); 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( ptrace::setregs(
pid, pid,
user_regs_struct { user_regs_struct {
@@ -310,22 +70,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Do inject here // Do inject here
let c = inject3(pid, seg_rw)?; let c = inject3(&proc, seg_rw)?;
// End inject logics // End inject logics
// Restore context // Restore context
ptrace::setregs(pid, regs)?; ptrace::setregs(pid, regs)?;
write_memory_ptrace(pid, seg_x.0 as usize, &buffer)?; proc.write_memory_ptrace(seg_x.0 as usize, &buffer)?;
write_memory_vm(pid, seg_rw.0 as usize, &buffer_rw)?; proc.write_memory_vm(seg_rw.0 as usize, &buffer_rw)?;
ptrace::detach(pid, None)?; ptrace::detach(pid, None)?;
ptrace::detach(Pid::from_raw(c), 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(()) Ok(())
} }

8
01/project-hbj-hook/.idea/.gitignore generated vendored Normal file
View File

@@ -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

8
01/project-hbj-hook/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/project-hbj-hook.iml" filepath="$PROJECT_DIR$/.idea/project-hbj-hook.iml" />
</modules>
</component>
</project>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
01/project-hbj-hook/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

302
01/project-hbj-hook/Cargo.lock generated Normal file
View File

@@ -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"

View File

@@ -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"

View File

@@ -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);
}
*/

View File

@@ -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<Mmap, Box<dyn std::error::Error>> {
let file = File::open(path)?;
unsafe { Ok(Mmap::map(&file)?) }
}
#[self_referencing]
pub struct ExecuteLinkFile {
data: Vec<u8>,
#[borrows(data)]
#[covariant]
elf: Elf<'this>
}
impl ExecuteLinkFile {
pub fn prase(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
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<Vec<ProgramHeader>, Box<dyn std::error::Error>> {
let loads = self.borrow_elf()
.program_headers
.iter()
.filter_map(|ph| match ph.p_type {
PT_LOAD => Some(ph.to_owned()),
_ => None,
})
.collect::<Vec<ProgramHeader>>();
Ok(loads)
}
pub fn get_dynamic(&self) -> Result<ProgramHeader, Box<dyn std::error::Error>> {
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<Reloc, Box<dyn std::error::Error>> {
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::<Vec<Reloc>>();
let first = sym
.first()
.context(format!("No symbol found with name {}", name))?;
Ok(first.clone())
}
pub fn get_dyn_sym(&self, location: usize) -> Result<Sym, Box<dyn std::error::Error>> {
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<Sym, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
let str = self.borrow_elf()
.dynstrtab
.get_at(location)
.context(format!("Could not get dynstr at location {}", location))?;
Ok(str.to_owned())
}
}

View File

@@ -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<dyn std::error::Error>> {
// 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(())
}

View File

@@ -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<u64>,
pub dev: Option<String>,
pub inode: Option<u64>,
pub pathname: Option<String>,
}
impl MemoryRegion {
pub fn parse(line: &str) -> Option<Self> {
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::<u64>().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<MemoryRegion>,
}
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<u64> {
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<MemoryRegion>
{
let r = self.regions.iter()
.filter_map(|r| if r.pathname.as_deref() == Some(module) { Some(r.clone()) } else { None })
.collect::<Vec<MemoryRegion>>();
r
}
}

View File

@@ -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<HashMap<String, i32>, std::io::Error>
{
let mut processes = HashMap::<String, i32>::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::<String>
}).collect::<Vec<String>>();
for dir in dirs {
let Ok(pid) = dir.parse::<i32>() 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<i32, std::io::Error>
{
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<usize, Box<dyn Error>>
{
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<usize, Box<dyn Error>>
{
let mut arr = [0u8; size_of::<libc::c_long>()];
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::<libc::c_long>())
}
fn write_unaligned_tail(
pid: Pid,
addr: usize,
data: &[u8],
_word_size: usize,
) -> Result<usize, Box<dyn Error>>
{
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<Self, Box<dyn Error>>
{
let maps = fs::read_to_string(format!("/proc/{}/maps", pid))?;
let map = MemoryMap::new(&maps.lines().filter(|&line| !line.is_empty()).collect::<Vec<&str>>());
Ok(Self { pid, map, })
}
pub fn wait(&self) -> Option<WaitStatus>
{
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<String, Box<dyn Error>>
{
let r = fs::read_link(format!("/proc/{}/exe", self.pid))?
.to_string_lossy()
.into_owned();
Ok(r)
}
pub fn get_map_str(&self) -> Result<String, Box<dyn Error>>
{
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<Vec<u8>, Box<dyn Error>>
{
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<usize, Box<dyn Error>>
{
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<usize, Box<dyn Error>>
{
let word_size = size_of::<libc::c_long>();
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<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 sym = elf.prase_dyn_sym(symbol).ok()?;
Some(sym.st_value + base)
}
pub fn find_got_pointer_plt(&self, symbol: &str) -> Option<u64>
{
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)?)
}
}