6
0
mirror of https://github.com/EddieIvan01/memexec synced 2024-06-16 12:08:03 +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]
name = "memexec"
version = "0.1.1"
version = "0.1.2"
authors = ["iv4n <iv4n.cc@qq.com>"]
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"
license = "GPL-3.0"
# 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)
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
+ Applicable to EXE and DLL
+ Cross-architecture, applicable to x86 and x86_64
+ Applicable to EXE and DLL (except .NET assembly)
+ Cross-architecture, applicable to x86 and x86-64
+ Zero-dependency
+ Contains a zero-copy submodule of PE parser
+ Contains a simple, zero-copy PE parser submodule
# Install
@ -76,6 +76,12 @@ let pe = PE::new(&buf);
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
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_snake_case)]
#![allow(overflowing_literals)]
@ -17,7 +14,7 @@ use std::os::raw::c_void;
pub unsafe fn memexec_exe(bs: &[u8]) -> Result<()> {
let pe = PE::new(bs)?;
let loader = ExeLoader::new(&pe).unwrap();
let loader = ExeLoader::new(&pe)?;
Ok(loader.invoke_entry_point())
}
@ -28,7 +25,7 @@ pub unsafe fn memexec_dll(
lp_reserved: *const c_void,
) -> Result<bool> {
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))
}

@ -9,6 +9,7 @@ pub enum Error {
MismatchedArch,
MismatchedLoader,
NoEntryPoint,
UnsupportedDotNetExecutable,
}
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
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 {
let mut reloc_table_ptr =
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 {
let patch_addr = base_addr
.offset(reloc_block.VirtualAddress as isize + (item & 0xfff) as isize)
as *mut isize;
as *mut usize;
*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)
.to_str()?;
// TODO: implement loading module by calling self recursively
let hmod = winapi::load_library(dll_name)?;
// Whether HNT exists
let (mut iat_ptr, mut hnt_ptr) = if import_desc.DUMMYUNIONNAME != 0 {
// 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 {
(
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,
)
} else {
@ -103,32 +104,33 @@ unsafe fn load_pe_into_mem(pe: &PE) -> Result<*const c_void> {
};
loop {
let thunk_data = *hnt_ptr as isize;
let thunk_data = *ilt_ptr as isize;
if thunk_data == 0 {
break;
}
let proc_addr;
if thunk_data & IMAGE_ORDINAL_FLAG == IMAGE_ORDINAL_FLAG {
if thunk_data & IMAGE_ORDINAL_FLAG != 0 {
// Import by ordinal number
proc_addr = winapi::get_proc_address_by_ordinal(hmod, thunk_data & 0xffff)?;
} 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),
);
if 0 == import_by_name.Name {
if 0 == hint_name_table.Name {
break;
}
proc_addr = winapi::get_proc_address(
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 = 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(
&(base_addr.offset(section.VirtualAddress as isize)) as _,
&size as _,
section.protect_value(),
section.get_protection(),
)?;
}
@ -189,16 +191,20 @@ impl ExeLoader {
pub unsafe fn new(pe: &PE) -> Result<ExeLoader> {
check_platform(pe)?;
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 {
let entry_point = pe.pe_header.nt_header.get_address_of_entry_point();
if entry_point == 0 {
Err(Error::NoEntryPoint)
} else {
Ok(ExeLoader {
entry_point_va: load_pe_into_mem(pe)?.offset(entry_point),
})
}
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> {
check_platform(pe)?;
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 {
let entry_point = pe.pe_header.nt_header.get_address_of_entry_point();
if entry_point == 0 {
Err(Error::NoEntryPoint)
} else {
Ok(DllLoader {
entry_point_va: load_pe_into_mem(pe)?.offset(entry_point),
})
}
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);
}
// 32-bit .NET assembly may not set IMAGE_FILE_32BIT_MACHINE
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);
}
@ -44,7 +45,7 @@ impl<'a> ImageNtHeaders<'a> {
}
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);
}

@ -12,7 +12,9 @@ pub(crate) const IMAGE_DOS_SIGNATURE: WORD = 0x5a4d;
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).
#[allow(dead_code)]
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_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_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_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_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"))]
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)
}
*/
DUMMYUNIONNAME: DWORD,
OriginalFirstThunk: DWORD,
TimeDateStamp: DWORD,
ForwarderChain: DWORD, // -1 if no forwarders
Name: DWORD,

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

@ -43,4 +43,10 @@ impl<'a> PE<'a> {
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;
*/
impl IMAGE_SECTION_HEADER {
#[inline]
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 {
self.Characteristics & IMAGE_SCN_MEM_WRITE == IMAGE_SCN_MEM_WRITE
self.Characteristics & IMAGE_SCN_MEM_WRITE != 0
}
#[inline]
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() {
true => match self.can_read() {
true => {
if self.can_write() {
PAGE_EXECUTE_READWRITE
} else {
PAGE_EXECUTE_READ
}
}
true => match self.can_write() {
true => PAGE_EXECUTE_READWRITE,
false => PAGE_EXECUTE_READ,
},
false => PAGE_EXECUTE,
},
false => match self.can_read() {