mirror of
https://github.com/EddieIvan01/memexec
synced 2024-06-16 12:08:03 +00:00
Add an IAT hooking interface
This commit is contained in:
parent
db55833355
commit
19fc8641de
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
*.exe
|
||||
|
@ -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 = []
|
||||
|
89
README.md
89
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**
|
||||
|
126
example/__wgetmainargs_hook.rs
Normal file
126
example/__wgetmainargs_hook.rs
Normal 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();
|
||||
}
|
||||
}
|
174
src/lib.rs
174
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<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
163
src/peloader/hook.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
*/
|
@ -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 = 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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> {
|
||||
|
Loading…
Reference in New Issue
Block a user