
Los productos de seguridad modernos (CrowdStrike, Bitdefender, SentinelOne, etc.) enganchan la función nLoadImage dentro de clr.dll para interceptar y escanear cargas de ensamblados .NET en memoria. Esta herramienta desengancha esa función.
Una utilidad nativa en C++ que evita los ganchos (hooks) de EDR/AV en el Common Language Runtime de .NET restaurando la implementación original de la función nLoadImage.
Esta herramienta elimina los ganchos de productos de seguridad de la función nLoadImage del CLR, el punto de entrada nativo crítico que maneja toda la carga de ensamblados .NET en memoria. Al leer la clr.dll limpia desde el disco y sobrescribir los bytes de la función enganchada en memoria, restaura el comportamiento original del CLR, permitiendo que Assembly.Load(byte[]) se ejecute sin inspección ni escaneo del EDR.
Los productos de seguridad modernos (BitDefender, CrowdStrike, SentinelOne, etc.) enganchan la función nLoadImage dentro de clr.dll para interceptar y escanear las cargas de ensamblados .NET en memoria. Esta herramienta desengancha esa función:
clr.dll limpia del disconLoadImageDespués del desenganche, Assembly.Load(byte[]) se ejecuta sin inspección del EDR.
nLoadImage es la función nativa crítica que maneja todas las cargas de ensamblados en memoria en el runtime de .NET. Está declarada como un InternalCall en código administrado, lo que significa que no tiene implementación en C#; en cambio, es un puente directo al código nativo del CLR.
La Cadena de Llamadas:
Código Administrado (C#)
↓
Assembly.Load(byte[])
↓
RuntimeAssembly.nLoadImage(...) [InternalCall - sin cuerpo administrado]
↓
clr.dll!AssemblyNative::LoadImage (Implementación nativa en C++)
↓
Ensamblado cargado en AppDomain
Por Qué es Crítico:
Casi todas las cargas de ensamblados en memoria pasan por nLoadImage. El método Assembly.Load(byte[]) y sus sobrecargas (incluyendo la carga con bytes de símbolos) invocan a nLoadImage internamente. Cuando llamas a Assembly.Load(byte[]), el código administrado en mscorlib.dll pasa tu arreglo de bytes a través de RuntimeAssembly.nLoadImage(), que está marcado con [MethodImpl(MethodImplOptions.InternalCall)] - lo que significa que su cuerpo está vacío en C# y la ejecución salta inmediatamente al código nativo del CLR.
Incluso los escenarios de generación dinámica de código - frameworks de serialización que emiten ensamblados en tiempo de ejecución, generación de serializadores XML, y herramientas de red team como execute-assembly de Cobalt Strike - todos pasan por esta única función.
Implementación Nativa:
El stub InternalCall de nLoadImage en mscorlib.dll apunta a la función nativa C++ AssemblyNative::LoadImage dentro de clr.dll. Esta función:
En .NET Framework 4.8+, cada llamada a nLoadImage pasa automáticamente los bytes del ensamblado a AMSI de Windows Defender (AmsiScanBuffer) para escaneo antes de la ejecución, convirtiéndolo en un punto de estrangulamiento crítico para los productos de seguridad.
Firma de la Función (.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
);
Cuando llamas a Assembly.Load(byte[]), invoca nLoadImage con estos parámetros típicos:
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
return RuntimeAssembly.nLoadImage(
rawAssembly, // Your byte array
null, // rawSymbolStore
null, // evidence
ref stackMark, // LookForMyCaller
false, // fIntrospection
SecurityContextSource.CurrentAssembly // securityContextSource
);
El parámetro fIntrospection controla si el ensamblado se carga para ejecución (false) o solo para inspección por reflexión (true). El método Assembly.ReflectionOnlyLoad(byte[]) llama a nLoadImage con fIntrospection=true, permitiendo el examen de metadatos sin ejecución de código.
Por Qué lo Enganchan los EDR:
Dado que nLoadImage es el único punto de entrada para todas las cargas de ensamblados en memoria, los productos EDR lo enganchan a nivel nativo en clr.dll. Esto les permite:
Las soluciones tradicionales (parcheo de AMSI, desactivación de ETW) no afectan los ganchos a nivel de CLR porque operan en un nivel superior en la pila. El gancho ocurre dentro del propio CLR, antes de que AMSI siquiera sea invocado.
CLRUnhook.exe
Desengancha el CLR en el proceso actual. Nota: Esto solo funciona si el CLR ya está cargado (es decir, ejecutándose desde una aplicación .NET o después de cargar el CLR manualmente).
CLRUnhook.exe powershell.exe
CLRUnhook.exe 1234
Desengancha el CLR en un proceso remoto.
=== 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...
Managed Code (C#)
↓
Assembly.Load(byte[])
↓
RuntimeAssembly.nLoadImage(...) [InternalCall]
↓
clr.dll!AssemblyNative::LoadImage
↓
[EDR HOOK] ← We bypass this
↓
Original CLR Code
nLoadImage en la clr.dll cargada (actualmente enganchada)clr.dll original desde C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Utiliza escaneo de patrones para localizar nLoadImage:
Investigación de la Técnica:
Implementación:
SOLO PARA FINES EDUCATIVOS E INVESTIGACIÓN DE SEGURIDAD AUTORIZADA.
El uso no autorizado de esta herramienta para eludir controles de seguridad puede violar las leyes de fraude informático (CFAA, estatutos equivalentes). Úselo solo en sistemas que posea o para los que tenga permiso explícito por escrito para realizar pruebas.