rustdllproxy 3.0.0

Crate to ease the development of proxy DLLs in Rust
rustdllproxy-3.0.0 is not a library.

rustdllproxy

Installation

cargo install rustdllproxy

Compatibility

This crate currently only supports the standard DLL PE format. .NET DLLs are not supported.

Use Cases

This crate serves two main purposes:

  1. Single DLL Proxying - Proxy a single DLL to modify or better understand its behavior
  2. DLL Consolidation - Collect several DLLs into one unified proxy, which can be used alongside custom applications or techniques

Current Limitations

  • Only supports exports from the standard PE DLL format (.NET DLLs are not compatible)
  • When hooking functions with custom code, the function signature must be known
    • This can be found using disassemblers and reverse engineering tools like Ghidra

Commands

Rustdllproxy ships two subcommands:

Command Purpose
rustdllproxy new Generate a new proxy cdylib crate from one or more existing DLLs.
rustdllproxy build Sync the .def file with src/lib.rs and build the crate.
rustdllproxy --help        # top-level help

rustdllproxy new --help    # generation flags

rustdllproxy build --help  # build flags

Creating a New Crate

rustdllproxy new -p path/to/target.dll -n my_proxy

Tip: Use -p multiple times to unify several DLLs into one proxy.

Tip: The -a flag can be used to optionally compile for 32 bit.

Important: Proxy Strategy

Before creating your crate, decide how the proxy DLL will interact with the original(s). A typical pattern is to append an underscore to the original DLL name.

⚠️ THIS MUST BE DONE BEFORE GENERATING THE CRATE - the generated .def file will reference this name for forwarding behavior.

Writing Hooks

The macro library supports 3 main hook types: prehook, posthook, and fullhook.

Hook Implementation Steps

  1. Replace the #[no_mangle] directive with the hook macro (leave the //<dllname>.dll trailing comment in place — rustdllproxy build uses it to recover the original DLL name):

    #[prehook("dllbeingproxied.dll", "function_name")] //dllbeingproxied.dll
    
  2. Fill out the function signature (declare inputs as mut to modify them)

  3. Build with rustdllproxy build.

Previously, step 3 required hand-editing the .def file (function_name = dll.function_name @Nfunction_name @N) and then forcing a Cargo rebuild because Cargo doesn't fingerprint .def changes. The build subcommand handles both.

Hook Types

In this section, target.dll is commonly used. Remember in most cases this would be target_.dll

prehook

Executes code before the original function. Allows you to add functionality or modify input variables.

#[prehook("target.dll", "my_function")] //target.dll
fn my_function(mut param1: i32, mut param2: &str) {
    // Your code here - executes before original function
    param1 *= 2;  // Modify parameters if needed
}

posthook

Executes code after the original function. View and edit the return value using the magic ret variable.

#[posthook("target.dll", "calculate")] //target.dll
fn calculate(input: i32) -> i32 {
    // Original function executes first
    // Then your code runs with access to 'ret'
    ret = ret * 2;  // Modify return value
}

Note: The ret variable is automatically defined as mutable. You don't need to reference it if not needed.

fullhook

Provides complete control over function execution. Manually manage the return value and function calling.

#[fullhook("target.dll", "do_multi_add")] //target.dll
fn do_multi_add(mut a: i32, mut b: i32, mut c: i32) -> i32 {
    // Pre-processing
    a += 10;
    b += 20;

    // Call original function with magic func()
    let mut return_value: i32 = func(a, b, c);

    // Post-processing
    return_value *= 2;

    // Must explicitly return the value
    return_value
}

Building the Crate

Run from the proxy crate directory (or pass it as the first argument):

rustdllproxy build [PATH] [--profile <name>] [--no-build] [-- <extra cargo args>]

Flag Default Effect
PATH . Path to the proxy crate root.
--profile <name> release Cargo build profile (release, dev, custom).
--no-build off Regenerate the .def file but skip cargo build.
-- <args> Forwarded verbatim to cargo build.

How It Works

  1. Reads [package].name from Cargo.toml to locate <name>.def.
  2. Walks src/lib.rs to classify every exported function as either hooked (#[prehook] / #[posthook] / #[fullhook]) or forwarded (#[no_mangle]).
  3. For each function, the original DLL name is recovered from, in order:
    • the first string literal of the hook macro (hooked functions only),
    • the trailing //<dllname>.dll comment on the attribute line,
    • the existing forwarding entry already in the .def file.
  4. Rewrites <name>.def so hooked functions read name @N and forwarded functions read name = origdll.name @N. Existing ordinals are preserved.
  5. Bumps src/lib.rs's mtime to force a relink (Cargo doesn't fingerprint .def changes), then invokes cargo build.

Caveats

  • The .def file is fully regenerated on every run — manual edits to it (extra directives, custom ordinals) will be overwritten.
  • If an #[no_mangle] function loses both its //<dllname>.dll comment and its forwarding entry in the .def file, the build aborts with an error explaining how to restore one of them.

Example Workflow

Let's say you want to modify office.dll used in office software via DLL search order hijacking:

Step 1: Prepare the Original DLL

# Rename the original DLL

mv office.dll office_.dll

Step 2: Generate Proxy Crate

rustdllproxy new -p office_.dll -n office_proxy

Step 3: Implement Hooks

Leave the //office_.dll trailing comment that the generator emitted — the build step reads it.

#[prehook("office_.dll", "open_window")] //office_.dll
fn open_window() {
    // Your custom code here...
    println!("Window is about to open!");
}

Step 4: Build and Deploy

cd office_proxy

rustdllproxy build

# Rename the built DLL back to office.dll

# Place in the target directory

DLL Bundling Considerations

Important: Proxying several DLLs together is typically useful for reverse engineering and custom software development, not process modification.

When bundling multiple DLLs:

  • Function ordinals may change due to export ordering
  • This is rarely problematic since modern software uses export names for compatibility
  • Primarily useful for analysis and custom application development

Changelog

Release notes live in CHANGELOG.md.

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.