NeuroAgent

DLL Injection in .NET: Framework DLL Replacement Guide

Explore the feasibility of DLL injection in .NET through framework DLL replacement. Understand assembly loading order, security mechanisms, and more reliable interception methods.

Question

Is it possible to perform DLL injection in a .NET application by placing a modified version of a framework DLL (such as System.Diagnostics.Process.dll) in the working directory to intercept API calls? Specifically, if a .NET executable with its dependencies (dll, deps.json, and runtimeconfig.json) launches a process named ‘alpha.exe’, can a modified System.Diagnostics.Process.dll in the working directory redirect this to ‘beta.exe’ instead?

NeuroAgent

Yes, DLL injection in a .NET application by placing a modified framework DLL in the working directory is technically possible but faces significant challenges due to .NET’s security mechanisms and assembly loading order. The .NET runtime typically loads framework assemblies from the GAC (Global Assembly Cache) or framework directories before checking the working directory, making this approach unreliable for intercepting core framework APIs like System.Diagnostics.Process.

Understanding DLL Injection in .NET

DLL injection is a technique used for running code within the address space of another process by forcing it to load a dynamic-link library [source]. In the context of .NET applications, this becomes more complex due to the managed runtime environment.

For .NET processes, there are two main types of DLL injection approaches:

  1. Native DLL injection: Injecting a native (unmanaged) DLL that can then interact with the .NET runtime
  2. Managed DLL injection: Directly injecting a managed .NET assembly

The technique you’re describing falls into the second category - attempting to replace a framework DLL with a malicious version. According to research, this approach involves “simply injecting native DLL, next in DLLMain we get a handle for .NET framework, and finally we execute a piece of code” [source].

Assembly Loading Order in .NET

The .NET runtime follows a specific order when searching for assemblies, which is crucial for understanding why your proposed approach might not work as expected.

The default assembly loading behavior is to:

  1. Look in all system locations first (like the GAC)
  2. Check the current directory (working directory)
  3. Finally search any user-defined paths [source]

This means that for framework assemblies like System.Diagnostics.Process, the runtime will typically find the legitimate version in the .NET framework directory or GAC before it even considers looking in the application’s working directory.

Important Security Note: This behavior exists specifically to prevent DLL hijacking attacks. As Microsoft explains, “the phrase ‘Dll hijack’ is used to mean finding a way to have the victim application load your dll instead of the correct binary” [source].

Framework DLL Replacement Challenges

Replacing a core framework DLL like System.Diagnostics.Process.dll faces several significant technical challenges:

Strong Naming and Authenticode

Framework assemblies are strong-named and digitally signed by Microsoft. The .NET runtime performs integrity checks on these assemblies, making it difficult to simply replace them with malicious versions.

Loader Lock Issues

When attempting to inject managed code, you must be “very careful when executing code in DLLMain method because of loader lock” [source]. The loader lock is a critical synchronization primitive in the Windows loader, and holding it during DLL initialization can cause deadlocks.

Assembly Binding Redirects

Modern .NET applications use configuration files (app.config, web.config) that can contain assembly binding redirects. These redirects explicitly tell the runtime which version of an assembly to load, potentially bypassing any DLL in the working directory.

Dependencies and Dependencies

Framework DLLs have complex dependencies. For example, System.Diagnostics.Process.dll depends on other core assemblies. A replacement DLL would need to correctly implement or proxy all these dependencies.

Practical Implementation Considerations

If you were attempting to implement this technique, here are the practical considerations:

Current Working Directory Control

The working directory is typically set to the location of the executable or can be changed programmatically. An attacker would need to ensure their malicious DLL is placed in the correct directory before the target application runs.

Process Creation Interception

To intercept Process.Start() calls, your malicious DLL would need to:

  1. Hook the Process.Start method
  2. Detect when ‘alpha.exe’ is being launched
  3. Redirect the call to launch ‘beta.exe’ instead
  4. Possibly proxy the original functionality

Native vs Managed Approach

Some research suggests that “LoadLibrary etc. only loads native libraries” [source], which means you might need to create a native DLL that loads the .NET framework and then executes your managed code.

csharp
// Example of what the malicious Process.dll might contain
public static class Process
{
    public static Process Start(ProcessStartInfo startInfo)
    {
        if (startInfo.FileName == "alpha.exe")
        {
            startInfo.FileName = "beta.exe";
        }
        return System.Diagnostics.Process.Start(startInfo);
    }
}

Security Implications

This type of attack falls under several security categories:

CWE-427: Uncontrolled Search Path Element

The vulnerability you’re describing aligns with MITRE’s CWE-427: “It’s bad to load assemblies from directories that an attacker can control” [source]. This is a well-known security weakness that Microsoft has addressed in recent .NET versions.

DLL Preloading Attacks

Microsoft specifically provides guidance on “secure loading of libraries to prevent DLL preloading attacks” [source]. Modern .NET versions have improved security to make these attacks more difficult.

Mitigation Strategies

Applications can prevent this type of attack by:

  • Using explicit assembly loading with full paths
  • Implementing AppDomain isolation
  • Using strong assembly binding redirects
  • Following Microsoft’s best practices for assembly loading [source]

Alternative Approaches

If your goal is to intercept process creation in a .NET application, there are more reliable approaches:

Hooking Native APIs

Instead of replacing managed assemblies, you could hook native Windows APIs like CreateProcess or ShellExecute that are used by Process.Start().

Using Profiling APIs

.NET provides profiling APIs that allow you to monitor and modify method calls at runtime.

ETW (Event Tracing for Windows)

ETW can be used to monitor process creation events and potentially intercept or modify them.

Environmental Variables

You could modify PATH or other environment variables to control which executable gets found.


Sources

  1. DLL injection - Wikipedia
  2. DLL Injection Part 4 — Injecting managed DLL – Random IT Utensils
  3. DLL injection - Microsoft Q&A
  4. .NET - In what order are locations searched to load referenced DLLs? - Stack Overflow
  5. Prevent .Net Framework from Loading Assemblies from Current Working Directory - Stack Overflow
  6. Secure loading of libraries to prevent DLL preloading attacks - Microsoft Support
  7. Dll Injection in C# not working - Stack Overflow
  8. DLL Hijacking - HackTricks

Conclusion

While technically possible to attempt DLL injection in .NET by placing a modified framework DLL in the working directory, this approach faces significant challenges due to .NET’s security mechanisms and assembly loading order. The .NET runtime is designed to prioritize framework assemblies over working directory DLLs, making reliable interception of core APIs like System.Diagnostics.Process difficult.

For practical purposes, more reliable alternative approaches exist for intercepting process creation, including native API hooking, .NET profiling APIs, and ETW monitoring. Modern .NET versions have implemented additional security measures to prevent DLL preloading attacks, making this technique increasingly unreliable.

If you’re exploring this for security research or defensive purposes, understanding these limitations helps in developing better protection mechanisms. For offensive purposes, consider exploring alternative approaches that don’t rely on replacing core framework assemblies.