Expand description
§lazy_importer
lazy_importer is a no_std Rust port of Justas Masiulis’s original C++
lazy_importer. It resolves already-loaded Windows modules and exports by
hashing names and walking the process PEB directly, keeping Windows internals in
small unsafe modules and exposing a typed API at the edge.
§Features
- Resolve loaded modules by hash with
li_module!. - Resolve exports by hash across loaded modules with
li_fn!. - Resolve exports inside a known module with
address_in,raw_address_in, orget_in. - Cache global module and function resolution per macro call site by default.
- Follow forwarded exports by default in
address,get,address_in, andget_in. - Resolve API-set forwarded exports such as
api-ms-win-*andext-ms-*contracts through the process API set map. - Expose
raw_addressandraw_address_infor lookups that do not follow forwarded exports. - Fold ASCII case for module and export hashing with the
case-insensitiveCargo feature. - Generate compile-time-random hash offsets with
const-random; setCONST_RANDOM_SEEDwhen deterministic build output is required. - Fail at compile time on non-Windows targets.
Resolution is implemented for Windows x86, x86_64, and aarch64.
§Basic Usage
type GetCurrentProcessId = unsafe extern "system" fn() -> u32;
let get_current_process_id = unsafe {
lazy_importer::li_fn!(GetCurrentProcessId)
.get::<GetCurrentProcessId>()
.expect("GetCurrentProcessId should be loaded")
};
let pid = unsafe { get_current_process_id() };li_fn! accepts either an identifier or a string literal:
type GetCurrentProcessId = unsafe extern "system" fn() -> u32;
let function = unsafe {
lazy_importer::li_fn!("GetCurrentProcessId")
.get::<GetCurrentProcessId>()
.expect("GetCurrentProcessId should resolve")
};§Modules
Resolve a loaded module by name:
let kernel32 = lazy_importer::li_module!("KERNEL32.DLL")
.get()
.expect("kernel32 should be loaded");Each macro invocation has its own static cache. Calling get again from the
same li_module! call site reuses the cached module handle after the first
successful lookup. There is no separate cached method because global module
lookups are cached by default.
§Functions
Resolve a function across all loaded modules:
type GetCurrentProcessId = unsafe extern "system" fn() -> u32;
let function = lazy_importer::li_fn!(GetCurrentProcessId);
let address = function.address().expect("export should resolve");
let typed = unsafe {
function
.get::<GetCurrentProcessId>()
.expect("export should resolve")
};address and get follow forwarded exports by default and cache the resolved
address per macro call site. Use raw_address when you need the raw export
address without following forwarded exports. There is no separate cached
method because global function lookups are cached by default.
§Known Modules
Resolve inside a module handle you already have:
type GetCurrentProcessId = unsafe extern "system" fn() -> u32;
let kernel32 = lazy_importer::li_module!("KERNEL32.DLL")
.get()
.expect("kernel32 should be loaded");
let address = lazy_importer::li_fn!("GetCurrentProcessId")
.address_in(kernel32)
.expect("export should resolve in kernel32");
let function = unsafe {
lazy_importer::li_fn!("GetCurrentProcessId")
.get_in::<GetCurrentProcessId>(kernel32)
.expect("export should resolve in kernel32")
};address_in and get_in follow forwarded exports. Use raw_address_in for the
raw in-module export address. In-module lookups are not cached by the macro call
site cache.
§Case Sensitivity
By default, hashing is case-sensitive. Enable case-insensitive if you want
module and export hashing to fold ASCII case:
lazy_importer = { version = "0.1", features = ["case-insensitive"] }§Platform Behavior
On supported Windows targets, resolution walks the PEB loader list and parses PE export directories directly. Non-Windows targets fail at compile time.