mmap_inject 0.1.0

Manual-map DLL injection for Windows x64 — inject DLLs without LoadLibrary
Documentation
  • Coverage
  • 70%
    7 out of 10 items documented0 out of 2 items with examples
  • Size
  • Source code size: 51.69 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 656.3 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 4s Average build duration of successful builds.
  • all releases: 4s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • jeanzuck/mmap_inject
    3 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • jeanzuck

mmap_inject

Rust License: MIT crates.io docs.rs

A minimal, robust manual-map DLL injection library for Windows x64, written in Rust.

Features

  • Manual Mapping — injects DLLs without LoadLibrary, evading basic user-mode hooks
  • Randomized Base Address — ASLR-style allocation in 64-bit user space (3 retries + OS fallback)
  • SEH Support — calls RtlAddFunctionTable for x64 structured exception handling
  • PE Header Wiping — overwrites PE headers immediately after injection
  • Import Resolution — resolves normal import tables (IAT) and delayed imports
  • TLS Callbacks — executes DLL_PROCESS_ATTACH TLS callbacks
  • Base Relocations — applies IMAGE_REL_BASED_DIR64 relocations when the DLL isn't loaded at its preferred base
  • Detailed error diagnostics — unique error codes identify exactly which step failed
  • Zero-cost FFI — single dependency on windows-sys, no heavy runtime

Quick Start

# Cargo.toml
[dependencies]
mmap_inject = "0.1.0"
use mmap_inject::inject_dll;
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_ALL_ACCESS};

let dll_bytes = std::fs::read("my_dll.dll")?;
let h_proc = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, target_pid) };

unsafe {
    match inject_dll(h_proc, &dll_bytes) {
        Ok(()) => println!("Injected!"),
        Err(e) => eprintln!("Injection failed: {e}"),
    }
}

Architecture

┌──────────────────────────────────────────────────────┐
│                   Injector Process                    │
│                                                      │
│  injector.rs                                         │
│  ├─ validate PE                                      │
│  ├─ VirtualAllocEx (random base)                     │
│  ├─ write headers + sections                         │
│  ├─ apply base relocations (injector-side)           │
│  ├─ write MappingCtx + shellcode                     │
│  └─ CreateRemoteThread → WaitForSingleObject         │
│                                                      │
│                ▼                                     │
│  ┌──────────────────────────────────────┐            │
│  │         Target Process               │            │
│  │                                      │            │
│  │  shellcode.rs (minimal, ~2 KB)       │            │
│  │  ├─ resolve imports (IAT)            │            │
│  │  ├─ resolve delayed imports          │            │
│  │  ├─ execute TLS callbacks            │            │
│  │  ├─ RtlAddFunctionTable (SEH)        │            │
│  │  └─ DllMain(DLL_PROCESS_ATTACH)      │            │
│  └──────────────────────────────────────┘            │
└──────────────────────────────────────────────────────┘

API

inject_dll

pub unsafe fn inject_dll(process: HANDLE, dll_bytes: &[u8]) -> Result<()>

Manually maps the raw DLL bytes into the target process. PE headers are wiped immediately; shellcode and context memory are freed. The DLL itself stays loaded in the target until the process exits.

The process handle needs at minimum:

  • PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION
  • PROCESS_CREATE_THREAD

Error

Variant Meaning
InvalidPe Not a valid x64 PE file
Win32(u32) A Win32 API returned an error (GetLastError)
InvalidArgument Null handle, empty payload, etc.
ShellcodeFailed(usize) Import resolution failed; code indicates which step
SehRegistrationFailed DLL loaded but RtlAddFunctionTable failed
ShellcodeCrashed(u32, usize) Remote thread crashed; code = NTSTATUS, step = last marker

Building

Requires Rust 1.85+ (edition 2024). Release mode is required — debug builds are not supported because the compiler emits function calls (stack probes, non-inlined helpers) that fall outside the shellcode section.

cargo build --release

Static CRT

The workspace .cargo/config.toml enables +crt-static (equivalent to MSVC /MT) so injected DLLs don't depend on VCRUNTIME140.dll. It also enables /DYNAMICBASE for full .reloc section generation.

Notes for injected DLLs

  • Rust heap allocation (format!, Box, String, Vec) works fine in manually-mapped DLLs — the default Windows allocator wraps HeapAlloc and needs no CRT initialization.
  • Statically link the CRT (+crt-static) so your DLL has no external CRT dependency.
  • Enable /DYNAMICBASE so the DLL has a .reloc section (the injector applies relocations when loading at a different base).

Testing

# Unit tests — PE structs, RVA conversion, shellcode layout
cargo test -p mmap_inject

# Self-injection test — shows a MessageBox
cargo run -p test_injector

# Inject into a specific process by PID
cargo run -p test_injector -- 12345

Workspace

Crate Description
mmap_inject The injection library
test_dll Example DLL — MessageBox("Hello from test_dll")
test_injector Injector — test_injector (self) or test_injector <PID>

License

MIT — see LICENSE.