initial commit

This commit is contained in:
Ben-Lichtman
2022-05-05 19:20:01 -07:00
commit d15b17f2bb
8 changed files with 1142 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/target
/Cargo.lock
+12
View File
@@ -0,0 +1,12 @@
edition = "2021"
fn_single_line = true
hard_tabs = true
trailing_semicolon = true
use_field_init_shorthand = true
use_try_shorthand = true
struct_lit_single_line = true
condense_wildcard_suffixes = true
control_brace_style = "ClosingNextLine"
overflow_delimited_expr = false
use_small_heuristics = "Default"
imports_granularity = "Crate"
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "reloader"
version = "0.1.0"
edition = "2021"
[features]
default = ["debug"]
debug = []
[dependencies]
cstr_core = "0.2.5"
ntapi = "0.3.7"
object = { version = "0.28.3", default_features = false, features = ["read"] }
wchar = "0.11.0"
[dependencies.windows-sys]
version = "0.34.0"
features = ["Win32_Foundation", "Win32_System_Memory", "Win32_System_LibraryLoader"]
+21
View File
@@ -0,0 +1,21 @@
pub type Result<T> = core::result::Result<T, Error>;
#[repr(u16)]
pub enum Error {
SelfFind,
ModuleByHash,
ModuleByAscii,
ExportVaByHash,
ExportVaByAscii,
PeHeaders,
ExportTable,
ImportTable,
Allocation,
Protect,
Flush,
RelocationType,
SplitString,
ParseNumber,
SyscallNumber,
LdrLoadDll,
}
+315
View File
@@ -0,0 +1,315 @@
use crate::{
error::{Error, Result},
structures::{ExportTable, PeHeaders},
};
use core::{
mem::MaybeUninit,
ptr::null_mut,
slice,
sync::atomic::{compiler_fence, Ordering},
};
use cstr_core::CStr;
use ntapi::{ntldr::LDR_DATA_TABLE_ENTRY, ntpsapi::PEB_LDR_DATA};
use windows_sys::Win32::Foundation::UNICODE_STRING;
const SYSCALL_TABLE_SIZE: usize = 512;
const LIBRARY_CONVERSION_BUFFER_SIZE: usize = 64;
#[inline(never)]
pub const fn fnv1a_hash_32_wstr(wchars: &[u16]) -> u32 {
const FNV_OFFSET_BASIS_32: u32 = 0x811c9dc5;
const FNV_PRIME_32: u32 = 0x01000193;
let mut hash = FNV_OFFSET_BASIS_32;
let mut i = 0;
while i < wchars.len() {
let c = unsafe { char::from_u32_unchecked(wchars[i] as u32).to_ascii_lowercase() };
hash ^= c as u32;
hash = hash.wrapping_mul(FNV_PRIME_32);
i += 1;
}
hash
}
#[inline(never)]
pub const fn fnv1a_hash_32(chars: &[u8]) -> u32 {
const FNV_OFFSET_BASIS_32: u32 = 0x811c9dc5;
const FNV_PRIME_32: u32 = 0x01000193;
let mut hash = FNV_OFFSET_BASIS_32;
let mut i = 0;
while i < chars.len() {
let c = unsafe { char::from_u32_unchecked(chars[i] as u32).to_ascii_lowercase() };
hash ^= c as u32;
hash = hash.wrapping_mul(FNV_PRIME_32);
i += 1;
}
hash
}
#[inline(never)]
pub fn simple_memcpy(dest: *mut u8, src: *mut u8, len: usize) {
let n_bytes = len; // Iterate backwards to avoid optimizing..?
for i in (0..n_bytes).rev() {
compiler_fence(Ordering::Acquire);
unsafe { *dest.add(i) = *src.add(i) };
}
}
#[inline(never)]
pub fn simple_memset_u32(dest: &mut [MaybeUninit<u32>], value: u32) {
let n_bytes = dest.len(); // Iterate backwards to avoid optimizing..?
for i in (0..n_bytes).rev() {
compiler_fence(Ordering::Acquire);
unsafe { dest.get_unchecked_mut(i).write(value) };
}
}
#[inline(never)]
pub fn ascii_wstr_eq(ascii: &CStr, wstr: &[u16]) -> bool {
// Check if the lengths are equal
if wstr.len() != ascii.to_bytes().len() {
return false;
}
// Check if they are equal
if wstr
.iter()
.copied()
.zip(ascii.to_bytes().iter().copied())
.map(|(a, b)| ((a as u8).to_ascii_lowercase(), b.to_ascii_lowercase()))
.any(|(a, b)| a != b)
{
return false;
}
true
}
#[inline(never)]
pub fn ascii_ascii_eq(a: &[u8], b: &[u8]) -> bool {
// Check if the lengths are equal
if a.len() != b.len() {
return false;
}
// Check if they are equal
if a.iter()
.copied()
.zip(b.iter().copied())
.map(|(a, b)| (a.to_ascii_lowercase(), b.to_ascii_lowercase()))
.any(|(a, b)| a != b)
{
return false;
}
true
}
#[inline(never)]
pub fn find_pe(start: usize) -> Result<(*mut u8, PeHeaders)> {
let mut page_aligned = start & !0xfff;
loop {
if page_aligned == 0 {
return Err(Error::SelfFind);
}
match PeHeaders::parse(page_aligned as _) {
Ok(pe) => break Ok((page_aligned as _, pe)),
Err(_) => page_aligned -= 0x1000,
}
}
}
#[inline(never)]
pub fn find_loaded_module_by_hash(ldr: &PEB_LDR_DATA, hash: u32) -> Result<*mut u8> {
// Get initial entry in the list
let mut ldr_data_ptr = ldr.InLoadOrderModuleList.Flink as *mut LDR_DATA_TABLE_ENTRY;
while !ldr_data_ptr.is_null() {
let ldr_data = unsafe { &*ldr_data_ptr };
// Make a slice of wchars from the base name
let dll_name = ldr_data.BaseDllName;
let buffer = dll_name.Buffer;
if buffer.is_null() {
break;
}
let dll_name_wstr = unsafe { slice::from_raw_parts(buffer, dll_name.Length as usize / 2) };
if fnv1a_hash_32_wstr(dll_name_wstr) != hash {
// Go to the next entry
ldr_data_ptr = ldr_data.InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
continue;
}
// Return the base address for this DLL
return Ok(ldr_data.DllBase as _);
}
Err(Error::ModuleByHash)
}
#[inline(never)]
pub fn find_loaded_module_by_ascii(ldr: &PEB_LDR_DATA, ascii: *const i8) -> Result<*mut u8> {
let ascii = unsafe { CStr::from_ptr(ascii) };
// Get initial entry in the list
let mut ldr_data_ptr = ldr.InLoadOrderModuleList.Flink as *mut LDR_DATA_TABLE_ENTRY;
while !ldr_data_ptr.is_null() {
let ldr_data = unsafe { &*ldr_data_ptr };
// Make a slice of wchars from the base name
let dll_name = ldr_data.BaseDllName;
let buffer = dll_name.Buffer;
if buffer.is_null() {
break;
}
let dll_name_wstr = unsafe { slice::from_raw_parts(buffer, dll_name.Length as usize / 2) };
if ascii_wstr_eq(ascii, dll_name_wstr) {
return Ok(ldr_data.DllBase as _);
}
// Go to the next entry
ldr_data_ptr = ldr_data.InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
}
Err(Error::ModuleByAscii)
}
#[inline(never)]
pub fn find_export_by_hash(exports: &ExportTable, base: *mut u8, hash: u32) -> Result<*mut u8> {
exports
.iter_string_addr(base)
.find(|(name, _)| fnv1a_hash_32(name.to_bytes()) == hash)
.map(|(_, addr)| addr)
.ok_or(Error::ExportVaByHash)
}
#[inline(never)]
pub fn find_export_by_ascii(
exports: &ExportTable,
base: *mut u8,
string: &CStr,
) -> Result<*mut u8> {
exports
.iter_string_addr(base)
.find(|(name, _)| ascii_ascii_eq(name.to_bytes(), string.to_bytes()))
.map(|(_, addr)| addr)
.ok_or(Error::ExportVaByAscii)
}
#[inline(never)]
pub fn get_library_base(
peb_ldr: &PEB_LDR_DATA,
library_name: *const u8,
ldrloaddll: unsafe extern "system" fn(
DllPath: *const u16,
DllCharacteristics: *const u32,
DllName: *const UNICODE_STRING,
DllHandle: *mut *mut u8,
) -> i32,
) -> Result<*mut u8> {
let loaded_library_base = match find_loaded_module_by_ascii(peb_ldr, library_name as _) {
Ok(base) => base,
Err(_) => {
let name_ascii = unsafe { CStr::from_ptr(library_name as _) };
let mut buffer_space = [MaybeUninit::uninit(); LIBRARY_CONVERSION_BUFFER_SIZE];
buffer_space
.iter_mut()
.zip(name_ascii.to_bytes_with_nul().iter())
.for_each(|(wchar, &ascii)| {
wchar.write(ascii as u16);
});
let unicode_string = UNICODE_STRING {
Length: (name_ascii.to_bytes().len() * 2) as _,
MaximumLength: (name_ascii.to_bytes_with_nul().len() * 2) as _,
Buffer: MaybeUninit::slice_as_ptr(&buffer_space) as _,
};
// Now load the library
let mut module_handle = null_mut::<u8>();
unsafe {
ldrloaddll(
null_mut(),
null_mut(),
&unicode_string as _,
&mut module_handle,
)
};
if module_handle.is_null() {
return Err(Error::LdrLoadDll);
}
module_handle
}
};
if loaded_library_base.is_null() {
return Err(Error::LdrLoadDll);
}
Ok(loaded_library_base)
}
#[inline(never)]
pub fn syscall_table(exports: &ExportTable, base: *mut u8) -> [u32; SYSCALL_TABLE_SIZE] {
let mut scratch_table = [MaybeUninit::<(u32, *mut u8)>::uninit(); SYSCALL_TABLE_SIZE];
let mut num_syscalls = 0;
// Iterate through exports which match the names of syscalls
exports
.iter_string_addr(base)
.filter(|(name, _)| {
// Our condition is - name must start with zW
let name = name.to_bytes();
let name_0 = match name.get(0) {
Some(&x) => x,
None => return false,
};
let name_1 = match name.get(1) {
Some(&x) => x,
None => return false,
};
if name_0 != b'Z' {
return false;
}
if name_1 != b'w' {
return false;
}
true
})
.enumerate()
.for_each(|(n, (name, addr))| {
// Turn each function name into a hash
let name_hash = fnv1a_hash_32(name.to_bytes());
unsafe { scratch_table.get_unchecked_mut(n).write((name_hash, addr)) };
num_syscalls += 1;
});
let working_slice = unsafe {
MaybeUninit::slice_assume_init_mut(scratch_table.get_unchecked_mut(..num_syscalls))
};
// Sort the filled entries by address
working_slice.sort_unstable_by_key(|(_, addr)| *addr);
let mut output = [MaybeUninit::uninit(); SYSCALL_TABLE_SIZE];
simple_memset_u32(&mut output, 0);
let mut output = unsafe { MaybeUninit::array_assume_init(output) };
// Copy hashes over to output slice
for i in 0..num_syscalls {
unsafe { *output.get_unchecked_mut(i) = working_slice.get_unchecked(i).0 };
}
output
}
#[inline(never)]
pub fn find_syscall_by_hash(table: &[u32; SYSCALL_TABLE_SIZE], hash: u32) -> Result<u32> {
table
.iter()
.position(|&table_hash| table_hash == hash)
.map(|x| x as u32)
.ok_or(Error::SyscallNumber)
}
+510
View File
@@ -0,0 +1,510 @@
#![no_std]
#![feature(slice_split_at_unchecked)]
#![feature(maybe_uninit_slice)]
#![feature(maybe_uninit_array_assume_init)]
#![feature(const_char_convert)]
#![feature(asm_const)]
mod error;
mod helpers;
mod structures;
mod syscall;
use crate::{
error::{Error, Result},
helpers::{
find_export_by_ascii, find_export_by_hash, find_loaded_module_by_hash, find_pe,
find_syscall_by_hash, fnv1a_hash_32, fnv1a_hash_32_wstr, get_library_base, simple_memcpy,
syscall_table,
},
structures::PeHeaders,
syscall::{syscall3, syscall5, syscall6},
};
use core::{
arch::asm,
mem::{size_of, transmute},
ptr::null_mut,
slice,
str::from_utf8_unchecked,
};
use cstr_core::CStr;
use ntapi::{
ntpebteb::TEB,
winapi::um::winnt::{DLL_PROCESS_ATTACH, PAGE_NOACCESS},
};
use object::{
pe::{
ImageThunkData64, IMAGE_DIRECTORY_ENTRY_BASERELOC, IMAGE_REL_BASED_ABSOLUTE,
IMAGE_REL_BASED_DIR64, IMAGE_REL_BASED_HIGHLOW, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ,
IMAGE_SCN_MEM_WRITE,
},
read::pe::{ImageNtHeaders, ImageOptionalHeader, ImageThunkData},
LittleEndian,
};
use wchar::wch;
use windows_sys::Win32::{
Foundation::{STATUS_SUCCESS, UNICODE_STRING},
System::Memory::{
MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE,
PAGE_EXECUTE_WRITECOPY, PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY,
},
};
// Abusing fnv1a hash to find the strings we're looking for
// Can't just do a string comparison because the segments aren't properly loaded yet
const NTDLL_HASH: u32 = fnv1a_hash_32_wstr(wch!("ntdll.dll"));
const ZWFLUSHINSTRUCTIONCACHE_HASH: u32 = fnv1a_hash_32("ZwFlushInstructionCache".as_bytes());
const ZWALLOCATEVIRTUALMEMORY_HASH: u32 = fnv1a_hash_32("ZwAllocateVirtualMemory".as_bytes());
const ZWPROTECTVIRTUALMEMORY_HASH: u32 = fnv1a_hash_32("ZwProtectVirtualMemory".as_bytes());
const LDRLOADDLL_HASH: u32 = fnv1a_hash_32("LdrLoadDll".as_bytes());
#[inline(never)]
fn load() -> Result<(*mut u8, *mut u8)> {
let rip: usize;
#[cfg(target_arch = "x86_64")]
unsafe {
asm!("lea {rip}, [rip]", rip = out(reg) rip)
};
#[cfg(target_arch = "x86")]
unsafe {
asm!("lea {eip}, [eip]", rip = out(reg) rip)
};
let (pe_base, pe) = find_pe(rip)?;
// Locate other important data structures
let teb: *mut TEB;
unsafe {
#[cfg(target_arch = "x86_64")]
asm!("mov {teb}, gs:[0x30]", teb = out(reg) teb);
#[cfg(target_arch = "x86")]
asm!("mov {teb}, fs:[0x18]", teb = out(reg) teb);
}
let teb = unsafe { &mut *teb };
let peb = unsafe { &mut *teb.ProcessEnvironmentBlock };
let peb_ldr = unsafe { &*peb.Ldr };
// Traverse loaded modules to find ntdll.dll
let ntdll_base = find_loaded_module_by_hash(peb_ldr, NTDLL_HASH)?;
let ntdll = PeHeaders::parse(ntdll_base)?;
// Locate the export table for ntdll.dll
let ntdll_export_table = ntdll.export_table_mem(ntdll_base)?;
let syscall_table = syscall_table(&ntdll_export_table, ntdll_base);
// Find some important syscall numbers
let sys_no_zwflushinstructioncache =
find_syscall_by_hash(&syscall_table, ZWFLUSHINSTRUCTIONCACHE_HASH)?;
let sys_no_zwallocatevirtualmemory =
find_syscall_by_hash(&syscall_table, ZWALLOCATEVIRTUALMEMORY_HASH)?;
let sys_no_zwprotectvirtualmemory =
find_syscall_by_hash(&syscall_table, ZWPROTECTVIRTUALMEMORY_HASH)?;
let ldrloaddll = find_export_by_hash(&ntdll_export_table, ntdll_base, LDRLOADDLL_HASH)?;
let ldrloaddll = unsafe {
transmute::<
_,
unsafe extern "system" fn(
DllPath: *const u16,
DllCharacteristics: *const u32,
DllName: *const UNICODE_STRING,
DllHandle: *mut *mut u8,
) -> i32,
>(ldrloaddll)
};
// Allocate space to map the PE into
let size_of_image = pe
.nt_header
.optional_header()
.size_of_image
.get(LittleEndian) as usize;
let mut allocated_ptr = null_mut::<u8>();
let mut region_size = size_of_image as usize;
let nt_status = unsafe {
syscall6(
sys_no_zwallocatevirtualmemory,
-1i64 as _,
&mut allocated_ptr as *mut _ as _,
0,
&mut region_size as *mut _ as _,
(MEM_RESERVE | MEM_COMMIT) as _,
PAGE_READWRITE as _,
)
};
if nt_status != STATUS_SUCCESS as _ {
return Err(Error::Allocation);
}
if allocated_ptr.is_null() {
return Err(Error::Allocation);
}
// Copy over header data
let header_size = pe
.nt_header
.optional_header()
.size_of_headers
.get(LittleEndian) as _;
simple_memcpy(allocated_ptr, pe_base, header_size);
// Map sections
pe.section_headers.iter().for_each(|section| {
let dest = unsafe { allocated_ptr.add(section.virtual_address.get(LittleEndian) as _) };
let src = unsafe { pe_base.add(section.pointer_to_raw_data.get(LittleEndian) as _) };
let size = section.size_of_raw_data.get(LittleEndian) as _;
simple_memcpy(dest, src, size);
});
// Process import table
let import_table = pe.import_table_mem(allocated_ptr)?;
for idt in import_table.import_descriptors {
// Load the library
let name_rva = idt.name.get(LittleEndian) as usize;
let library_name = unsafe { allocated_ptr.add(name_rva) };
// Load library if it is not already loaded and get the base
let loaded_library_base = get_library_base(peb_ldr, library_name as _, ldrloaddll)?;
// Find the exports of the loaded library
let loaded_library = PeHeaders::parse(loaded_library_base)?;
let loaded_library_exports = loaded_library.export_table_mem(loaded_library_base)?;
let ilt_rva = idt.original_first_thunk.get(LittleEndian) as usize;
let iat_rva = idt.first_thunk.get(LittleEndian) as usize;
let mut ilt_ptr = unsafe { allocated_ptr.add(ilt_rva).cast::<ImageThunkData64>() };
let mut iat_ptr = unsafe { allocated_ptr.add(iat_rva).cast::<usize>() };
// Look through each entry in the ILT until we find a null entry
while unsafe { ilt_ptr.read().raw() != 0 } {
let ilt_entry = unsafe { ilt_ptr.read() };
// Resolve the function VA - taking forwarding into account
// First step - get an address from direct dependency
let mut resolved_address = match ilt_entry.is_ordinal() {
true => {
// Load from ordinal
let ordinal = ilt_entry.ordinal();
// Find matching function in loaded library
let export_rva = unsafe {
*loaded_library_exports
.address_table
.get_unchecked(ordinal as usize)
};
unsafe { loaded_library_base.add(export_rva as _) }
}
false => {
// Load from name
let address_rva = ilt_entry.address() as _;
// Get the name of the function
let string_va = unsafe { allocated_ptr.add(address_rva).add(size_of::<u16>()) };
let string = unsafe { CStr::from_ptr(string_va as _) };
// Find matching function in loaded library
find_export_by_ascii(&loaded_library_exports, loaded_library_base, string)?
}
};
// Forwarding
let mut current_export_start = loaded_library_exports.start_address;
let mut current_export_end =
unsafe { current_export_start.add(loaded_library_exports.size as _) };
let function_va = loop {
let mut buffer = [0u8; 64];
if !(current_export_start <= resolved_address
&& resolved_address < current_export_end)
{
// The pointer is not located in the exports section and therefore has no more forwarders
break resolved_address;
}
// We have a pointer to a null-terminated string of the form "MYDLL.expfunc" or "MYDLL.#27"
let string = unsafe { CStr::from_ptr(resolved_address as _) };
let string = string.to_bytes_with_nul();
// Copy string to buffer
for i in 0..string.len() {
unsafe {
let c = *string.get_unchecked(i);
*buffer.get_unchecked_mut(i) = c;
}
}
// Give an extra 4 bytes for adding "dll\0"
let working_buffer = unsafe { buffer.get_unchecked_mut(..string.len() + 4) };
// Find the dot
let dot_index = working_buffer
.iter()
.position(|&b| b == b'.')
.ok_or(Error::SplitString)?;
// Move things after the dot forwards
for i in (dot_index + 1..working_buffer.len()).rev() {
unsafe {
let c = *working_buffer.get_unchecked(i);
*working_buffer.get_unchecked_mut(i + 4) = c;
};
}
// Write DLL
unsafe {
*working_buffer.get_unchecked_mut(dot_index + 1) = b'd';
*working_buffer.get_unchecked_mut(dot_index + 2) = b'l';
*working_buffer.get_unchecked_mut(dot_index + 3) = b'l';
*working_buffer.get_unchecked_mut(dot_index + 4) = b'\0';
}
let (dll_name, rest) =
unsafe { working_buffer.split_at_mut_unchecked(dot_index + 5) };
// Load library if it is not already loaded and get the base
let loaded_library_base = get_library_base(peb_ldr, dll_name.as_ptr(), ldrloaddll)?;
// Find the exports of the loaded library
let loaded_library = PeHeaders::parse(loaded_library_base)?;
let loaded_library_exports =
loaded_library.export_table_mem(loaded_library_base)?;
// Set resolved address for next loop
resolved_address = if unsafe { *rest.get_unchecked(0) } == b'#' {
// Load from ordinal
let number_string = unsafe { from_utf8_unchecked(rest.get_unchecked(1..)) };
let ordinal =
u16::from_str_radix(number_string, 10).map_err(|_| Error::ParseNumber)?;
// Find matching function in loaded library
let export_rva = unsafe {
*loaded_library_exports
.address_table
.get_unchecked(ordinal as usize)
};
unsafe { loaded_library_base.add(export_rva as _) }
}
else {
// Load from name
// Get the name of the function
let string = unsafe { CStr::from_ptr(rest.as_ptr() as _) };
// Find matching function in loaded library
find_export_by_ascii(&loaded_library_exports, loaded_library_base, string)?
};
// Set the new export range
current_export_start = loaded_library_exports.start_address;
current_export_end =
unsafe { current_export_start.add(loaded_library_exports.size as _) };
};
// Write function VA into IAT
unsafe { *iat_ptr = function_va as _ };
// Advance to the next entry
ilt_ptr = unsafe { ilt_ptr.add(1) };
iat_ptr = unsafe { iat_ptr.add(1) };
}
}
// Process relocations
let image_base_in_file = pe.nt_header.optional_header().image_base();
let calculated_offset = allocated_ptr as isize - image_base_in_file as isize;
let relocations = unsafe {
pe.data_directories
.get_unchecked(IMAGE_DIRECTORY_ENTRY_BASERELOC)
};
// Iterate through the relocation table
let reloc_start_address =
unsafe { allocated_ptr.add(relocations.virtual_address.get(LittleEndian) as _) };
let reloc_size_bytes = relocations.size.get(LittleEndian) as _;
let mut reloc_byte_slice =
unsafe { slice::from_raw_parts(reloc_start_address, reloc_size_bytes) };
// Loop over relocation blocks - each has a 8 byte header
while let &[a, b, c, d, e, f, g, h, ref rest @ ..] = reloc_byte_slice {
let rva = u32::from_le_bytes([a, b, c, d]) as usize;
let relocs_bytes = u32::from_le_bytes([e, f, g, h]) as usize - 8;
let block_va = unsafe { allocated_ptr.add(rva) };
// Loop over the relocations in this block
let (mut relocs_slice, rest) = unsafe { rest.split_at_unchecked(relocs_bytes) };
while let &[a, b, ref rest @ ..] = relocs_slice {
let reloc = u16::from_le_bytes([a, b]);
let reloc_type = (reloc & 0xf000) >> 0xc;
let reloc_offset = reloc & 0x0fff;
let reloc_va = unsafe { block_va.add(reloc_offset as _) };
// Apply the relocation
match reloc_type {
IMAGE_REL_BASED_ABSOLUTE => (),
IMAGE_REL_BASED_DIR64 => {
let ptr = reloc_va as *mut u64;
unsafe { *ptr = *ptr + calculated_offset as u64 };
}
IMAGE_REL_BASED_HIGHLOW => {
let ptr = reloc_va as *mut u32;
unsafe { *ptr = *ptr + calculated_offset as u32 };
}
_ => return Err(Error::RelocationType),
}
relocs_slice = rest;
}
reloc_byte_slice = rest;
}
// Set header permissions
let base_address = allocated_ptr;
let mut region_size = header_size;
let mut old_permissions = u32::default();
let nt_status = unsafe {
syscall5(
sys_no_zwprotectvirtualmemory,
-1i64 as _,
&base_address as *const _ as _,
&mut region_size as *mut _ as _,
PAGE_READONLY as _,
&mut old_permissions as *mut _ as _,
)
};
if nt_status != STATUS_SUCCESS as _ {
return Err(Error::Protect);
}
// Set section permissions
pe.section_headers.iter().try_for_each(|section| {
let virtual_address =
unsafe { allocated_ptr.add(section.virtual_address.get(LittleEndian) as _) };
let virtual_size = section.virtual_size.get(LittleEndian);
// Change permissions
let characteristics = section.characteristics.get(LittleEndian);
let r = characteristics & IMAGE_SCN_MEM_READ != 0;
let w = characteristics & IMAGE_SCN_MEM_WRITE != 0;
let x = characteristics & IMAGE_SCN_MEM_EXECUTE != 0;
let new_permissions = match (r, w, x) {
(false, false, false) => PAGE_NOACCESS,
(true, false, false) => PAGE_READONLY,
(false, true, false) => PAGE_WRITECOPY,
(true, true, false) => PAGE_READWRITE,
(false, false, true) => PAGE_EXECUTE,
(true, false, true) => PAGE_EXECUTE_READ,
(false, true, true) => PAGE_EXECUTE_WRITECOPY,
(true, true, true) => PAGE_EXECUTE_READWRITE,
};
let base_address = virtual_address;
let mut region_size = virtual_size;
let mut old_permissions = u32::default();
let nt_status = unsafe {
syscall5(
sys_no_zwprotectvirtualmemory,
-1i64 as _,
&base_address as *const _ as _,
&mut region_size as *mut _ as _,
new_permissions as _,
&mut old_permissions as *mut _ as _,
)
};
if nt_status != STATUS_SUCCESS as _ {
return Err(Error::Protect);
}
Ok(())
})?;
let entry_point = unsafe {
allocated_ptr.add(
pe.nt_header
.optional_header()
.address_of_entry_point
.get(LittleEndian) as _,
)
};
// We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing
let nt_status = unsafe { syscall3(sys_no_zwflushinstructioncache, -1i64 as _, 0, 0) };
if nt_status != STATUS_SUCCESS as _ {
return Err(Error::Flush);
}
// unsafe { ntflushinstructioncache(-1 as _, null_mut(), 0) };
Ok((allocated_ptr, entry_point))
}
#[inline(never)]
fn handle_error(error: Error) {
#[cfg(feature = "debug")]
{
let error_code = error as u16;
// Write error code to invalid address for rudimentary debugging
unsafe { *(0xdeadbeefdeadbeef as *mut _) = error_code };
}
}
#[no_mangle]
#[inline(never)]
pub extern "system" fn reflective_loader(reserved: usize) {
match load() {
Ok((allocated_ptr, entry_point)) => {
// Call entry point
let entry_point_callable = unsafe {
transmute::<_, unsafe extern "system" fn(usize, u32, usize)>(entry_point)
};
unsafe { entry_point_callable(allocated_ptr as _, DLL_PROCESS_ATTACH, reserved) };
}
Err(e) => handle_error(e),
}
}
#[no_mangle]
#[inline(never)]
pub extern "system" fn reflective_loader_wow64(reserved: usize) {
unsafe {
asm!(
".code32",
"push ebp",
"mov ebp, esp",
"and esp, 0xfffffff0",
"push 0x33",
"call 1f",
"1:",
"add dword ptr [esp], 5",
"retf",
".code64",
);
reflective_loader(reserved);
asm!(
".code64",
"call 1f",
"1:",
"mov dword ptr [rsp + 4], 0x23",
"add dword ptr [rsp], 0xd",
"retf",
".code32",
"mov esp, ebp",
"pop ebp",
".code64",
);
}
}
+195
View File
@@ -0,0 +1,195 @@
use crate::error::{Error, Result};
use core::{mem::size_of, slice};
use cstr_core::CStr;
use object::{
pe::{
self, ImageDataDirectory, ImageDosHeader, ImageExportDirectory, ImageImportDescriptor,
ImageSectionHeader, IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_DIRECTORY_ENTRY_IMPORT,
IMAGE_DOS_SIGNATURE, IMAGE_NT_SIGNATURE,
},
read::pe::{ImageNtHeaders, ImageOptionalHeader},
LittleEndian,
};
pub struct PeHeaders {
pub dos_header: &'static mut ImageDosHeader,
#[cfg(target_arch = "x86_64")]
pub nt_header: &'static mut pe::ImageNtHeaders64,
#[cfg(target_arch = "x86")]
pub nt_header: &'static mut pe::ImageNtHeaders32,
pub data_directories: &'static mut [ImageDataDirectory],
pub section_headers: &'static mut [ImageSectionHeader],
}
impl PeHeaders {
#[inline(never)]
pub fn parse(address: *mut u8) -> Result<Self> {
let dos_header_ptr = address;
let dos_header = unsafe { &mut *dos_header_ptr.cast::<ImageDosHeader>() };
if dos_header.e_magic.get(LittleEndian) != IMAGE_DOS_SIGNATURE {
return Err(Error::PeHeaders);
}
let nt_header_offset = dos_header.nt_headers_offset() as usize;
// Sanity check
if nt_header_offset > 1024 {
return Err(Error::PeHeaders);
}
let nt_header_ptr = unsafe { address.add(nt_header_offset) };
#[cfg(target_arch = "x86_64")]
let nt_header = unsafe { &mut *nt_header_ptr.cast::<pe::ImageNtHeaders64>() };
#[cfg(target_arch = "x86")]
let nt_header = unsafe { &mut *nt_header_ptr.cast::<ImageNtHeaders32>() };
if nt_header.signature.get(LittleEndian) != IMAGE_NT_SIGNATURE {
return Err(Error::PeHeaders);
}
if !nt_header.is_valid_optional_magic() {
return Err(Error::PeHeaders);
}
#[cfg(target_arch = "x86_64")]
let data_directories_ptr = unsafe { nt_header_ptr.add(size_of::<pe::ImageNtHeaders64>()) };
#[cfg(target_arch = "x86")]
let data_directories_ptr = unsafe { nt_header_ptr.add(size_of::<pe::ImageNtHeaders32>()) };
let num_data_directories = nt_header.optional_header().number_of_rva_and_sizes() as _;
let data_directories = unsafe {
slice::from_raw_parts_mut(
data_directories_ptr.cast::<ImageDataDirectory>(),
num_data_directories,
)
};
let section_headers_ptr = unsafe {
data_directories_ptr.add(num_data_directories * size_of::<ImageDataDirectory>())
};
let num_section_headers = nt_header.file_header().number_of_sections.get(LittleEndian) as _;
let section_headers = unsafe {
slice::from_raw_parts_mut(
section_headers_ptr.cast::<ImageSectionHeader>(),
num_section_headers,
)
};
Ok(Self {
dos_header,
nt_header,
data_directories,
section_headers,
})
}
#[inline(never)]
pub fn export_table_mem(&self, image_base: *mut u8) -> Result<ExportTable> {
let export_table_data_dir = self
.data_directories
.get(IMAGE_DIRECTORY_ENTRY_EXPORT)
.ok_or(Error::ExportTable)?;
let export_table_rva = export_table_data_dir.virtual_address.get(LittleEndian);
let export_table_ptr = unsafe { image_base.add(export_table_rva as _) };
let export_table_size = export_table_data_dir.size.get(LittleEndian);
Ok(ExportTable::parse(
export_table_ptr,
export_table_rva as _,
export_table_size,
))
}
#[inline(never)]
pub fn import_table_mem(&self, image_base: *mut u8) -> Result<ImportTable> {
let import_table_data_dir = self
.data_directories
.get(IMAGE_DIRECTORY_ENTRY_IMPORT)
.ok_or(Error::ImportTable)?;
let import_table_rva = import_table_data_dir.virtual_address.get(LittleEndian);
let import_table_size = import_table_data_dir.size.get(LittleEndian);
let import_table_ptr = unsafe { image_base.add(import_table_rva as _) };
Ok(ImportTable::parse(import_table_ptr, import_table_size as _))
}
}
pub struct ExportTable {
pub export_directory: &'static mut ImageExportDirectory,
pub address_table: &'static mut [u32],
pub name_table: &'static mut [u32],
pub ordinal_table: &'static mut [u16],
pub start_address: *mut u8,
pub size: u32,
}
impl ExportTable {
#[inline(never)]
pub fn parse(address: *mut u8, rva: usize, size: u32) -> Self {
let export_directory_ptr = address;
let export_directory = unsafe { &mut *export_directory_ptr.cast::<ImageExportDirectory>() };
let address_table_ptr = unsafe {
address
.add(export_directory.address_of_functions.get(LittleEndian) as _)
.wrapping_sub(rva)
.cast::<u32>()
};
let address_table_len = export_directory.number_of_functions.get(LittleEndian) as _;
let address_table =
unsafe { slice::from_raw_parts_mut(address_table_ptr, address_table_len) };
let name_table_ptr = unsafe {
address
.add(export_directory.address_of_names.get(LittleEndian) as _)
.wrapping_sub(rva)
.cast::<u32>()
};
let name_table_len = export_directory.number_of_names.get(LittleEndian) as _;
let name_table = unsafe { slice::from_raw_parts_mut(name_table_ptr, name_table_len) };
let ordinal_table_ptr = unsafe {
address
.add(export_directory.address_of_name_ordinals.get(LittleEndian) as _)
.wrapping_sub(rva)
.cast::<u16>()
};
let ordinal_table_len = export_directory.number_of_names.get(LittleEndian) as _;
let ordinal_table =
unsafe { slice::from_raw_parts_mut(ordinal_table_ptr, ordinal_table_len) };
Self {
export_directory,
address_table,
name_table,
ordinal_table,
start_address: address,
size,
}
}
#[inline(never)]
pub fn iter_name_ord(&self) -> impl Iterator<Item = (u32, u16)> + '_ {
self.name_table
.iter()
.copied()
.zip(self.ordinal_table.iter().copied())
}
#[inline(never)]
pub fn iter_string_addr(&self, image_base: *mut u8) -> impl Iterator<Item = (&CStr, *mut u8)> {
self.iter_name_ord().map(move |(name_rva, ord)| {
let string_ptr = unsafe { image_base.add(name_rva as _) };
let string = unsafe { CStr::from_ptr(string_ptr as _) };
let address_rva = unsafe { *self.address_table.get_unchecked(ord as usize) };
let address = unsafe { image_base.add(address_rva as _) };
(string, address)
})
}
}
pub struct ImportTable {
pub import_descriptors: &'static mut [ImageImportDescriptor],
}
impl ImportTable {
#[inline(never)]
pub fn parse(address: *mut u8, size: usize) -> Self {
let number_of_entries = size / size_of::<ImageImportDescriptor>() - 1;
let import_descriptor_ptr = address.cast::<ImageImportDescriptor>();
let import_descriptors =
unsafe { slice::from_raw_parts_mut(import_descriptor_ptr, number_of_entries) };
Self { import_descriptors }
}
}
+69
View File
@@ -0,0 +1,69 @@
use core::arch::asm;
#[inline(never)]
pub unsafe fn syscall3(number: u32, arg1: u64, arg2: u64, arg3: u64) -> u64 {
let output: u64;
asm!(
"syscall",
in("rax") number,
in("r10") arg1,
in("rdx") arg2,
in("r8") arg3,
lateout("rax") output,
lateout("rcx") _,
lateout("r11") _
);
output
}
#[inline(never)]
pub unsafe fn syscall5(number: u32, arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 {
let output: u64;
asm!(
"sub rsp, 0x38",
"mov [rsp+0x28], {arg5}",
"syscall",
"add rsp, 0x38",
arg5 = in(reg) arg5,
in("rax") number,
in("r10") arg1,
in("rdx") arg2,
in("r8") arg3,
in("r9") arg4,
lateout("rax") output,
lateout("rcx") _,
lateout("r11") _
);
output
}
#[inline(never)]
pub unsafe fn syscall6(
number: u32,
arg1: u64,
arg2: u64,
arg3: u64,
arg4: u64,
arg5: u64,
arg6: u64,
) -> u64 {
let output: u64;
asm!(
"sub rsp, 0x38",
"mov [rsp+0x30], {arg6}",
"mov [rsp+0x28], {arg5}",
"syscall",
"add rsp, 0x38",
arg5 = in(reg) arg5,
arg6 = in(reg) arg6,
in("rax") number,
in("r10") arg1,
in("rdx") arg2,
in("r8") arg3,
in("r9") arg4,
lateout("rax") output,
lateout("rcx") _,
lateout("r11") _
);
output
}