Reflective PE loader library in pure Rust (no_std). Loads native and .NET assemblies from memory.
- Native PE loading (C/C++/Rust/Go...)
- 64-bit (x86_64)
- 32-bit (x86)
- ARM/ARM64 relocation support
- Architecture validation (PE bitness must match host process)
- .NET assembly loading (C#/VB/CLR)
- 64-bit .NET execution via CLR hosting
- 32-bit .NET execution via CLR hosting
- Command-line argument passing to
Main(string[] args) - Automatic runtime version detection from PE metadata
- Parse DOS/NT headers (PE32 and PE32+ at runtime)
- Map sections into allocated memory
- Resolve imports (name and ordinal, PE32 4-byte / PE32+ 8-byte thunks)
- Apply base relocations (HIGHLOW, DIR64, ARM_MOV32, THUMB_MOV32)
- Resolve delayed imports (DataDirectory[13], modern RVA format)
- Register exception handlers (RtlAddFunctionTable, x64/ARM64 only)
- Set per-section memory protections
- Invoke TLS callbacks (DLL_PROCESS_ATTACH)
- Execute entry point via CreateThread
- Cleanup (unregister exception tables, free memory)
- Security Cookie Initialization (
__security_cookiefor /GS buffer security) - CFG (Control Flow Guard) - Registers valid call targets via
SetProcessValidCallTargets - SxS / Activation Context - Activates embedded manifests for GUI applications
- Bound Import Directory invalidation
Utility functions for resolving exports from a loaded PE image:
get_export_by_name(baseptr, ntheader, name)— resolve by nameget_export_by_ordinal(baseptr, ntheader, ordinal)— resolve by ordinal
Handles forwarded exports automatically by loading the target DLL (e.g., "NTDLL.RtlAllocateHeap" resolves to the actual function in ntdll.dll).
Automatically resolves Windows API sets (api-ms-win-*, ext-ms-win-*) to their actual host DLLs using the PEB ApiSetMap. This enables loading PEs that import from virtual API set DLLs.
utils::detect_platform(bytes)— Detect if PE is 32-bit or 64-bitutils::check_dotnet(pe)— Check if PE is a .NET assemblyutils::get_dotnet_version(pe)— Extract .NET runtime version string from metadatautils::rva_to_file_offset(pe, rva)— Convert RVA to raw file offset
| Architecture | Machine Code | Bitness |
|---|---|---|
| i386 | 0x014c | 32-bit |
| AMD64 | 0x8664 | 64-bit |
| IA64 | 0x0200 | 64-bit |
| ARM (ARMNT) | 0x01C4 | 32-bit |
| ARM64 | 0xAA64 | 64-bit |
The library uses a PeError enum for error handling:
pub enum PeError {
InvalidPe,
FileTooSmall,
ArchMismatch, // PE bitness doesn't match host process
AllocFailed,
InvalidNtHeader,
ImportResolveFailed,
SectionOutOfBounds,
UnsupportedRelocationType,
ThreadCreationFailed,
VirtualProtectFailed,
DotNetError(&'static str),
}[dependencies]
rust-pe = { version = "0.1", default-features = true }
# Or disable .NET support to reduce binary size:
rust-pe = { version = "0.1", default-features = false }| Feature | Default | Description |
|---|---|---|
dotnet |
Yes | Enable .NET assembly execution via CLR hosting |
use rust_pe::reflective_loader;
fn main() {
let data = include_bytes!("app.exe");
// Native PE or .NET assembly — detected automatically
unsafe {
reflective_loader(data).expect("failed to load PE");
}
}use rust_pe::reflective_loader_with_args;
fn main() {
let data = include_bytes!("app.exe");
// Arguments are passed to native PE via PEB CommandLine patching
// or to .NET Main(string[] args) via CLR invocation
unsafe {
reflective_loader_with_args(data, &["arg1", "arg2"]).expect("failed");
}
}use rust_pe::reflective_loader;
use std::fs;
fn main() {
let data = fs::read("path/to/executable.exe").expect("failed to read file");
unsafe {
reflective_loader(&data).expect("failed to load PE");
}
}use rust_pe::reflective_loader_with_args;
fn main() {
let data = include_bytes!("app.exe");
let args: Vec<String> = std::env::args().skip(1).collect();
let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
unsafe {
reflective_loader_with_args(data, &arg_refs).expect("failed");
}
}- Native PEs: Arguments are passed by patching the PEB CommandLine, so
GetCommandLineW()returns the provided arguments - .NET Assemblies: Arguments are passed directly to
Main(string[] args)via CLR invocation
This library is no_std compatible (requires alloc). It uses no standard library dependencies, making it suitable for constrained environments or when minimal binary size is important.
Special thanks to the following individuals and projects for their contributions to this project:
- memN0ps for providing useful winapi rust code for learning
- trickster0 for providing many OffensiveRust code for learning
This project is licensed under the MIT License - see the LICENSE file for details.