diff --git a/.gitignore b/.gitignore index 96ef6c0..69db9d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target Cargo.lock +*.exe diff --git a/Cargo.toml b/Cargo.toml index fe1c49c..21080ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,13 @@ [package] name = "memexec" -version = "0.1.2" +version = "0.2.0" authors = ["iv4n "] edition = "2018" -description = "A library for loading and executing PE (Portable Executable) without ever touching the disk" -git = "https://github.com/eddieivan01/memexec.git" +description = "A library for loading and executing PE (Portable Executable) from memory without ever touching the disk" +repository = "https://github.com/eddieivan01/memexec.git" license = "GPL-3.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +hook = [] diff --git a/README.md b/README.md index b5569d8..8104adf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![](https://img.shields.io/crates/v/memexec)](https://crates.io/crates/memexec) [![](https://img.shields.io/crates/d/memexec?label=downloads%40crates.io&style=social)](https://crates.io/crates/memexec) -A library for loading and executing PE (Portable Executable) without ever touching the disk +A library for loading and executing PE (Portable Executable) from memory without ever touching the disk # Features @@ -10,6 +10,7 @@ A library for loading and executing PE (Portable Executable) without ever touchi + Cross-architecture, applicable to x86 and x86-64 + Zero-dependency + Contains a simple, zero-copy PE parser submodule ++ Provides an IAT hooking interface # Install @@ -17,12 +18,12 @@ A library for loading and executing PE (Portable Executable) without ever touchi # Cargo.toml [dependencies] -memexec = "0.1" +memexec = "0.2" ``` # Usage -## Load and execute +## Execute from memory **⚠The architecture of target program must be same as current process, otherwise an error will occur** @@ -35,7 +36,7 @@ use std::io::Read; /* EXE */ /***********************************************************/ let mut buf = Vec::new(); -File::open("./mimikatz.exe") +File::open("./test.exe") .unwrap() .read_to_end(&mut buf) .unwrap(); @@ -43,6 +44,7 @@ File::open("./mimikatz.exe") unsafe { // If you need to pass command line parameters, // try to modify PEB's command line buffer + // Or use `memexec_exe_with_hooks` to hook related functions (see below) memexec::memexec_exe(&buf).unwrap(); } @@ -63,6 +65,85 @@ unsafe { } ``` +## IAT hooking + +Add the `hook` feature in `Cargo.toml` + +```toml +[dependencies] +memexec = { version="0.2", features=[ "hook" ] } +``` + +Hook the `__wgetmainargs` function (see `example/hook.rs`) + +```rust +let mut buf = Vec::new(); +File::open("./test.x64.exe") + .unwrap() + .read_to_end(&mut buf) + .unwrap(); + +let mut hooks = HashMap::new(); + +unsafe { + hooks.insert( + "msvcrt.dll!__wgetmainargs".into(), + mem::transmute::< + extern "win64" fn( + *mut i32, + *mut *const *const u16, + *const c_void, + i32, + *const c_void, + ) -> i32, + *const c_void, + >(__wgetmainargs), + ); + memexec::memexec_exe_with_hooks(&buf, &hooks).unwrap(); +} +``` + +The definition of `__wgetmainargs` (notice the calling convention on different archtectures): + +```rust +// https://docs.microsoft.com/en-us/cpp/c-runtime-library/getmainargs-wgetmainargs?view=msvc-160 +/* +int __wgetmainargs ( + int *_Argc, + wchar_t ***_Argv, + wchar_t ***_Env, + int _DoWildCard, + _startupinfo * _StartInfo) +*/ +#[cfg(all(target_arch = "x86_64", target_os = "windows"))] +extern "win64" fn __wgetmainargs( + _Argc: *mut i32, + _Argv: *mut *const *const u16, + _Env: *const c_void, + _DoWildCard: i32, + _StartInfo: *const c_void, +) -> i32 { + unsafe { + *_Argc = 2; + let a0: Vec<_> = "program_name\0" + .chars() + .map(|c| (c as u16).to_le()) + .collect(); + let a1: Vec<_> = "token::whoami\0" + .chars() + .map(|c| (c as u16).to_le()) + .collect(); + *_Argv = [a0.as_ptr(), a1.as_ptr()].as_ptr(); + + // Avoid calling destructor + mem::forget(a0); + mem::forget(a1); + } + + 0 +} +``` + ## Parse PE **PE parser could parse programs which have different architectures from current process** diff --git a/example/__wgetmainargs_hook.rs b/example/__wgetmainargs_hook.rs new file mode 100644 index 0000000..cbc9f31 --- /dev/null +++ b/example/__wgetmainargs_hook.rs @@ -0,0 +1,126 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; +use std::mem; +use std::os::raw::c_void; + +// https://docs.microsoft.com/en-us/cpp/c-runtime-library/getmainargs-wgetmainargs?view=msvc-160 +/* +int __wgetmainargs ( + int *_Argc, + wchar_t ***_Argv, + wchar_t ***_Env, + int _DoWildCard, + _startupinfo * _StartInfo) +*/ +#[cfg(all(target_arch = "x86", target_os = "windows"))] +extern "cdecl" fn __wgetmainargs( + _Argc: *mut i32, + _Argv: *mut *const *const u16, + _Env: *const c_void, + _DoWildCard: i32, + _StartInfo: *const c_void, +) -> i32 { + unsafe { + *_Argc = 2; + let a0: Vec<_> = "program_name\0" + .chars() + .map(|c| (c as u16).to_le()) + .collect(); + let a1: Vec<_> = "token::whoami\0" + .chars() + .map(|c| (c as u16).to_le()) + .collect(); + *_Argv = [a0.as_ptr(), a1.as_ptr()].as_ptr(); + + // Avoid calling destructor + mem::forget(a0); + mem::forget(a1); + } + + 0 +} + +#[cfg(all(target_arch = "x86", target_os = "windows"))] +fn hook_x86() { + let mut buf = Vec::new(); + File::open("./test.x86.exe") + .unwrap() + .read_to_end(&mut buf) + .unwrap(); + + let mut hooks = HashMap::new(); + + unsafe { + hooks.insert( + "msvcrt.dll!__wgetmainargs".into(), + mem::transmute::< + extern "cdecl" fn( + *mut i32, + *mut *const *const u16, + *const c_void, + i32, + *const c_void, + ) -> i32, + *const c_void, + >(__wgetmainargs), + ); + memexec::memexec_exe_with_hooks(&buf, &hooks).unwrap(); + } +} + +#[cfg(all(target_arch = "x86_64", target_os = "windows"))] +extern "win64" fn __wgetmainargs( + _Argc: *mut i32, + _Argv: *mut *const *const u16, + _Env: *const c_void, + _DoWildCard: i32, + _StartInfo: *const c_void, +) -> i32 { + unsafe { + *_Argc = 2; + let a0: Vec<_> = "program_name\0" + .chars() + .map(|c| (c as u16).to_le()) + .collect(); + let a1: Vec<_> = "token::whoami\0" + .chars() + .map(|c| (c as u16).to_le()) + .collect(); + *_Argv = [a0.as_ptr(), a1.as_ptr()].as_ptr(); + + // Avoid calling destructor + mem::forget(a0); + mem::forget(a1); + } + + 0 +} + +#[cfg(all(target_arch = "x86_64", target_os = "windows"))] +fn hook_x64() { + let mut buf = Vec::new(); + File::open("./test.x64.exe") + .unwrap() + .read_to_end(&mut buf) + .unwrap(); + + let mut hooks = HashMap::new(); + + unsafe { + hooks.insert( + "msvcrt.dll!__wgetmainargs".into(), + mem::transmute::< + extern "win64" fn( + *mut i32, + *mut *const *const u16, + *const c_void, + i32, + *const c_void, + ) -> i32, + *const c_void, + >(__wgetmainargs), + ); + memexec::memexec_exe_with_hooks(&buf, &hooks).unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 23875cf..efab0ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,12 +12,32 @@ use peloader::{DllLoader, ExeLoader}; use peparser::PE; use std::os::raw::c_void; +#[cfg(feature = "hook")] +pub use peloader::hook; +#[cfg(feature = "hook")] +use peloader::hook::ProcDesc; +#[cfg(feature = "hook")] +use std::collections::HashMap; + pub unsafe fn memexec_exe(bs: &[u8]) -> Result<()> { let pe = PE::new(bs)?; + #[cfg(feature = "hook")] + let loader = ExeLoader::new(&pe, None)?; + #[cfg(not(feature = "hook"))] let loader = ExeLoader::new(&pe)?; Ok(loader.invoke_entry_point()) } +#[cfg(feature = "hook")] +pub unsafe fn memexec_exe_with_hooks( + bs: &[u8], + hooks: &HashMap, +) -> Result<()> { + let pe = PE::new(bs)?; + let loader = ExeLoader::new(&pe, Some(hooks))?; + Ok(loader.invoke_entry_point()) +} + pub unsafe fn memexec_dll( bs: &[u8], hmod: *const c_void, @@ -25,10 +45,26 @@ pub unsafe fn memexec_dll( lp_reserved: *const c_void, ) -> Result { let pe = PE::new(bs)?; + #[cfg(feature = "hook")] + let loader = DllLoader::new(&pe, None)?; + #[cfg(not(feature = "hook"))] let loader = DllLoader::new(&pe)?; Ok(loader.invoke_entry_point(hmod, reason_for_call, lp_reserved)) } +#[cfg(feature = "hook")] +pub unsafe fn memexec_dll_with_hooks( + bs: &[u8], + hmod: *const c_void, + reason_for_call: u32, + lp_reserved: *const c_void, + hooks: &HashMap, +) -> Result { + let pe = PE::new(bs)?; + let loader = DllLoader::new(&pe, Some(hooks))?; + Ok(loader.invoke_entry_point(hmod, reason_for_call, lp_reserved)) +} + #[cfg(test)] mod tests { use super::*; @@ -36,9 +72,16 @@ mod tests { use std::io::Read; #[test] + #[cfg(not(feature = "hook"))] fn test_dll() { let mut buf = Vec::new(); - File::open("./test.dll") + #[cfg(all(target_arch = "x86_64", target_os = "windows"))] + File::open("./test.x64.dll") + .unwrap() + .read_to_end(&mut buf) + .unwrap(); + #[cfg(all(target_arch = "x86", target_os = "windows"))] + File::open("./test.x86.dll") .unwrap() .read_to_end(&mut buf) .unwrap(); @@ -49,9 +92,16 @@ mod tests { } #[test] + #[cfg(not(feature = "hook"))] fn test_exe() { let mut buf = Vec::new(); - File::open("./test.exe") + #[cfg(all(target_arch = "x86_64", target_os = "windows"))] + File::open("./test.x64.exe") + .unwrap() + .read_to_end(&mut buf) + .unwrap(); + #[cfg(all(target_arch = "x86", target_os = "windows"))] + File::open("./test.x86.exe") .unwrap() .read_to_end(&mut buf) .unwrap(); @@ -60,4 +110,124 @@ mod tests { memexec_exe(&buf).unwrap(); } } + + #[cfg(feature = "hook")] + use std::mem; + + #[cfg(feature = "hook")] + #[cfg(all(target_arch = "x86", target_os = "windows"))] + extern "cdecl" fn __wgetmainargs( + _Argc: *mut i32, + _Argv: *mut *const *const u16, + _Env: *const c_void, + _DoWildCard: i32, + _StartInfo: *const c_void, + ) -> i32 { + unsafe { + *_Argc = 2; + let a0: Vec<_> = "program_name\0" + .chars() + .map(|c| (c as u16).to_le()) + .collect(); + let a1: Vec<_> = "token::whoami\0" + .chars() + .map(|c| (c as u16).to_le()) + .collect(); + *_Argv = [a0.as_ptr(), a1.as_ptr()].as_ptr(); + + mem::forget(a0); + mem::forget(a1); + } + + 0 + } + + #[test] + #[cfg(feature = "hook")] + #[cfg(all(target_arch = "x86", target_os = "windows"))] + fn hook_x86() { + let mut buf = Vec::new(); + File::open("./test.x86.exe") + .unwrap() + .read_to_end(&mut buf) + .unwrap(); + + let mut hooks = HashMap::new(); + + unsafe { + hooks.insert( + "msvcrt.dll!__wgetmainargs".into(), + mem::transmute::< + extern "cdecl" fn( + *mut i32, + *mut *const *const u16, + *const c_void, + i32, + *const c_void, + ) -> i32, + *const c_void, + >(__wgetmainargs), + ); + memexec_exe_with_hooks(&buf, &hooks).unwrap(); + } + } + + #[cfg(feature = "hook")] + #[cfg(all(target_arch = "x86_64", target_os = "windows"))] + extern "win64" fn __wgetmainargs( + _Argc: *mut i32, + _Argv: *mut *const *const u16, + _Env: *const c_void, + _DoWildCard: i32, + _StartInfo: *const c_void, + ) -> i32 { + unsafe { + *_Argc = 2; + + let a0: Vec<_> = "program_name\0" + .chars() + .map(|c| (c as u16).to_le()) + .collect(); + let a1: Vec<_> = "token::whoami\0" + .chars() + .map(|c| (c as u16).to_le()) + .collect(); + *_Argv = [a0.as_ptr(), a1.as_ptr()].as_ptr(); + + mem::forget(a0); + mem::forget(a1); + } + + 0 + } + + #[test] + #[cfg(feature = "hook")] + #[cfg(all(target_arch = "x86_64", target_os = "windows"))] + fn hook_x64() { + let mut buf = Vec::new(); + File::open("./test.x64.exe") + .unwrap() + .read_to_end(&mut buf) + .unwrap(); + + let mut hooks = HashMap::new(); + + unsafe { + hooks.insert( + "msvcrt.dll!__wgetmainargs".into(), + mem::transmute::< + extern "win64" fn( + *mut i32, + *mut *const *const u16, + *const c_void, + i32, + *const c_void, + ) -> i32, + *const c_void, + >(__wgetmainargs), + ); + memexec_exe_with_hooks(&buf, &hooks).unwrap(); + } + } } diff --git a/src/peloader/error.rs b/src/peloader/error.rs index 08b1aa8..a0d4ca5 100644 --- a/src/peloader/error.rs +++ b/src/peloader/error.rs @@ -10,6 +10,7 @@ pub enum Error { MismatchedLoader, NoEntryPoint, UnsupportedDotNetExecutable, + InvalidProcDescString, } pub type Result = std::result::Result; @@ -19,3 +20,9 @@ impl std::convert::From for Error { Error::InvalidUtf8String } } + +impl std::convert::From for Error { + fn from(_: std::num::ParseIntError) -> Self { + Error::InvalidProcDescString + } +} diff --git a/src/peloader/hook.rs b/src/peloader/hook.rs new file mode 100644 index 0000000..50be1e5 --- /dev/null +++ b/src/peloader/hook.rs @@ -0,0 +1,163 @@ +#![cfg(feature = "hook")] + +use std::hash::Hash; +use std::mem; + +#[derive(Debug)] +pub enum Thunk<'a> { + Ordinal(isize), + Name(&'a str), +} + +impl<'a> PartialEq for Thunk<'a> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (&Thunk::Ordinal(s), &Thunk::Ordinal(o)) => s == o, + (&Thunk::Name(s), &Thunk::Name(o)) => s == o, + _ => false, + } + } +} + +impl<'a> Eq for Thunk<'a> {} + +impl<'a> Hash for Thunk<'a> { + fn hash(&self, state: &mut H) { + mem::discriminant(self).hash(state); + match *self { + Thunk::Ordinal(o) => o.hash(state), + Thunk::Name(n) => n.hash(state), + }; + } +} + +#[derive(Debug)] +pub struct ProcDesc<'a> { + // Use `String` instead of `&str`, + // because we need a case conversion in `new` function + dll: String, + thunk: Thunk<'a>, +} + +impl<'a> PartialEq for ProcDesc<'a> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.dll == other.dll && self.thunk == other.thunk + } +} + +impl<'a> Eq for ProcDesc<'a> {} + +impl<'a> Hash for ProcDesc<'a> { + fn hash(&self, state: &mut H) { + self.dll.hash(state); + self.thunk.hash(state); + } +} + +impl<'a> ProcDesc<'a> { + /// Dll name is case insensitive + pub fn new(dll: &str, thunk: Thunk<'a>) -> ProcDesc<'a> { + ProcDesc { + dll: dll.to_ascii_lowercase(), + thunk: thunk, + } + } +} + +// Couldn't implement `FromStr` trait here, +// because the signature of `from_str` doesn't allow an explicit lifetime hint +impl<'a> From<&'a str> for ProcDesc<'a> { + fn from(s: &'a str) -> Self { + match s.find('!') { + Some(i) => { + let (dll, proc_name) = s.split_at(i); + ProcDesc { + dll: dll.to_ascii_lowercase(), + thunk: Thunk::Name(&proc_name[1..]), + } + } + None => match s.find('#') { + Some(i) => { + let (dll, proc_ord) = s.split_at(i); + ProcDesc { + dll: dll.to_ascii_lowercase(), + thunk: Thunk::Ordinal(proc_ord[1..].parse().unwrap_or(0)), + } + } + // Failure case + None => ProcDesc { + dll: String::new(), + thunk: Thunk::Ordinal(0), + }, + }, + } + } +} + +/* +use std::collections::HashMap; +use std::os::raw::c_void; + +trait ProcDescTbl<'a> { + fn get(&self, proc_desc: &'a ProcDesc) -> Option<&*const c_void>; +} + +impl<'a> ProcDescTbl<'a> for HashMap, *const c_void> { + fn get(&self, proc_desc: &'a ProcDesc) -> Option<&*const c_void> { + self.get(proc_desc) + } +} +*/ + +/* +// Implement FNV-1a hash +#[derive(Debug, Hash)] +pub struct ProcHash(u64, u64); + +impl PartialEq for ProcHash { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +impl Eq for ProcHash {} + +impl ProcHash { + /// Dll name is case insensitive + /// ProcHash::new("kernel32.dll", Thunk::Name("CreateProcessW")) + pub fn new(dll: &str, thunk: Thunk) -> ProcHash { + // The hash of DLL and thunk is calculated and saved separately, + // in order to reduce the collision probability + let mut h1: u64 = 0xcbf29ce484222325; + + for c in dll.chars() { + h1 ^= c.to_ascii_lowercase() as u64; + h1 = h1.wrapping_mul(0x100000001b3); + } + + let mut h2: u64 = 0xcbf29ce484222325; + match thunk { + Thunk::Ordinal(ord) => { + // # + h2 ^= 0x23; + h2 = h2.wrapping_mul(0x100000001b3); + h2 ^= ord as u64; + h2 = h2.wrapping_mul(0x100000001b3); + } + Thunk::Name(name) => { + // ! + h2 ^= 0x21; + h2 = h2.wrapping_mul(0x100000001b3); + for c in name.chars() { + h2 ^= c as u64; + h2 = h2.wrapping_mul(0x100000001b3); + } + } + } + + ProcHash(h1, h2) + } +} +*/ diff --git a/src/peloader/mod.rs b/src/peloader/mod.rs index 821a569..da25da3 100644 --- a/src/peloader/mod.rs +++ b/src/peloader/mod.rs @@ -4,48 +4,24 @@ pub mod winapi; use crate::peparser::def::*; use crate::peparser::PE; -use def::{DllMain, DLL_PROCESS_ATTACH, MEM_COMMIT, MEM_RESERVE, PVOID}; +use def::{DllMain, DLL_PROCESS_ATTACH, MEM_COMMIT, MEM_RESERVE}; use error::{Error, Result}; use std::ffi::CStr; use std::mem; use std::os::raw::c_void; use std::ptr; -unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> { - // Step1: allocate memory for image - let mut base_addr = pe.pe_header.nt_header.get_image_base(); - let size = pe.pe_header.nt_header.get_size_of_image(); +#[cfg(feature = "hook")] +use hook::{ProcDesc, Thunk}; +#[cfg(feature = "hook")] +use std::collections::HashMap; +#[cfg(feature = "hook")] +pub mod hook; - // ALSR - if winapi::nt_alloc_vm( - &base_addr as _, - &size as _, - MEM_RESERVE | MEM_COMMIT, - PAGE_READWRITE, - ) - .is_err() - { - base_addr = 0 as PVOID; - winapi::nt_alloc_vm( - &base_addr as _, - &size as _, - MEM_RESERVE | MEM_COMMIT, - PAGE_READWRITE, - )?; - } - - // Step2: copy sections - for section in pe.section_area.section_table { - ptr::copy_nonoverlapping( - pe.raw.as_ptr().offset(section.PointerToRawData as isize), - base_addr.offset(section.VirtualAddress as isize) as *mut u8, - section.SizeOfRawData as usize, - ); - } - - // Step3: handle base relocataion table +unsafe fn patch_reloc_table(pe: &PE, base_addr: *const c_void) -> Result<()> { let reloc_entry = &pe.pe_header.nt_header.get_data_directory()[IMAGE_DIRECTORY_ENTRY_BASERELOC]; - let image_base_offset = base_addr as usize - pe.pe_header.nt_header.get_image_base() as usize; + let image_base_offset = base_addr as isize - pe.pe_header.nt_header.get_image_base() as isize; + if image_base_offset != 0 && reloc_entry.VirtualAddress != 0 && reloc_entry.Size != 0 { let mut reloc_table_ptr = base_addr.offset(reloc_entry.VirtualAddress as isize) as *const u8; @@ -62,7 +38,7 @@ unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> { if (item >> 12) == IMAGE_REL_BASED { let patch_addr = base_addr .offset(reloc_block.VirtualAddress as isize + (item & 0xfff) as isize) - as *mut usize; + as *mut isize; *patch_addr = *patch_addr + image_base_offset; } } @@ -70,24 +46,33 @@ unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> { reloc_table_ptr = reloc_table_ptr.offset(reloc_block.SizeOfBlock as isize); } } + Ok(()) +} - // Step4: resolve import symbols +unsafe fn resolve_import_symbols( + pe: &PE, + base_addr: *const c_void, + #[cfg(feature = "hook")] hooks: Option<&HashMap>, +) -> Result<()> { let import_entry = &pe.pe_header.nt_header.get_data_directory()[IMAGE_DIRECTORY_ENTRY_IMPORT]; if import_entry.Size != 0 && import_entry.VirtualAddress != 0 { - for i in 0..(import_entry.Size as usize / mem::size_of::()) { - let import_desc = - &*mem::transmute::(base_addr.offset( - import_entry.VirtualAddress as isize - + (i * mem::size_of::()) as isize, - )); - if 0 == import_desc.Name { + let mut import_desc_ptr = base_addr.offset(import_entry.VirtualAddress as isize) + as *const IMAGE_IMPORT_DESCRIPTOR; + loop { + let import_desc = &*import_desc_ptr; + if 0 == import_desc.Name + && 0 == import_desc.FirstThunk + && 0 == import_desc.OriginalFirstThunk + && 0 == import_desc.TimeDateStamp + && 0 == import_desc.ForwarderChain + { break; } let dll_name = CStr::from_ptr(base_addr.offset(import_desc.Name as isize) as *const i8) .to_str()?; // TODO: implement loading module by calling self recursively - let hmod = winapi::load_library(dll_name)?; + let hmod = winapi::load_library(&dll_name)?; // Whether the ILT (called INT in IDA) exists? (some linkers didn't generate the ILT) let (mut iat_ptr, mut ilt_ptr) = if import_desc.OriginalFirstThunk != 0 { @@ -109,31 +94,142 @@ unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> { break; } - let proc_addr; if thunk_data & IMAGE_ORDINAL_FLAG != 0 { // Import by ordinal number - proc_addr = winapi::get_proc_address_by_ordinal(hmod, thunk_data & 0xffff)?; + + #[cfg(not(feature = "hook"))] + let proc_addr = winapi::get_proc_address_by_ordinal(hmod, thunk_data & 0xffff)?; + #[cfg(feature = "hook")] + let proc_addr = match hooks { + Some(hooks) => { + match hooks.get(&ProcDesc::new( + dll_name, + Thunk::Ordinal(thunk_data & 0xffff), + )) { + Some(addr) => *addr, + None => { + winapi::get_proc_address_by_ordinal(hmod, thunk_data & 0xffff)? + } + } + } + None => winapi::get_proc_address_by_ordinal(hmod, thunk_data & 0xffff)?, + }; + + // rust-lang/rust/issues/15701 + *iat_ptr = proc_addr as IMAGE_THUNK_DATA; } else { // TODO: implement resolving proc address by `IMAGE_IMPORT_BY_NAME.Hint` - let hint_name_table = &*mem::transmute::( - base_addr.offset(thunk_data), - ); + let hint_name_table = &*mem::transmute::< + *const c_void, + *const IMAGE_IMPORT_BY_NAME, + >(base_addr.offset(thunk_data)); if 0 == hint_name_table.Name { break; } - proc_addr = winapi::get_proc_address( + #[cfg(not(feature = "hook"))] + let proc_addr = winapi::get_proc_address_by_name( hmod, CStr::from_ptr(&hint_name_table.Name as _).to_str()?, )?; + #[cfg(feature = "hook")] + let proc_addr = match hooks { + Some(hooks) => match hooks.get(&ProcDesc::new( + dll_name, + Thunk::Name(CStr::from_ptr(&hint_name_table.Name as _).to_str()?), + )) { + Some(addr) => *addr, + None => winapi::get_proc_address_by_name( + hmod, + CStr::from_ptr(&hint_name_table.Name as _).to_str()?, + )?, + }, + None => winapi::get_proc_address_by_name( + hmod, + CStr::from_ptr(&hint_name_table.Name as _).to_str()?, + )?, + }; + + *iat_ptr = proc_addr as IMAGE_THUNK_DATA; } - *iat_ptr = proc_addr as IMAGE_THUNK_DATA; iat_ptr = iat_ptr.offset(1); ilt_ptr = ilt_ptr.offset(1); } + + import_desc_ptr = import_desc_ptr.offset(1); } } + Ok(()) +} + +unsafe fn call_tls_callback(pe: &PE, base_addr: *const c_void) -> Result<()> { + let tls_entry = &pe.pe_header.nt_header.get_data_directory()[IMAGE_DIRECTORY_ENTRY_TLS]; + if tls_entry.Size != 0 && tls_entry.VirtualAddress != 0 { + let tls = &*mem::transmute::<*const c_void, *const IMAGE_TLS_DIRECTORY>( + base_addr.offset(tls_entry.VirtualAddress as isize), + ); + let mut tls_callback_addr = tls.AddressOfCallBacks as *const *const c_void; + + loop { + if *tls_callback_addr == 0 as _ { + break; + } + + mem::transmute::<*const c_void, PIMAGE_TLS_CALLBACK>(*tls_callback_addr)( + base_addr, + DLL_PROCESS_ATTACH, + 0 as _, + ); + tls_callback_addr = tls_callback_addr.offset(1); + } + } + Ok(()) +} + +unsafe fn load_pe_into_mem( + pe: &PE, + #[cfg(feature = "hook")] hooks: Option<&HashMap>, +) -> Result<*const c_void> { + // Step1: allocate memory for image + let mut base_addr = pe.pe_header.nt_header.get_image_base(); + let size = pe.pe_header.nt_header.get_size_of_image(); + + // ASLR + if winapi::nt_alloc_vm( + &base_addr as _, + &size as _, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, + ) + .is_err() + { + base_addr = 0 as *const c_void; + winapi::nt_alloc_vm( + &base_addr as _, + &size as _, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, + )?; + } + + // Step2: copy sections + for section in pe.section_area.section_table { + ptr::copy_nonoverlapping( + pe.raw.as_ptr().offset(section.PointerToRawData as isize), + base_addr.offset(section.VirtualAddress as isize) as *mut u8, + section.SizeOfRawData as usize, + ); + } + + // Step3: handle base relocataion table + patch_reloc_table(pe, base_addr)?; + + // Step4: resolve import symbols + #[cfg(feature = "hook")] + resolve_import_symbols(pe, base_addr, hooks)?; + #[cfg(not(feature = "hook"))] + resolve_import_symbols(pe, base_addr)?; // Step5: restore sections' protection for section in pe.section_area.section_table { @@ -150,32 +246,14 @@ unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> { } // Step6: call TLS callback - let tls_entry = &pe.pe_header.nt_header.get_data_directory()[IMAGE_DIRECTORY_ENTRY_TLS]; - if tls_entry.Size != 0 && tls_entry.VirtualAddress != 0 { - let tls = &*mem::transmute::( - base_addr.offset(tls_entry.VirtualAddress as isize), - ); - let mut tls_callback_addr = tls.AddressOfCallBacks as *const PVOID; - - loop { - if *tls_callback_addr == 0 as _ { - break; - } - - mem::transmute::(*tls_callback_addr)( - base_addr, - DLL_PROCESS_ATTACH, - 0 as _, - ); - tls_callback_addr = tls_callback_addr.offset(1); - } - } + call_tls_callback(pe, base_addr)?; Ok(base_addr) } fn check_platform(pe: &PE) -> Result<()> { - if (mem::size_of::() == 4 && pe.is_x86()) || mem::size_of::() == 8 && pe.is_x64() + if (mem::size_of::() == 4 && pe.is_x86()) + || (mem::size_of::() == 8 && pe.is_x64()) { Ok(()) } else { @@ -188,7 +266,10 @@ pub struct ExeLoader { } impl ExeLoader { - pub unsafe fn new(pe: &PE) -> Result { + pub unsafe fn new( + pe: &PE, + #[cfg(feature = "hook")] hooks: Option<&HashMap>, + ) -> Result { check_platform(pe)?; if pe.is_dll() { return Err(Error::MismatchedLoader); @@ -202,14 +283,18 @@ impl ExeLoader { if entry_point == 0 { Err(Error::NoEntryPoint) } else { + #[cfg(feature = "hook")] + let entry_point_va = load_pe_into_mem(pe, hooks)?.offset(entry_point); + #[cfg(not(feature = "hook"))] + let entry_point_va = load_pe_into_mem(pe)?.offset(entry_point); Ok(ExeLoader { - entry_point_va: load_pe_into_mem(pe)?.offset(entry_point), + entry_point_va: entry_point_va, }) } } pub unsafe fn invoke_entry_point(&self) { - mem::transmute::(self.entry_point_va)() + mem::transmute::<*const c_void, extern "system" fn()>(self.entry_point_va)() } } @@ -218,7 +303,10 @@ pub struct DllLoader { } impl DllLoader { - pub unsafe fn new(pe: &PE) -> Result { + pub unsafe fn new( + pe: &PE, + #[cfg(feature = "hook")] hooks: Option<&HashMap>, + ) -> Result { check_platform(pe)?; if !pe.is_dll() { return Err(Error::MismatchedLoader); @@ -232,8 +320,12 @@ impl DllLoader { if entry_point == 0 { Err(Error::NoEntryPoint) } else { + #[cfg(feature = "hook")] + let entry_point_va = load_pe_into_mem(pe, hooks)?.offset(entry_point); + #[cfg(not(feature = "hook"))] + let entry_point_va = load_pe_into_mem(pe)?.offset(entry_point); Ok(DllLoader { - entry_point_va: load_pe_into_mem(pe)?.offset(entry_point), + entry_point_va: entry_point_va, }) } } @@ -244,6 +336,10 @@ impl DllLoader { reason_for_call: u32, lp_reserved: *const c_void, ) -> bool { - mem::transmute::(self.entry_point_va)(hmod, reason_for_call, lp_reserved) + mem::transmute::<*const c_void, DllMain>(self.entry_point_va)( + hmod, + reason_for_call, + lp_reserved, + ) } } diff --git a/src/peloader/winapi.rs b/src/peloader/winapi.rs index bca8040..4a5e53a 100644 --- a/src/peloader/winapi.rs +++ b/src/peloader/winapi.rs @@ -23,7 +23,7 @@ pub fn load_library(lib: &str) -> Result { } } -pub fn get_proc_address(hmod: HMODULE, proc_name: &str) -> Result { +pub fn get_proc_address_by_name(hmod: HMODULE, proc_name: &str) -> Result { if let Ok(proc_name) = CString::new(proc_name) { let proc = unsafe { GetProcAddress(hmod, proc_name.as_ptr()) }; if proc == 0 as PVOID { @@ -71,7 +71,7 @@ pub unsafe fn nt_alloc_vm( ) -> Result<()> { if p_nt_alloc_vm == 0 as _ { p_nt_alloc_vm = - get_proc_address(load_library("ntdll.dll")?, "NtAllocateVirtualMemory")? as _; + get_proc_address_by_name(load_library("ntdll.dll")?, "NtAllocateVirtualMemory")? as _; }; let ret = mem::transmute::< @@ -119,7 +119,7 @@ pub unsafe fn nt_protect_vm( ) -> Result<()> { if p_nt_protect_vm == 0 as _ { p_nt_protect_vm = - get_proc_address(load_library("ntdll.dll")?, "NtProtectVirtualMemory")? as _; + get_proc_address_by_name(load_library("ntdll.dll")?, "NtProtectVirtualMemory")? as _; }; let old_protect: ULONG = 0; diff --git a/src/peparser/def.rs b/src/peparser/def.rs index 1ad36cf..a05fd6b 100644 --- a/src/peparser/def.rs +++ b/src/peparser/def.rs @@ -267,6 +267,10 @@ struct_wrapper!( } */ OriginalFirstThunk: DWORD, + // 0 if not bound, + // -1 if bound, and real date\time stamp + // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) + // O.W. date/time stamp of DLL bound to (Old BIND) TimeDateStamp: DWORD, ForwarderChain: DWORD, // -1 if no forwarders Name: DWORD, diff --git a/src/peparser/header.rs b/src/peparser/header.rs index 1b8b943..cae1950 100644 --- a/src/peparser/header.rs +++ b/src/peparser/header.rs @@ -3,7 +3,7 @@ use super::error::{Error, Result}; use std::mem; use std::os::raw::c_void; -// zero copy +// Zero copy #[derive(Debug)] pub enum ImageNtHeaders<'a> { x86(&'a IMAGE_NT_HEADERS32), @@ -61,7 +61,7 @@ impl<'a> ImageNtHeaders<'a> { } } -// zero copy +// Zero copy #[repr(C)] #[derive(Debug)] pub struct PeHeader<'a> {