6
0
mirror of https://github.com/EddieIvan01/memexec synced 2024-06-26 00:39:51 +00:00

Add a check on .NET executable

This commit is contained in:
EddieIvan01 2020-11-13 21:05:55 +08:00
parent cb3defa6ca
commit db55833355
10 changed files with 91 additions and 65 deletions

@ -1,17 +1,10 @@
[package] [package]
name = "memexec" name = "memexec"
version = "0.1.1" version = "0.1.2"
authors = ["iv4n <iv4n.cc@qq.com>"] authors = ["iv4n <iv4n.cc@qq.com>"]
edition = "2018" edition = "2018"
description = "Lib used to load and execute PE (Portable Executable) in memory without ever touching the disk" description = "A library for loading and executing PE (Portable Executable) without ever touching the disk"
git = "https://github.com/eddieivan01/memexec.git" git = "https://github.com/eddieivan01/memexec.git"
license = "GPL-3.0" license = "GPL-3.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
lto = "fat"
codegen-units = 1
opt-level = 3
panic = "abort"
incremental = false

@ -2,14 +2,14 @@
[![](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) [![](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)
Lib used to load and execute PE (Portable Executable) in memory without ever touching the disk A library for loading and executing PE (Portable Executable) without ever touching the disk
# Features # Features
+ Applicable to EXE and DLL + Applicable to EXE and DLL (except .NET assembly)
+ Cross-architecture, applicable to x86 and x86_64 + Cross-architecture, applicable to x86 and x86-64
+ Zero-dependency + Zero-dependency
+ Contains a zero-copy submodule of PE parser + Contains a simple, zero-copy PE parser submodule
# Install # Install
@ -76,6 +76,12 @@ let pe = PE::new(&buf);
println!("{:?}", pe); println!("{:?}", pe);
``` ```
# TODO
- [ ] Replace `LoadLibrary` with calling `load_pe_into_mem` recursively
- [ ] Replace `GetProcAddress` with self-implemented [`LdrpSnapThunk`](https://doxygen.reactos.org/dd/d83/ntdllp_8h.html#ae2196bc7f46cc2a92d36b7c4881ee633), so as to support resolving proc address by `IMAGE_IMPORT_BY_NAME.Hint`
# License # License
The GPLv3 license The GPLv3 license

@ -1,6 +1,3 @@
//! # memexec
//! Lib used to load and execute PE (Portable Executable) in memory without ever touching the disk
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
#![allow(overflowing_literals)] #![allow(overflowing_literals)]
@ -17,7 +14,7 @@ use std::os::raw::c_void;
pub unsafe fn memexec_exe(bs: &[u8]) -> Result<()> { pub unsafe fn memexec_exe(bs: &[u8]) -> Result<()> {
let pe = PE::new(bs)?; let pe = PE::new(bs)?;
let loader = ExeLoader::new(&pe).unwrap(); let loader = ExeLoader::new(&pe)?;
Ok(loader.invoke_entry_point()) Ok(loader.invoke_entry_point())
} }
@ -28,7 +25,7 @@ pub unsafe fn memexec_dll(
lp_reserved: *const c_void, lp_reserved: *const c_void,
) -> Result<bool> { ) -> Result<bool> {
let pe = PE::new(bs)?; let pe = PE::new(bs)?;
let loader = DllLoader::new(&pe).unwrap(); let loader = DllLoader::new(&pe)?;
Ok(loader.invoke_entry_point(hmod, reason_for_call, lp_reserved)) Ok(loader.invoke_entry_point(hmod, reason_for_call, lp_reserved))
} }

@ -9,6 +9,7 @@ pub enum Error {
MismatchedArch, MismatchedArch,
MismatchedLoader, MismatchedLoader,
NoEntryPoint, NoEntryPoint,
UnsupportedDotNetExecutable,
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;

@ -45,7 +45,7 @@ unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> {
// Step3: handle base relocataion table // Step3: handle base relocataion table
let reloc_entry = &pe.pe_header.nt_header.get_data_directory()[IMAGE_DIRECTORY_ENTRY_BASERELOC]; let reloc_entry = &pe.pe_header.nt_header.get_data_directory()[IMAGE_DIRECTORY_ENTRY_BASERELOC];
let image_base_offset = base_addr as isize - pe.pe_header.nt_header.get_image_base() as isize; let image_base_offset = base_addr as usize - pe.pe_header.nt_header.get_image_base() as usize;
if image_base_offset != 0 && reloc_entry.VirtualAddress != 0 && reloc_entry.Size != 0 { if image_base_offset != 0 && reloc_entry.VirtualAddress != 0 && reloc_entry.Size != 0 {
let mut reloc_table_ptr = let mut reloc_table_ptr =
base_addr.offset(reloc_entry.VirtualAddress as isize) as *const u8; base_addr.offset(reloc_entry.VirtualAddress as isize) as *const u8;
@ -62,7 +62,7 @@ unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> {
if (item >> 12) == IMAGE_REL_BASED { if (item >> 12) == IMAGE_REL_BASED {
let patch_addr = base_addr let patch_addr = base_addr
.offset(reloc_block.VirtualAddress as isize + (item & 0xfff) as isize) .offset(reloc_block.VirtualAddress as isize + (item & 0xfff) as isize)
as *mut isize; as *mut usize;
*patch_addr = *patch_addr + image_base_offset; *patch_addr = *patch_addr + image_base_offset;
} }
} }
@ -86,13 +86,14 @@ unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> {
let dll_name = CStr::from_ptr(base_addr.offset(import_desc.Name as isize) as *const i8) let dll_name = CStr::from_ptr(base_addr.offset(import_desc.Name as isize) as *const i8)
.to_str()?; .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 HNT exists // Whether the ILT (called INT in IDA) exists? (some linkers didn't generate the ILT)
let (mut iat_ptr, mut hnt_ptr) = if import_desc.DUMMYUNIONNAME != 0 { let (mut iat_ptr, mut ilt_ptr) = if import_desc.OriginalFirstThunk != 0 {
( (
base_addr.offset(import_desc.FirstThunk as isize) as *mut IMAGE_THUNK_DATA, base_addr.offset(import_desc.FirstThunk as isize) as *mut IMAGE_THUNK_DATA,
base_addr.offset(import_desc.DUMMYUNIONNAME as isize) base_addr.offset(import_desc.OriginalFirstThunk as isize)
as *const IMAGE_THUNK_DATA, as *const IMAGE_THUNK_DATA,
) )
} else { } else {
@ -103,32 +104,33 @@ unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> {
}; };
loop { loop {
let thunk_data = *hnt_ptr as isize; let thunk_data = *ilt_ptr as isize;
if thunk_data == 0 { if thunk_data == 0 {
break; break;
} }
let proc_addr; let proc_addr;
if thunk_data & IMAGE_ORDINAL_FLAG == IMAGE_ORDINAL_FLAG { if thunk_data & IMAGE_ORDINAL_FLAG != 0 {
// Import by ordinal number // Import by ordinal number
proc_addr = winapi::get_proc_address_by_ordinal(hmod, thunk_data & 0xffff)?; proc_addr = winapi::get_proc_address_by_ordinal(hmod, thunk_data & 0xffff)?;
} else { } else {
let import_by_name = &*mem::transmute::<PVOID, *const IMAGE_IMPORT_BY_NAME>( // 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), base_addr.offset(thunk_data),
); );
if 0 == import_by_name.Name { if 0 == hint_name_table.Name {
break; break;
} }
proc_addr = winapi::get_proc_address( proc_addr = winapi::get_proc_address(
hmod, hmod,
CStr::from_ptr(&import_by_name.Name as _).to_str()?, 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); iat_ptr = iat_ptr.offset(1);
hnt_ptr = hnt_ptr.offset(1); ilt_ptr = ilt_ptr.offset(1);
} }
} }
} }
@ -143,7 +145,7 @@ unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> {
winapi::nt_protect_vm( winapi::nt_protect_vm(
&(base_addr.offset(section.VirtualAddress as isize)) as _, &(base_addr.offset(section.VirtualAddress as isize)) as _,
&size as _, &size as _,
section.protect_value(), section.get_protection(),
)?; )?;
} }
@ -189,16 +191,20 @@ impl ExeLoader {
pub unsafe fn new(pe: &PE) -> Result<ExeLoader> { pub unsafe fn new(pe: &PE) -> Result<ExeLoader> {
check_platform(pe)?; check_platform(pe)?;
if pe.is_dll() { if pe.is_dll() {
Err(Error::MismatchedLoader) return Err(Error::MismatchedLoader);
}
if pe.is_dot_net() {
return Err(Error::UnsupportedDotNetExecutable);
}
let entry_point = pe.pe_header.nt_header.get_address_of_entry_point();
if entry_point == 0 {
Err(Error::NoEntryPoint)
} else { } else {
let entry_point = pe.pe_header.nt_header.get_address_of_entry_point(); Ok(ExeLoader {
if entry_point == 0 { entry_point_va: load_pe_into_mem(pe)?.offset(entry_point),
Err(Error::NoEntryPoint) })
} else {
Ok(ExeLoader {
entry_point_va: load_pe_into_mem(pe)?.offset(entry_point),
})
}
} }
} }
@ -215,16 +221,20 @@ impl DllLoader {
pub unsafe fn new(pe: &PE) -> Result<DllLoader> { pub unsafe fn new(pe: &PE) -> Result<DllLoader> {
check_platform(pe)?; check_platform(pe)?;
if !pe.is_dll() { if !pe.is_dll() {
Err(Error::MismatchedLoader) return Err(Error::MismatchedLoader);
}
if pe.is_dot_net() {
return Err(Error::UnsupportedDotNetExecutable);
}
let entry_point = pe.pe_header.nt_header.get_address_of_entry_point();
if entry_point == 0 {
Err(Error::NoEntryPoint)
} else { } else {
let entry_point = pe.pe_header.nt_header.get_address_of_entry_point(); Ok(DllLoader {
if entry_point == 0 { entry_point_va: load_pe_into_mem(pe)?.offset(entry_point),
Err(Error::NoEntryPoint) })
} else {
Ok(DllLoader {
entry_point_va: load_pe_into_mem(pe)?.offset(entry_point),
})
}
} }
} }

@ -28,8 +28,9 @@ impl<'a> ImageNtHeaders<'a> {
return Err(Error::UnsupportedMachine); return Err(Error::UnsupportedMachine);
} }
// 32-bit .NET assembly may not set IMAGE_FILE_32BIT_MACHINE
if h.FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE == 0 if h.FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE == 0
|| h.FileHeader.Characteristics & IMAGE_FILE_32BIT_MACHINE == 0 // || h.FileHeader.Characteristics & IMAGE_FILE_32BIT_MACHINE == 0
{ {
return Err(Error::InvalidFileHeaderCharacteristics); return Err(Error::InvalidFileHeaderCharacteristics);
} }
@ -44,7 +45,7 @@ impl<'a> ImageNtHeaders<'a> {
} }
if h.FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE == 0 if h.FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE == 0
|| h.FileHeader.Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE == 0 // || h.FileHeader.Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE == 0
{ {
return Err(Error::InvalidFileHeaderCharacteristics); return Err(Error::InvalidFileHeaderCharacteristics);
} }

@ -12,7 +12,9 @@ pub(crate) const IMAGE_DOS_SIGNATURE: WORD = 0x5a4d;
pub(crate) const IMAGE_NT_SIGNATURE: DWORD = 0x00004550; pub(crate) const IMAGE_NT_SIGNATURE: DWORD = 0x00004550;
pub(crate) const IMAGE_FILE_EXECUTABLE_IMAGE: WORD = 0x0002; // File is executable (i.e. no unresolved external references). pub(crate) const IMAGE_FILE_EXECUTABLE_IMAGE: WORD = 0x0002; // File is executable (i.e. no unresolved external references).
#[allow(dead_code)]
pub(crate) const IMAGE_FILE_LARGE_ADDRESS_AWARE: WORD = 0x0020; // App can handle >2gb addresses pub(crate) const IMAGE_FILE_LARGE_ADDRESS_AWARE: WORD = 0x0020; // App can handle >2gb addresses
#[allow(dead_code)]
pub(crate) const IMAGE_FILE_32BIT_MACHINE: WORD = 0x0100; // 32 bit word machine. pub(crate) const IMAGE_FILE_32BIT_MACHINE: WORD = 0x0100; // 32 bit word machine.
pub(crate) const IMAGE_FILE_DLL: WORD = 0x2000; // File is a DLL. pub(crate) const IMAGE_FILE_DLL: WORD = 0x2000; // File is a DLL.
@ -39,10 +41,19 @@ pub(crate) const IMAGE_NUMBEROF_DIRECTORY_ENTRIES: usize = 16;
pub const IMAGE_DIRECTORY_ENTRY_EXPORT: usize = 0; // Export Directory pub const IMAGE_DIRECTORY_ENTRY_EXPORT: usize = 0; // Export Directory
pub const IMAGE_DIRECTORY_ENTRY_IMPORT: usize = 1; // Import Directory pub const IMAGE_DIRECTORY_ENTRY_IMPORT: usize = 1; // Import Directory
pub const IMAGE_DIRECTORY_ENTRY_RESOURCE: usize = 2; // Resource Directory
pub const IMAGE_DIRECTORY_ENTRY_EXCEPTION: usize = 3; // Exception Directory
pub const IMAGE_DIRECTORY_ENTRY_SECURITY: usize = 4; // Security Directory
pub const IMAGE_DIRECTORY_ENTRY_BASERELOC: usize = 5; // Base Relocation Table pub const IMAGE_DIRECTORY_ENTRY_BASERELOC: usize = 5; // Base Relocation Table
pub const IMAGE_DIRECTORY_ENTRY_TLS: usize = 9; pub const IMAGE_DIRECTORY_ENTRY_DEBUG: usize = 6; // Debug Directory
pub const IMAGE_DIRECTORY_ENTRY_ARCHITECTURE: usize = 7; // Architecture Specific Data
pub const IMAGE_DIRECTORY_ENTRY_GLOBALPTR: usize = 8; // RVA of GP
pub const IMAGE_DIRECTORY_ENTRY_TLS: usize = 9; // TLS Directory
pub const IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG: usize = 10; // Load Configuration Directory
pub const IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT: usize = 11; // Bound Import Directory in headers
pub const IMAGE_DIRECTORY_ENTRY_IAT: usize = 12; // Import Address Table pub const IMAGE_DIRECTORY_ENTRY_IAT: usize = 12; // Import Address Table
pub const IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: usize = 13; // Delay Load Import Descriptors pub const IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: usize = 13; // Delay Load Import Descriptors
pub const IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR: usize = 14; // COM Runtime descriptor
#[cfg(all(target_arch = "x86_64", target_os = "windows"))] #[cfg(all(target_arch = "x86_64", target_os = "windows"))]
pub(crate) const IMAGE_REL_BASED_DIR64: WORD = 10; pub(crate) const IMAGE_REL_BASED_DIR64: WORD = 10;
@ -255,7 +266,7 @@ struct_wrapper!(
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} }
*/ */
DUMMYUNIONNAME: DWORD, OriginalFirstThunk: DWORD,
TimeDateStamp: DWORD, TimeDateStamp: DWORD,
ForwarderChain: DWORD, // -1 if no forwarders ForwarderChain: DWORD, // -1 if no forwarders
Name: DWORD, Name: DWORD,

@ -1,10 +1,11 @@
mod check; mod check;
pub mod def;
pub mod error;
mod header; mod header;
mod pe; mod pe;
mod section; mod section;
pub mod def;
pub mod error;
pub use header::*; pub use header::*;
pub use pe::*; pub use pe::*;
pub use section::*; pub use section::*;

@ -43,4 +43,10 @@ impl<'a> PE<'a> {
ImageNtHeaders::x64(h) => h.FileHeader.Characteristics & IMAGE_FILE_DLL != 0, ImageNtHeaders::x64(h) => h.FileHeader.Characteristics & IMAGE_FILE_DLL != 0,
} }
} }
pub fn is_dot_net(&self) -> bool {
let dot_net_desc =
&self.pe_header.nt_header.get_data_directory()[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR];
dot_net_desc.Size != 0 && dot_net_desc.VirtualAddress != 0
}
} }

@ -35,28 +35,28 @@ pub const PAGE_EXECUTE_READ: DWORD = 0x20;
pub const PAGE_EXECUTE_READWRITE: DWORD = 0x40; pub const PAGE_EXECUTE_READWRITE: DWORD = 0x40;
*/ */
impl IMAGE_SECTION_HEADER { impl IMAGE_SECTION_HEADER {
#[inline]
pub fn can_read(&self) -> bool { pub fn can_read(&self) -> bool {
self.Characteristics & IMAGE_SCN_MEM_READ == IMAGE_SCN_MEM_READ self.Characteristics & IMAGE_SCN_MEM_READ != 0
} }
#[inline]
pub fn can_write(&self) -> bool { pub fn can_write(&self) -> bool {
self.Characteristics & IMAGE_SCN_MEM_WRITE == IMAGE_SCN_MEM_WRITE self.Characteristics & IMAGE_SCN_MEM_WRITE != 0
} }
#[inline]
pub fn can_exec(&self) -> bool { pub fn can_exec(&self) -> bool {
self.Characteristics & IMAGE_SCN_MEM_EXECUTE == IMAGE_SCN_MEM_EXECUTE self.Characteristics & IMAGE_SCN_MEM_EXECUTE != 0
} }
pub fn protect_value(&self) -> DWORD { pub fn get_protection(&self) -> DWORD {
match self.can_exec() { match self.can_exec() {
true => match self.can_read() { true => match self.can_read() {
true => { true => match self.can_write() {
if self.can_write() { true => PAGE_EXECUTE_READWRITE,
PAGE_EXECUTE_READWRITE false => PAGE_EXECUTE_READ,
} else { },
PAGE_EXECUTE_READ
}
}
false => PAGE_EXECUTE, false => PAGE_EXECUTE,
}, },
false => match self.can_read() { false => match self.can_read() {