diff --git a/src/exec.rs b/src/exec.rs index d7e5810..aa9d9fd 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -17,6 +17,36 @@ fn path_to_c_string(path: &Path) -> CString { .unwrap() } +fn close_fds() { + if let Ok(dir) = std::fs::read_dir("/proc/self/fd") { + let fds: Vec = dir + .flatten() + .filter_map(|e| e.file_name().to_str()?.parse().ok()) + .filter(|&fd| fd > 2) + .collect(); + for fd in fds { + unsafe { + nix::libc::close(fd); + } + } + } +} + +fn reset_signals() { + unsafe { + let mut sigset: nix::libc::sigset_t = std::mem::zeroed(); + nix::libc::sigemptyset(&mut sigset); + nix::libc::sigprocmask(nix::libc::SIG_SETMASK, &sigset, std::ptr::null_mut()); + + let mut sa: nix::libc::sigaction = std::mem::zeroed(); + sa.sa_sigaction = nix::libc::SIG_DFL; + nix::libc::sigemptyset(&mut sa.sa_mask); + for sig in 1..64 { + nix::libc::sigaction(sig, &sa, std::ptr::null_mut()); + } + } +} + pub fn exec_with_options(options: ExecOptions) -> ! { let (bin_addr, bin_header, opt_interp) = crate::loader::load(&options.executable, options.interpreter); @@ -30,6 +60,28 @@ pub fn exec_with_options(options: ExecOptions) -> ! { &options.args, &options.env, ); + + if let Some(file_name) = options.executable.file_name() { + if let Some(name_str) = file_name.to_str() { + let mut name = name_str.to_owned(); + name.truncate(15); + if let Ok(c_name) = CString::new(name) { + unsafe { + nix::libc::prctl( + nix::libc::PR_SET_NAME, + c_name.as_ptr() as std::primitive::usize, + 0, + 0, + 0, + ); + } + } + } + } + + close_fds(); + reset_signals(); + let entry = match opt_interp { Some((interp_addr, interp_header)) => { let interp_entry: usize = interp_header.e_entry.try_into().unwrap(); diff --git a/src/loader.rs b/src/loader.rs index f8b37c0..23dc023 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -64,7 +64,7 @@ pub fn load( mmap::( None, total_size, - ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, // TODO: read only fix + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON, None, 0, @@ -79,7 +79,11 @@ pub fn load( .try_into() .unwrap(); let page_round_down = |addr: usize| addr / page_size * page_size; - let page_round_up = |addr: usize| (addr + (page_size - 1)) / page_size * page_size; + let page_round_up = |addr: usize| addr.div_ceil(page_size) * page_size; + + // Track each segment's final address, size, and protection for mprotect later + let mut segments: Vec<(usize, usize, ProtFlags)> = Vec::new(); + for ph in elf.program_headers { if ph.p_type != PT_LOAD { continue; @@ -104,7 +108,7 @@ pub fn load( mmap( Some(addr), size, - prot | ProtFlags::PROT_WRITE, // TODO: read only fix + prot | ProtFlags::PROT_WRITE, MapFlags::MAP_PRIVATE | MapFlags::MAP_FIXED, Some(&file), offset, @@ -119,6 +123,7 @@ pub fn load( page_round_up(file_end_addr) - file_end_addr, ); } + segments.push((addr.get(), size.get(), prot)); } // Relocations (needed for musl but not glibc FWICT) @@ -130,5 +135,12 @@ pub fn load( unsafe { ptr::write(dst, src) } } + // Apply final memory protections after relocations are done + for (addr, size, prot) in segments { + unsafe { + nix::sys::mman::mprotect(addr as *mut nix::libc::c_void, size, prot).unwrap(); + } + } + (base_addr, elf.header, opt_interp) } diff --git a/src/run.rs b/src/run.rs index be508d7..9a6ceba 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,59 +1,219 @@ -use std::arch::asm; +use std::arch::global_asm; +use std::num::NonZeroUsize; +use std::os::fd::BorrowedFd; +use std::ptr; -#[cfg(target_arch = "x86_64")] -pub unsafe fn run(sp: usize, entry: usize) -> ! { - asm! { - "mov rsp, {sp}", - "jmp {entry}", - inout("rax") 0 => _, - sp = in(reg) sp, - entry = in(reg) entry, - } - unreachable!() +use nix::sys::mman::{mmap, MapFlags, ProtFlags}; + +#[repr(C)] +pub struct Region { + pub addr: usize, + pub len: usize, } +#[cfg(target_arch = "x86_64")] +global_asm!( + r#" +.global tramp_start +.global tramp_end +tramp_start: + mov r12, rdi + mov r13, rsi + mov r14, rdx + mov r15, rcx + test r13, r13 + jz 2f +1: + mov rax, 11 + mov rdi, [r12] + mov rsi, [r12 + 8] + syscall + add r12, 16 + dec r13 + jnz 1b +2: + mov rsp, r14 + jmp r15 +tramp_end: +"# +); + #[cfg(target_arch = "aarch64")] -pub unsafe fn run(sp: usize, entry: usize) -> ! { - asm! { - "mov sp, {sp}", - "br {entry}", - sp = in(reg) sp, - entry = in(reg) entry, - } - unreachable!() -} +global_asm!( + r#" +.global tramp_start +.global tramp_end +tramp_start: + mov x19, x0 + mov x20, x1 + mov x21, x2 + mov x22, x3 + cbz x20, 2f +1: + ldr x0, [x19] + ldr x1, [x19, #8] + mov x8, #215 + svc #0 + add x19, x19, #16 + sub x20, x20, #1 + cbnz x20, 1b +2: + mov sp, x21 + br x22 +tramp_end: +"# +); #[cfg(target_arch = "riscv64")] -pub unsafe fn run(sp: usize, entry: usize) -> ! { - asm! { - "mv sp, {sp}", - "jr {entry}", - sp = in(reg) sp, - entry = in(reg) entry, - } - unreachable!() -} +global_asm!( + r#" +.global tramp_start +.global tramp_end +tramp_start: + mv s0, a0 + mv s1, a1 + mv s2, a2 + mv s3, a3 + beqz s1, 2f +1: + ld a0, 0(s0) + ld a1, 8(s0) + li a7, 215 + ecall + addi s0, s0, 16 + addi s1, s1, -1 + bnez s1, 1b +2: + mv sp, s2 + jr s3 +tramp_end: +"# +); #[cfg(target_arch = "loongarch64")] -pub unsafe fn run(sp: usize, entry: usize) -> ! { - asm! { - "move $sp, {sp}", - "jirl $zero, {entry}, 0", - sp = in(reg) sp, - entry = in(reg) entry, - } - unreachable!() -} +global_asm!( + r#" +.global tramp_start +.global tramp_end +tramp_start: + move $s0, $a0 + move $s1, $a1 + move $s2, $a2 + move $s3, $a3 + beqz $s1, 2f +1: + ld.d $a0, $s0, 0 + ld.d $a1, $s0, 8 + li.d $a7, 215 + syscall 0 + addi.d $s0, $s0, 16 + addi.d $s1, $s1, -1 + bnez $s1, 1b +2: + move $sp, $s2 + jirl $zero, $s3, 0 +tramp_end: +"# +); #[cfg(target_arch = "powerpc64")] +global_asm!( + r#" +.global tramp_start +.global tramp_end +tramp_start: + mr 14, 3 + mr 15, 4 + mr 16, 5 + mr 17, 6 + cmpdi 15, 0 + beq 2f +1: + ld 3, 0(14) + ld 4, 8(14) + li 0, 73 + sc + addi 14, 14, 16 + addi 15, 15, -1 + cmpdi 15, 0 + bne 1b +2: + mtctr 17 + mr 12, 17 + mr 1, 16 + bctr +tramp_end: +"# +); + +extern "C" { + static tramp_start: u8; + static tramp_end: u8; +} + pub unsafe fn run(sp: usize, entry: usize) -> ! { - asm! { - "mtctr {entry}", - "mr 12, {entry}", - "mr 1, {sp}", - "bctr", - sp = in(reg) sp, - entry = in(reg) entry, + let mut regions = Vec::new(); + + // Find every memory region mapped by the Rust loader + if let Ok(maps) = std::fs::read_to_string("/proc/self/maps") { + if let Ok(exe_path) = std::fs::read_link("/proc/self/exe") { + if let Some(exe_str) = exe_path.to_str() { + for line in maps.lines() { + if line.ends_with(exe_str) { + let mut parts = line.split_whitespace(); + if let Some(range) = parts.next() { + let mut range_parts = range.split('-'); + if let (Some(start_str), Some(end_str)) = + (range_parts.next(), range_parts.next()) + { + if let (Ok(start), Ok(end)) = ( + usize::from_str_radix(start_str, 16), + usize::from_str_radix(end_str, 16), + ) { + regions.push(Region { + addr: start, + len: end - start, + }); + } + } + } + } + } + } + } } - unreachable!() + + let start_ptr = &tramp_start as *const u8 as usize; + let end_ptr = &tramp_end as *const u8 as usize; + let tramp_size = end_ptr - start_ptr; + + // Allocate 1 page for the trampoline and the regions array + let tramp_page = mmap::( + None, + NonZeroUsize::new(4096).unwrap(), + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE | ProtFlags::PROT_EXEC, + MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON, + None, + 0, + ) + .unwrap() as usize; + + // Copy the raw assembly machine code to the isolated page + ptr::copy_nonoverlapping(start_ptr as *const u8, tramp_page as *mut u8, tramp_size); + + // Copy the regions array safely away from the Rust heap + let regions_ptr = (tramp_page + 2048) as *mut Region; + ptr::copy_nonoverlapping(regions.as_ptr(), regions_ptr, regions.len()); + + nix::sys::mman::mprotect( + tramp_page as *mut nix::libc::c_void, + 4096, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ) + .unwrap(); + + // Jump to the trampoline! + let tramp_func: extern "C" fn(*const Region, usize, usize, usize) -> ! = + std::mem::transmute(tramp_page); + tramp_func(regions_ptr, regions.len(), sp, entry); } diff --git a/src/stack.rs b/src/stack.rs index b61b4cc..070b125 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -126,10 +126,9 @@ impl<'a, A: AsRef, E: AsRef> StackBuilder<'a, A, E> { let at_random_addr = self.push_bytes(at_random); // Align argc at bottom - while (self.stack_reversed.len() + while !(self.stack_reversed.len() + (arg_addrs.len() + env_var_addrs.len() + 3) * size_of::()) - % 16 - != 0 + .is_multiple_of(16) { self.stack_reversed.push(0); } @@ -203,5 +202,42 @@ pub fn make_stack( unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), sp as *mut u8, data.len()) } + // Patch the original kernel auxv so tools reading /proc/self/maps [stack] + // (e.g. frida-gum) see the correct values for the loaded binary. + unsafe { + extern "C" { + static environ: *mut *mut c_char; + } + if !environ.is_null() { + let mut envp = environ as *mut usize; + while *envp != 0 { + envp = envp.add(1); + } + let mut auxv = envp.add(1); + while *auxv != AT_NULL as usize { + let val_ptr = auxv.add(1); + match *auxv { + x if x == AT_BASE as usize => { + *val_ptr = interp_addr.unwrap_or_default(); + } + x if x == AT_PHDR as usize => { + *val_ptr = bin_addr + bin_header.e_phoff as usize; + } + x if x == AT_PHNUM as usize => { + *val_ptr = bin_header.e_phnum as usize; + } + x if x == AT_PHENT as usize => { + *val_ptr = bin_header.e_phentsize as usize; + } + x if x == AT_ENTRY as usize => { + *val_ptr = bin_addr + bin_header.e_entry as usize; + } + _ => {} + } + auxv = auxv.add(2); + } + } + } + sp }