lazy_importer 0.1.0

Rust port of Justas Masiulis's lazy_importer for Windows module and export resolution.
docs.rs failed to build lazy_importer-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: lazy_importer-0.1.2

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, or get_in.
  • Cache global module and function resolution per macro call site by default.
  • Follow forwarded exports by default in address, get, address_in, and get_in.
  • Resolve API-set forwarded exports such as api-ms-win-* and ext-ms-* contracts through the process API set map.
  • Expose raw_address and raw_address_in for lookups that do not follow forwarded exports.
  • Fold ASCII case for module and export hashing with the case-insensitive Cargo feature.
  • Generate compile-time-random hash offsets with const-random; set CONST_RANDOM_SEED when 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.