[feat] new way

This commit is contained in:
rootacite
2025-10-23 15:50:48 +08:00
parent cf524608a1
commit f5f95e4714
165 changed files with 188 additions and 82 deletions

View File

@@ -7,4 +7,5 @@ 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::module_base_address;
pub use map::first_exec_segment;

View File

@@ -46,6 +46,23 @@ pub fn first_rw_segment(range_strings: &Vec<&str>) -> Option<(u64, u64)> {
None
}
pub fn first_exec_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.contains('x') {
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;

View File

@@ -1,26 +1,29 @@
#![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::arch::asm;
use std::ffi::CString;
use helper::*;
use iced_x86::code_asm::asm_traits::CodeAsmJmp;
use iced_x86::{Instruction, code_asm::*};
use libc::user_regs_struct;
use libc::{RTLD_NEXT, c_void, dlsym};
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
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())?;
@@ -31,9 +34,9 @@ fn inject1(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), B
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.syscall()?; // Syscall interrupt
asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow
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)?;
@@ -45,10 +48,16 @@ fn inject1(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), B
Ok(())
}
fn inject2(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), Box<dyn std::error::Error>> // ld injection
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 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
@@ -57,49 +66,72 @@ fn inject2(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), B
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"))); };
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{
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);
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"))); };
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);
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);
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);
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(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.call(target_dlopen_addr)?; // Syscall interrupt
asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow
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);
println!(
"{GREEN}[trace]{RESET} write instructions to {:#016x}",
regs.rip
);
// Continue target
ptrace::cont(pid, None)?;
@@ -109,26 +141,36 @@ fn inject2(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), B
Ok(())
}
fn inject3(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), Box<dyn std::error::Error>> // thread inject
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(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
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);
println!(
"{GREEN}[trace]{RESET} write instructions to {:#016x}",
regs.rip
);
// Continue target
ptrace::cont(pid, None)?;
@@ -138,7 +180,10 @@ fn inject3(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), B
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);
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())?;
@@ -154,14 +199,14 @@ fn inject3(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), B
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.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.syscall()?; // Syscall interrupt
asm.jmp(target_label)?; // Jmp back to loop
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)?;
@@ -174,18 +219,25 @@ fn inject3(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), B
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(
rdi,
(libc::CLONE_VM
| libc::CLONE_FS
| libc::CLONE_FILES
| libc::CLONE_SIGHAND
| libc::CLONE_THREAD) as u64,
)?; // Flags
asm.mov(rsi, (page_addr + 0x800) as u64)?; // Stack top
asm.mov(rdx, 0u64)?; // parent_tid = NULL
asm.mov(r10, 0u64)?; // child_tid = NULL
asm.mov(r8, 0u64)?; // tls = NULL
asm.mov(r8, 0u64)?; // tls = NULL
asm.syscall()?; // Syscall interrupt
asm.test(eax, eax)?; // Syscall returns zero?
asm.syscall()?; // Syscall interrupt
asm.test(eax, eax)?; // Syscall returns zero?
asm.jz(page_addr as u64)?;
asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow
asm.int3()?; // (Important!!!) Use int3 interrupt to retrieve control flow
let injected_trigger = asm.assemble(regs.rip as u64)?;
write_memory_ptrace(pid, regs.rip as usize, &injected_trigger)?;
@@ -198,12 +250,13 @@ fn inject3(pid: Pid, seg_rw: (u64, u64), regs: user_regs_struct) -> Result<(), B
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
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 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()
@@ -214,38 +267,70 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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"))); };
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",
)));
};
let Some(seg_x) = first_exec_segment(&lines) else {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"first exec segment not found",
)));
};
ptrace::attach(pid)?;
waitpid(pid, None)?;
ptrace::step(pid, None)?;
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)?;
}
// ↓ Old behavior, but maybe the process stay in their libraries forever ?
// 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: {:#016x}", regs.rip);
// break;
// }
// println!("{GREEN}[trace]{RESET} Skipped: {:#016x}", regs.rip);
// 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 = read_memory_vm(pid, seg_x.0 as usize, 128)?; // Save current memory context
let buffer_rw = read_memory_vm(pid, seg_rw.0 as usize, 128)?; // Save current rw memory
println!("{GREEN}[trace]{RESET} Seg_x.0 is {:#016x}", seg_x.0);
write_memory_ptrace(pid, seg_x.0 as usize, &[0x90u8; 128])?;
ptrace::setregs(pid, user_regs_struct {
rip: seg_x.0,
..regs
})?;
// Do inject here
inject3(pid, seg_rw, regs)?;
inject3(
pid,
seg_rw,
user_regs_struct {
rip: seg_x.0,
..regs
},
)?;
// End inject logics
// Restore context
ptrace::setregs(pid, regs)?;
write_memory_ptrace(pid, regs.rip as usize, &buffer)?;
write_memory_ptrace(pid, seg_x.0 as usize, &buffer)?;
write_memory_vm(pid, seg_rw.0 as usize, &buffer_rw)?;
ptrace::detach(pid, None)?;