Add an IAT hooking interface

This commit is contained in:
EddieIvan01 2020-11-25 18:26:36 +08:00
parent db55833355
commit 19fc8641de
11 changed files with 743 additions and 92 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
Cargo.lock
*.exe

View File

@ -1,10 +1,13 @@
[package]
name = "memexec"
version = "0.1.2"
version = "0.2.0"
authors = ["iv4n <iv4n.cc@qq.com>"]
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 = []

View File

@ -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**

View File

@ -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();
}
}

View File

@ -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<ProcDesc, *const c_void>,
) -> 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<bool> {
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<ProcDesc, *const c_void>,
) -> Result<bool> {
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();
}
}
}

View File

@ -10,6 +10,7 @@ pub enum Error {
MismatchedLoader,
NoEntryPoint,
UnsupportedDotNetExecutable,
InvalidProcDescString,
}
pub type Result<T> = std::result::Result<T, Error>;
@ -19,3 +20,9 @@ impl std::convert::From<std::str::Utf8Error> for Error {
Error::InvalidUtf8String
}
}
impl std::convert::From<std::num::ParseIntError> for Error {
fn from(_: std::num::ParseIntError) -> Self {
Error::InvalidProcDescString
}
}

163
src/peloader/hook.rs Normal file
View File

@ -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<H: std::hash::Hasher>(&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<H: std::hash::Hasher>(&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<ProcDesc<'a>, *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)
}
}
*/

View File

@ -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<ProcDesc, *const c_void>>,
) -> 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::<IMAGE_IMPORT_DESCRIPTOR>()) {
let import_desc =
&*mem::transmute::<PVOID, *const IMAGE_IMPORT_DESCRIPTOR>(base_addr.offset(
import_entry.VirtualAddress as isize
+ (i * mem::size_of::<IMAGE_IMPORT_DESCRIPTOR>()) 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::<PVOID, *const IMAGE_IMPORT_BY_NAME>(
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<ProcDesc, *const c_void>>,
) -> 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::<PVOID, *const IMAGE_TLS_DIRECTORY>(
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::<PVOID, PIMAGE_TLS_CALLBACK>(*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::<usize>() == 4 && pe.is_x86()) || mem::size_of::<usize>() == 8 && pe.is_x64()
if (mem::size_of::<usize>() == 4 && pe.is_x86())
|| (mem::size_of::<usize>() == 8 && pe.is_x64())
{
Ok(())
} else {
@ -188,7 +266,10 @@ pub struct ExeLoader {
}
impl ExeLoader {
pub unsafe fn new(pe: &PE) -> Result<ExeLoader> {
pub unsafe fn new(
pe: &PE,
#[cfg(feature = "hook")] hooks: Option<&HashMap<ProcDesc, *const c_void>>,
) -> Result<ExeLoader> {
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::<PVOID, extern "system" fn()>(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<DllLoader> {
pub unsafe fn new(
pe: &PE,
#[cfg(feature = "hook")] hooks: Option<&HashMap<ProcDesc, *const c_void>>,
) -> Result<DllLoader> {
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::<PVOID, DllMain>(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,
)
}
}

View File

@ -23,7 +23,7 @@ pub fn load_library(lib: &str) -> Result<HMODULE> {
}
}
pub fn get_proc_address(hmod: HMODULE, proc_name: &str) -> Result<PVOID> {
pub fn get_proc_address_by_name(hmod: HMODULE, proc_name: &str) -> Result<PVOID> {
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;

View File

@ -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,

View File

@ -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> {