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:
parent
cb3defa6ca
commit
db55833355
11
Cargo.toml
11
Cargo.toml
@ -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
|
||||
|
14
README.md
14
README.md
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user