Un desofuscador de ConfuserEx2 con soporte para anti-manipulación, compresor, constantes, flujo de control y recuperación de recursos.
Fork mejorado de UnconfuserEx con soporte mejorado para variantes modernas de ConfuserEx2, recuperación anti-manipulación, eliminación de compresor, restauración del flujo de control y reconstrucción de recursos.
https://github.com/user-attachments/assets/de2c7fd9-6736-4f39-83c0-3c25aa9c1f24
Si alguna vez has jugado con muestras de malware, algunas de ellas están ofuscadas con ConfuserEx, y he probado algunos deofuscadores públicos contra la última versión, pero simplemente no funcionaba, así que decidí hacer un fork de uno público que sí funcionaba contra esa última versión y modificarlo según mis necesidades.
Este repositorio es un fork de MadMin3r/UnconfuserEx. El mérito también es suyo. El proyecto original hizo la parte difícil de crear un deofuscador centrado en ConfuserEx2 que pudiera eliminar protecciones reales.
Eso es lo que hace este proyecto. Ejecuta una lista de removedores en un orden fijo, reescribe cuerpos de métodos/recursos/metadatos donde puede, y escribe un nuevo ensamblado de vuelta. La parte importante es el orden. El compresor y la protección anti-manipulación deben ocurrir al principio porque el resto del módulo podría ni siquiera ser IL real todavía.
La versión original ya era útil, pero seguían apareciendo algunos casos.
Uno era la ruta LZMA. Algunas muestras te dan bytes que parecen la carga útil de constantes/recursos, pero las propiedades LZMA no tienen sentido. Si se lo pasas directamente al decodificador, obtienes tamaños de diccionario absurdos y finalmente excepciones como dimensiones de matriz que superan el rango admitido. Por eso esta versión verifica las propiedades, limita el tamaño del diccionario, limita el tamaño sin comprimir, y aborta antes de que el decodificador asigne algo ridículo.
LZMA properties => CE FD 62 5F 9F
Invalid LZMA properties byte 0xCE or unreasonable dictionary size
Las constantes tenían otro problema tonto pero real. Gran parte del código del resolvedor espera que el ID ubicado delante de la llamada al getter sea un ldc.i4. A veces ya no es una sola instrucción. Es una pequeña expresión aritmética.
ldc.i4 0x1234
ldc.i4 0x55
xor
call string <const getter>(int32)
El fork original ve xor, llama a GetLdcI4Value() y falla porque xor obviamente no es una carga de entero. Esta versión retrocede sobre la pequeña secuencia aritmética, emula la pila, la colapsa de nuevo en un solo ldc.i4, y luego deja que el resolvedor normal continúe.
Así que en lugar de tratar esto como una protección de constantes totalmente diferente, se convierte en esto:
ldc.i4 0x1261
call string <const getter>(int32)
Luego la ruta existente del resolvedor de constantes normal/x86 puede hacer su trabajo.
El flujo de control es donde flojeaba
El removedor de switches puede manejar la forma normal del despachador de switch de ConfuserEx. Recorre bloques, recupera el siguiente objetivo, elimina bloques muertos y emite un cuerpo de método coherente. Pero hay muestras donde solo se entiende parte del método. Si mutas la mitad de un método y luego descubres que sigue ofuscado, el resultado es peor que inútil porque ahora tienes IL roto y ninguna forma clara de razonar sobre lo que sucedió.
Así que esta versión toma una instantánea del cuerpo del método antes de tocarlo:
instructions
exception handlers
Si la deofuscación falla, o si el método sigue pareciendo ofuscado después, el cuerpo original se restaura. El registro puede seguir diciendo "este método no se resolvió", pero el ensamblado no se corrompe silenciosamente solo porque un método tenía un despachador extraño.
El flujo de control de saltos/trampolines también recibió su propio pase. Algunos métodos no son solo despachadores de switch. Son pequeños trampolines de ramas encadenados hasta llegar al bloque real. Ahora se detectan y se pliegan en lugar de ser ignorados por la ruta que solo maneja switches.
El removedor de compresor es la parte que debe ejecutarse antes que todas las demás.
Los stubs de compresor de ConfuserEx normalmente mantienen el ensamblado real comprimido, inician un pequeño cargador, descomprimen la carga útil y la cargan en tiempo de ejecución.
El removedor encuentra la forma del cargador, extrae la carga útil incrustada, la descomprime y cambia el módulo al ensamblado real. Se manejan tanto los diseños de compresor normal como compacto.
[+] Compressor detected
[+] Extracted compressed module payload
[+] Decompressed real module
[+] Continuing pipeline on unpacked assembly
La protección anti-manipulación tiene ahora dos rutas.
La protección anti-manipulación normal/dinámica descifra los cuerpos de métodos de secciones protegidas y escribe los cuerpos restaurados de vuelta en el módulo. La protección anti-manipulación JIT es más molesta porque se supone que los cuerpos se materializan cuando el runtime los solicita.
La forma aproximada es:
find init
extract keys
find encrypted JIT body section
derive per method key
read body
write CilBody back
Esto todavía se basa en patrones. Si el stub cambió lo suficiente, fallará, obvio.
Los recursos se manejan de manera similar a las constantes: encontrar el blob de recursos cifrados, recuperar la forma de la clave/descifrador, descifrar, descomprimir si es necesario, y luego colocar los recursos de vuelta donde las herramientas normales de .NET los esperan.
También hay una ruta opcional de reconstrucción de PE incrustado. Algunas muestras protegidas llevan un PE administrado dentro de un recurso. Con la reconstrucción habilitada, el removedor intenta analizar y reescribir esa carga útil también, en lugar de dejar un ensamblado externo deofuscado con uno interno intacto.
UnConfuserEx.exe sample.exe sample.clean.exe --rebuild-embedded-pe
Úsala cuando sepas que la muestra oculta otro ensamblado administrado dentro de los recursos. Si la carga útil no es un PE administrado, la ruta de reconstrucción debería dejarla en paz.
Compílalo:
dotnet build .\UnConfuserEx.sln -c Release
Ejecútalo:
.\UnConfuserEx\bin\Release\net9.0\UnConfuserEx.exe .\protected.exe
O dale una ruta de salida explícita:
.\UnConfuserEx\bin\Release\net9.0\UnConfuserEx.exe .\protected.exe .\protected.clean.exe
Si no das una ruta de salida, escribe una al lado de la entrada con -deobfuscated añadido al nombre.
Para cargas útiles administradas incrustadas:
.\UnConfuserEx\bin\Release\net9.0\UnConfuserEx.exe .\protected.exe .\protected.clean.exe --rebuild-embedded-pe
Esta es la lista de soporte actual. No significa que todos los forks posibles de ConfuserEx funcionen. Significa que estas son las formas que el pipeline sabe buscar.