AppDomainManager Injection: The .NET Hijack That Runs Your Code Inside Trusted Managed Binaries
How attackers abuse the CLR's AppDomainManager to hijack signed .NET applications for stealthy execution and persistence on Windows, and what to audit.
Most defenders spend their energy hunting native code injection — VirtualAllocEx, reflective loaders, process hollowing. But there is a quieter path that requires no shellcode, no memory patching, and no unsigned binary hitting disk. It abuses a legitimate feature of the .NET Common Language Runtime (CLR): the AppDomainManager. With three configuration knobs, an attacker can make a fully signed, Microsoft-shipped .NET executable load and run their managed assembly at startup — inside a process that your allowlist, your EDR, and your analyst all trust.
How AppDomainManager injection works
When any .NET Framework application starts, the CLR checks whether a custom AppDomainManager should manage the default application domain. It resolves this from three environment variables or from the application's config file:
APPDOMAIN_MANAGER_ASM— the strong-named assembly to loadAPPDOMAIN_MANAGER_TYPE— the class inside it that extendsAppDomainManagerCOMPLUS_Version— pins the runtime version so an old .NET Framework app is used
The equivalent config-file form lives in the app's *.exe.config:
<configuration>
<runtime>
<appDomainManagerAssembly value="Evil, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=abc123..." />
<appDomainManagerType value="Evil.Hijack" />
</runtime>
</configuration>
The attacker's class simply overrides InitializeNewDomain. That method runs before Main(), so the payload fires the instant the trusted host process launches:
public sealed class Hijack : AppDomainManager {
public override void InitializeNewDomain(AppDomainSetup info) {
// runs inside the signed host, before its Main()
Payload.Run();
}
}
Because the malicious code executes within a legitimate signed process (a common example is a small .NET utility that lives in a writable directory), the parent-child telemetry looks benign and the loaded module is a normal managed assembly. This maps to MITRE ATT&CK T1574.014 (Hijack Execution Flow: AppDomainManager).
Why it evades common controls
- Application allowlisting (WDAC/AppLocker) trusts the host EXE by publisher — and the host is genuinely signed. The injected assembly rides in under that trust unless you enforce managed-code integrity too.
- EDR process trees see the expected image path and signature. No
CreateRemoteThread, no suspicious API sequence. - Environment-variable persistence survives reboots when set at the user or machine scope, and a dropped
.exe.configbeside a target binary is trivially easy and rarely inventoried.
The hijack is not a bug — it is documented CLR behavior. That is exactly why it is durable: you cannot patch it away, you can only audit and constrain it.
What to hunt for
There is no single "off switch," so detection is about surfacing the three ingredients:
- Suspicious environment variables. Enumerate machine- and user-scope variables for
APPDOMAIN_MANAGER_ASM,APPDOMAIN_MANAGER_TYPE, andCOMPLUS_Version. These should essentially never appear on a normal endpoint. - Rogue
.exe.configfiles. Look for config files next to signed binaries that containappDomainManagerAssembly/appDomainManagerTypeelements, especially in user-writable paths like%APPDATA%,%TEMP%, orC:\ProgramData. - Unexpected managed modules. A signed .NET host loading an unsigned or oddly-located assembly from a temp directory is a strong signal. Sysmon Event ID 7 (Image Loaded) and CLR ETW provider events help here.
A quick manual sweep from PowerShell:
Get-ChildItem Env: | Where-Object {
$_.Name -match 'APPDOMAIN_MANAGER|COMPLUS_Version'
}
[Environment]::GetEnvironmentVariables('Machine').GetEnumerator() |
Where-Object { $_.Key -match 'APPDOMAIN_MANAGER' }
Hardening the surface
- Baseline and alert on any
APPDOMAIN_MANAGER_*/COMPLUS_*environment variables at machine and user scope — treat their presence as an incident, not a tuning problem. - Enforce WDAC in a mode that validates managed code, not just the PE host, so an untrusted assembly cannot load even inside a trusted process.
- Lock down directory permissions so standard users cannot drop an
.exe.configbeside privileged binaries, and monitor creation of*.exe.configin unusual locations. - Keep .NET-heavy admin tooling off user-writable paths, and prefer .NET (Core)/modern runtimes where the legacy AppDomainManager surface is reduced.
Where WinSentinel fits
AppDomainManager injection is precisely the kind of misconfiguration that hides between "the file is signed" and "the behavior is wrong." WinSentinel audits the endpoint's own configuration — environment variables, autostart and execution-flow hijack points, and file/permission hygiene — and flags the specific artifacts this technique depends on, so a planted config or a rogue variable shows up as a finding instead of living silently until an incident. Every WinSentinel install runs the full audit engine and all 64 modules locally on a single machine with no limits, so an individual admin can sweep their own workstation for this today at no cost.
For teams, WinSentinel Pro adds fleet orchestration: a central node rolls those same findings up across every managed machine, so if one endpoint suddenly grows an APPDOMAIN_MANAGER_ASM variable or a suspicious .exe.config, you see the drift across the fleet and can dispatch a remediation instead of discovering it one host at a time. Execution-flow hijacks are quiet by design — the win comes from auditing continuously and catching the configuration change the moment it lands.