GAC Hijacking
Detection stack
- AIDR
- Alert
- ETL
- Query
Summary
The article explains how threat actors with local administrator access can tamper with .NET assemblies in the Global Assembly Cache (GAC) to run code from a trusted execution path. By swapping out DLLs like MIGUIControls.dll, an attacker can establish persistence and trigger malicious payloads under legitimate processes. The method depends on elevated rights to write into GAC locations and may include removing native images so the runtime is forced to load the altered assembly.
Investigation
A proof-of-concept utility (GAC-POC.exe) demonstrates the workflow by copying a legitimate DLL, patching it with Mono.Cecil to display a message box, and writing the modified version back into the GAC. Observable actions include the GAC write operation, deletion of native images using ngen.exe, and the follow-on process creation activity that occurs when the updated assembly is loaded.
Mitigation
Enable detailed process creation auditing (Event ID 4688) and file system auditing (Event ID 4663) for GAC directories. Alert on suspicious use of ngen.exe, execution of mscorsvw.exe, and unexpected DLL write activity in GAC paths. Reduce exposure by limiting local administrator access and enforcing code-signing validation or integrity checks for assemblies stored in the GAC.
Response
If GAC-POC.exe execution, unexpected assembly modifications, or native image removals are detected, isolate the host, preserve forensic artifacts, and restore GAC assemblies from a known-good baseline. Continue scoping to determine how the attacker obtained administrator privileges and whether additional persistence was established.
"graph TB %% Class Definitions Section classDef technique fill:#ffcc99 classDef process fill:#c2f0c2 classDef malware fill:#ffeb99 %% Node definitions tech_valid_accounts["<b>Technique</b> – <b>T1078.003 Valid Accounts: Local Accounts</b><br/><b>Description</b>: Adversaries obtain and abuse credentials for local accounts, including administrators, to gain unauthorized access."] class tech_valid_accounts technique tech_hijack_exec["<b>Technique</b> – <b>T1574.014 Hijack Execution Flow: AppDomainManager</b><br/><b>Description</b>: Adversaries replace or modify the .NET AppDomainManager assembly in the Global Assembly Cache to hijack the execution flow of .NET applications."] class tech_hijack_exec technique tech_dll_injection["<b>Technique</b> – <b>T1055.001 Dynamic-link Library Injection</b><br/><b>Description</b>: Adversaries inject malicious DLLs into a trusted process to execute their code in the context of that process."] class tech_dll_injection technique process_task_sched["<b>Process</b> – Task Scheduler MMC snapu2011in<br/><b>Role</b>: Trusted system component used to schedule tasks."] class process_task_sched process malicious_dll["<b>Malware</b> – Malicious DLL (e.g., payload)<br/><b>Purpose</b>: Executed after injection to run malicious actions."] class malicious_dll malware %% Connections showing attack flow tech_valid_accounts –>|enables| tech_hijack_exec tech_hijack_exec –>|leads to| tech_dll_injection tech_dll_injection –>|uses| process_task_sched process_task_sched –>|loads| malicious_dll "
Attack Flow
Simulation Execution
Prerequisite: The Telemetry & Baseline Pre‑flight Check must have passed.
-
Attack Narrative & Commands:
The adversary has prepared a malicious assembly (
GAC‑PoC.exe) that, when placed in the Global Assembly Cache, will be loaded by any .NET process that resolves a matching strong name. To trigger immediate execution and ensure the detection rule sees the exact command line, the attacker runs the assembly directly and also invokesngen.exeto pre‑compile the malicious DLL, a typical step in GAC hijacking to improve stealth. The actions generate distinct 4688 events containing the watched strings.- Copy malicious assembly to a writable directory (e.g.,
C:Temp). - Execute the proof‑of‑concept binary to demonstrate the hijack.
- Run
ngen.exeagainst the malicious assembly to simulate a native‑image generation step often observed after GAC placement.
- Copy malicious assembly to a writable directory (e.g.,
-
Regression Test Script:
# ------------------------------------------------- # GAC‑Hijacking Detection Validation Script # ------------------------------------------------- # Prerequisite: ensure the test account has local admin rights # and that the Windows audit policies from the pre‑flight # step are enabled. # ------------------------------------------------- # 1. Deploy the malicious executable (simulated) $tempDir = "C:Temp" if (-not (Test-Path $tempDir)) { New-Item -Path $tempDir -ItemType Directory | Out-Null } $gacPoCPath = Join-Path $tempDir "GAC-PoC.exe" # Simulate the payload – create a tiny executable that writes to a log. # Here we use a placeholder binary; in a real test you would copy the real file. Write-Output "Fake payload placeholder" | Out-File "$gacPoCPath.txt" # For demonstration, we just call cmd /c echo (the file name still appears in CommandLine) & cmd.exe /c "echo Running GAC-PoC simulation > NUL" # 2. Direct execution of GAC-PoC.exe Write-Host "[*] Executing GAC-PoC.exe" Start-Process -FilePath $gacPoCPath -WindowStyle Hidden -PassThru | Out-Null # 3. Invoke ngen.exe against the same binary $ngenPath = "$env:WINDIRMicrosoft.NETFramework64v4.0.30319ngen.exe" if (Test-Path $ngenPath) { Write-Host "[*] Running ngen.exe on GAC-PoC.exe" & $ngenPath install $gacPoCPath } else { Write-Warning "ngen.exe not found on this host – skipping step." } # 4. Log completion for the analyst Write-Host "[+] Simulation completed. Review SIEM for EventID 4688 alerts." -
Cleanup Commands:
# ------------------------------------------------- # Cleanup after GAC‑Hijacking validation # ------------------------------------------------- $tempDir = "C:Temp" $gacPoCPath = Join-Path $tempDir "GAC-PoC.exe" if (Test-Path $gacPoCPath) { Remove-Item -Path $gacPoCPath -Force Write-Host "[*] Removed GAC-PoC.exe" } # Remove any generated native images (if any were created) $ngenPath = "$env:WINDIRMicrosoft.NETFramework64v4.0.30319ngen.exe" if (Test-Path $ngenPath) { & $ngenPath uninstall $gacPoCPath 2>$null Write-Host "[*] Uninstalled native image (if it existed)." } # Optionally delete the temporary folder if empty if ((Get-ChildItem $tempDir).Count -eq 0) { Remove-Item -Path $tempDir -Force Write-Host "[*] Removed temporary directory $tempDir" } Write-Host "[+] Cleanup complete."