
Les produits de sécurité modernes (CrowdStrike, Bitdefender, SentinelOne, etc.) accrochent la fonction nLoadImage à l'intérieur de clr.dll pour intercepter et analyser les chargements d'assembly .NET en mémoire. Cet outil désaccroche cette fonction.
Un utilitaire C++ natif qui contourne les hooks EDR/AV dans le Common Language Runtime .NET en restaurant l'implémentation originale de la fonction nLoadImage.
Cet outil supprime les hooks des produits de sécurité de la fonction nLoadImage du CLR - le point d'entrée natif critique qui gère tout le chargement d'assembly .NET en mémoire. En lisant le clr.dll propre depuis le disque et en écrasant les octets de la fonction hookée en mémoire, il restaure le comportement original du CLR, permettant à Assembly.Load(byte[]) de s'exécuter sans inspection ni analyse EDR.
Les produits de sécurité modernes (BitDefender, CrowdStrike, SentinelOne, etc.) hookent la fonction nLoadImage dans clr.dll pour intercepter et analyser les chargements d'assembly .NET en mémoire. Cet outil déshooke cette fonction en :
clr.dll propre depuis le disquenLoadImageAprès le déshooking, Assembly.Load(byte[]) s'exécute sans inspection EDR.
nLoadImage est la fonction native critique qui gère tout le chargement d'assembly en mémoire dans le runtime .NET. Elle est déclarée comme un InternalCall dans le code managé, ce qui signifie qu'elle n'a pas d'implémentation C# - c'est plutôt un pont direct vers le code natif du CLR.
La chaîne d'appel :
Code managé (C#)
↓
Assembly.Load(byte[])
↓
RuntimeAssembly.nLoadImage(...) [InternalCall - pas de corps managé]
↓
clr.dll!AssemblyNative::LoadImage (Implémentation C++ native)
↓
Assembly chargé dans AppDomain
Pourquoi c'est critique :
Presque tous les chargements d'assembly en mémoire passent par nLoadImage. La méthode Assembly.Load(byte[]) et ses surcharges (y compris le chargement avec des octets de symboles) invoquent nLoadImage en interne. Lorsque vous appelez Assembly.Load(byte[]), le code managé dans mscorlib.dll passe votre tableau d'octets via RuntimeAssembly.nLoadImage(), qui est marqué avec [MethodImpl(MethodImplOptions.InternalCall)] - ce qui signifie que son corps est vide en C# et que l'exécution saute immédiatement vers le code natif du CLR.
Même les scénarios de génération de code dynamique - frameworks de sérialisation qui émettent des assemblies à l'exécution, génération de sérialiseurs XML, et outils de red team comme execute-assembly de Cobalt Strike - passent tous par cette seule fonction.
Implémentation native :
Le stub InternalCall de nLoadImage dans mscorlib.dll pointe vers la fonction C++ native AssemblyNative::LoadImage dans clr.dll. Cette fonction :
Dans .NET Framework 4.8+, chaque appel nLoadImage passe automatiquement les octets de l'assembly à l'AMSI de Windows Defender (AmsiScanBuffer) pour analyse avant exécution, ce qui en fait un point d'étranglement critique pour les produits de sécurité.
Signature de la fonction (.NET Framework 4.7+) :
[MethodImpl(MethodImplOptions.InternalCall)]
static internal extern Assembly nLoadImage(
byte[] rawAssembly, // PE bytes
byte[] rawSymbolStore, // Optional PDB bytes
Evidence evidence, // CAS evidence (obsolete)
ref StackCrawlMark stackMark, // Security stack marker
bool fIntrospection, // Reflection-only flag
bool fSkipIntegrityCheck, // Skip integrity validation
SecurityContextSource securityContextSource // Security context
);
Lorsque vous appelez Assembly.Load(byte[]), il invoque nLoadImage avec ces paramètres typiques :
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
return RuntimeAssembly.nLoadImage(
rawAssembly, // Your byte array
null, // rawSymbolStore
null, // evidence
ref stackMark, // LookForMyCaller
false, // fIntrospection
SecurityContextSource.CurrentAssembly // securityContextSource
);
Le paramètre fIntrospection contrôle si l'assembly est chargé pour exécution (false) ou pour inspection en lecture seule (true). La méthode Assembly.ReflectionOnlyLoad(byte[]) appelle nLoadImage avec fIntrospection=true, permettant l'examen des métadonnées sans exécution de code.
Pourquoi les EDR le hookent :
Étant donné que nLoadImage est le point d'entrée unique pour tous les chargements d'assembly en mémoire, les produits EDR le hookent au niveau natif dans clr.dll. Cela leur permet de :
Les contournements traditionnels (patching AMSI, désactivation ETW) n'affectent pas les hooks au niveau du CLR car ils opèrent à un niveau supérieur dans la pile. Le hook se produit à l'intérieur même du CLR, avant même que l'AMSI ne soit invoquée.
CLRUnhook.exe
Déshooke le CLR dans le processus actuel. Remarque : Cela fonctionne uniquement si le CLR est déjà chargé (c'est-à-dire exécuté depuis une application .NET ou après avoir chargé le CLR manuellement).
CLRUnhook.exe powershell.exe
CLRUnhook.exe 1234
Déshooke le CLR dans un processus distant.
=== CLR Unhooking Tool ===
[*] Mode -> Remote Process Unhooking
[*] Target -> PID 21436
[+] Found PID -> 21436
[*] Unhooking CLR->nLoadImage in remote process...
[DEBUG] Remote mode enabled
[DEBUG] Found clr.dll at 0x00007FFD38CB0000
[DEBUG] CLR path -> C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
[DEBUG] CLR module size -> 10108928 bytes
[DEBUG] Read 10108928 bytes from remote process
[DEBUG] Searching for 'nLoadImage' in module (size: 10108928)
[DEBUG] Remote base address: 0x00007FFD38CB0000
[DEBUG] Scanning for string 'nLoadImage' (11 bytes)...
[DEBUG] Found string at RVA 0x7c12b8
[DEBUG] Searching for remote pointer: 0x7ffd394712b8
[DEBUG] Found pointer at offset 0x7a4340
[DEBUG] Valid function pointer found at RVA 0x5e4f30
[DEBUG] Found nLoadImage at RVA 0x00000000005E4F30
[DEBUG] Hooked function address -> 0x00007FFD39294F30
[DEBUG] Clean function at offset 0x00000000005E4F30 in disk file
[DEBUG] Reading hooked bytes before patch...
[DEBUG] First 16 bytes BEFORE unhook:
4C 8B DC 49 89 5B 08 49 89 73 10 4D 89 4B 20 57
[DEBUG] Clean bytes from disk:
8B 4B 78 E8 88 A9 EA FF C6 44 24 28 00 80 3D A4
[DEBUG] Wrote 30 bytes successfully
[DEBUG] First 16 bytes AFTER unhook:
8B 4B 78 E8 88 A9 EA FF C6 44 24 28 00 80 3D A4
[DEBUG] VERIFICATION SUCCESS: Patched bytes match clean bytes!
[+] SUCCESS -> CLR nLoadImage unhooked in remote process!
[+] EDR/AV hooks bypassed
[*] Press Enter to exit...