
Un deobfuscator per ConfuserEx2 con supporto per anti-tamper, compressor, costanti, flusso di controllo e recupero delle risorse.
Fork potenziato di UnconfuserEx con supporto migliorato per le varianti moderne di ConfuserEx2, recupero anti-tamper, rimozione del compressor, ripristino del control flow e ricostruzione delle risorse.
https://github.com/user-attachments/assets/de2c7fd9-6736-4f39-83c0-3c25aa9c1f24
Se hai mai avuto a che fare con campioni di malware, alcuni di essi sono offuscati con ConfuserEx, e ho provato alcuni deobfuscator pubblici contro l'ultima versione ma semplicemente non funzionavano, quindi ho deciso di fare il fork di uno pubblico che funzionava con quell'ultima versione e modificarlo secondo le mie esigenze.
Questa repo è un fork di MadMin3r/UnconfuserEx. Il merito va anche a lui. Il progetto originale ha fatto la parte difficile creando un deobfuscator focalizzato su ConfuserEx2 che potesse rimuovere davvero le protezioni reali.
È ciò che fa questo progetto. Esegue una lista di remover in un ordine fisso, riscrive corpi dei metodi/risorse/metadati dove può e scrive un nuovo assembly in output. La parte importante è l'ordine. Compressor e anti tamper devono avvenire all'inizio perché il resto del modulo potrebbe non essere ancora IL reale.
La versione upstream era già utile, ma alcuni casi continuavano a presentarsi.
Uno era il percorso LZMA. Alcuni campioni ti passano byte che sembrano il payload di constants/risorse, ma le proprietà LZMA sono senza senso. Se li passi direttamente al decoder, ottieni dimensioni del dizionario assurde e alla fine eccezioni come dimensioni dell'array oltre l'intervallo supportato. Quindi questa versione controlla le proprietà, limita la dimensione del dizionario, limita la dimensione non compressa e si ferma prima che il decoder allochi qualcosa di ridicolo.
LZMA properties => CE FD 62 5F 9F
Invalid LZMA properties byte 0xCE or unreasonable dictionary size
Le constants avevano un altro problema stupido ma reale. Gran parte del codice del resolver vuole che l'ID davanti alla chiamata del getter sia un ldc.i4. A volte non è più una singola istruzione. È una piccola espressione aritmetica.
ldc.i4 0x1234
ldc.i4 0x55
xor
call string <const getter>(int32)
Il fork originale vede xor, chiama GetLdcI4Value() e muore perché xor ovviamente non è un caricamento di interi. Questa versione ripercorre la piccola sequenza aritmetica, emula lo stack, la riconduce a un singolo ldc.i4 e poi lascia che il resolver normale continui.
Quindi invece di trattarla come una protezione constants completamente diversa, diventa questo:
ldc.i4 0x1261
call string <const getter>(int32)
Poi il percorso esistente del resolver constants normale/x86 può fare il suo lavoro.
Il control flow è dove era così così
Il remover degli switch può gestire la forma normale del dispatcher a switch di ConfuserEx. Attraversa i blocchi, recupera la destinazione successiva, elimina i blocchi morti ed emette un corpo del metodo sano. Ma ci sono campioni in cui viene compresa solo una parte del metodo. Se muti metà di un metodo e poi scopri che è ancora offuscato, l'output è peggio che inutile perché ora hai IL rotto e nessun modo pulito per capire cosa sia successo.
Quindi questa versione crea uno snapshot del corpo del metodo prima di toccarlo:
instructions
exception handlers
Se la deoffuscazione lancia un'eccezione, o se il metodo sembra ancora offuscato in seguito, il corpo originale viene ripristinato. Il log può ancora dire "questo metodo non è stato risolto", ma l'assembly non viene corrotto silenziosamente solo perché un metodo aveva un dispatcher strano.
Anche il control flow a salti/trampoline ha ricevuto una passata dedicata. Alcuni metodi non sono solo dispatcher a switch. Sono piccoli trampoline di ramificazione concatenati finché non si raggiunge il blocco reale. Ora vengono rilevati e risolti invece di essere ignorati dal percorso che gestisce solo gli switch.
Il remover del compressor è la parte che deve essere eseguita prima di tutto il resto.
Gli stub del compressor di ConfuserEx di solito tengono compresso l'assembly reale, avviano un piccolo loader, decomprimono il payload e lo caricano a runtime.
Il remover individua la forma del loader, estrae il payload incorporato, lo decomprime e sostituisce il modulo con l'assembly reale. Vengono gestiti sia i layout normali che quelli compatti del compressor.
[+] Compressor detected
[+] Extracted compressed module payload
[+] Decompressed real module
[+] Continuing pipeline on unpacked assembly
L'anti-tamper ora ha due percorsi.
L'anti-tamper normale/dinamico decripta i corpi dei metodi dalle sezioni protette e riscrive i corpi ripristinati nel modulo. L'anti-tamper JIT è più fastidioso perché i corpi devono essere materializzati quando il runtime li richiede.
La forma approssimativa è:
find init
extract keys
find encrypted JIT body section
derive per method key
read body
write CilBody back
È ancora basato su pattern. Se lo stub è cambiato abbastanza, fallirà, ovviamente.
Le risorse vengono gestite in modo simile alle constants: trova il blob di risorse crittografato, recupera la forma chiave/decryptor, decripta, decomprime se necessario, quindi rimette le risorse dove i normali strumenti .NET se le aspettano.
C'è anche un percorso opzionale di rebuild del PE incorporato. Alcuni campioni protetti trasportano un PE gestito all'interno di una risorsa. Con il rebuild abilitato, il remover prova anche ad analizzare e riscrivere quel payload invece di lasciare un assembly esterno deoffuscato con uno interno intatto.
UnConfuserEx.exe sample.exe sample.clean.exe --rebuild-embedded-pe
Usalo quando sai che il campione nasconde un altro assembly gestito all'interno delle risorse. Se il payload non è un PE gestito, il percorso di rebuild dovrebbe lasciarlo stare.
Compilalo:
dotnet build .\UnConfuserEx.sln -c Release
Eseguilo:
.\UnConfuserEx\bin\Release\net9.0\UnConfuserEx.exe .\protected.exe
Oppure dagli un percorso di output esplicito:
.\UnConfuserEx\bin\Release\net9.0\UnConfuserEx.exe .\protected.exe .\protected.clean.exe
Se non fornisci un percorso di output, ne scrive uno accanto all'input con -deobfuscated aggiunto al nome.
Per payload gestiti incorporati:
.\UnConfuserEx\bin\Release\net9.0\UnConfuserEx.exe .\protected.exe .\protected.clean.exe --rebuild-embedded-pe
Questa è la lista di supporto attuale. Non significa che ogni possibile fork di ConfuserEx funzioni. Significa che queste sono le forme che la pipeline sa cercare.
Probabilmente c'è altro nascosto nel codice che ho dimenticato di elencare xD.
I log utili sono quelli che ti dicono quale fase è fallita, non solo che l'output non è stato eseguito.
Esempio di un percorso constants che è stato corretto:
Constants detected, attempting to remove
Found 3 constant getter(s)
Detected constant decryption type is Dynamic
Decompressed constants blob to 18492 byte(s)
Resolving getter <Module>::???????? as String with 41 call site method(s)
Removed all instances of getter <Module>::????????
Esempio di un metodo control-flow lasciato intenzionalmente da solo:
Removing obfuscation from method System.Void Example::Run()
Method System.Void Example::Run() still appears obfuscated after deobfuscation -- left original body intact
Removed obfuscation from 42 methods. Failed to remove from 0 methods. 1 methods left untouched
Quel secondo log non è perfetto, ma è ALMENO onesto
Un fork personalizzato di ConfuserEx che cambia ogni forma di helper.
Un getter di constants che calcola il proprio ID attraverso un intero casino di cf invece di una piccola espressione di stack.
Un grafo cf in cui il dispatcher dipende da valori runtime che l'emulatore statico non conosce.
Helper nativi che richiedono una reale esecuzione runtime invece dell'emulazione IL.
Assembly che erano già rotti prima di essere offuscati.
Varianti di anti-tamper JIT con un layout diverso del corpo crittografato.
Se vuoi che una issue sia utile, includi dati sufficienti per riprodurla.
Rimuovi le estensioni dei file dai campioni prima di caricarli.
Archivia tutto insieme e includi questo:
Command:
UnConfuserEx.exe <target> <optional output>
Failure stage:
- compressor
- anti tamper
- constants
- control flow
- resources
- writing output
- runtime after deobfuscation
Expected result:
Actual result:
Console output:
Archive link:
Notes / investigation:
Se mandi solo "non funziona", la risposta sarà probabilmente "già".
Piccole correzioni mirate sono meglio di enormi riscritture.
Se aggiungi il supporto per una nuova forma di protezione, mantienila isolata nel remover che la possiede. Se aggiungi un comportamento di fallback, assicurati che un errore non corrompa l'assembly di output. Se tocchi il control flow, presupponi che lo strano campione che hai corretto non sia l'unico campione strano che esiste :DDD.
Solo NON rendere lo strumento più difficile da debuggare.
Questo progetto è basato su MadMin3r/UnconfuserEx.
Il progetto originale ha fornito le fondamenta e la maggior parte della pipeline di deoffuscazione core. Questo fork si concentra sul migliorare l'affidabilità, aggiungere il supporto per ulteriori varianti di protezione e gestire casi limite osservati in campioni del mondo reale.
Questo progetto è nato come strumento pratico di reverse engineering piuttosto che come esercizio di ingegneria del software. L'attenzione è sempre stata sul recuperare in modo affidabile assembly protetti piuttosto che mantenere una qualità del codice perfetta.
Questo strumento è pensato per l'analisi autorizzata di malware, il reverse engineering, il recupero software, l'interoperabilità e la ricerca educativa.
Gli utenti sono responsabili del rispetto delle leggi applicabili e dell'ottenimento di qualsiasi autorizzazione richiesta prima di analizzare, accedere o elaborare software o sistemi.
Zypherion Technologies non autorizza l'uso illecito di questo strumento e declina ogni responsabilità per l'uso improprio da parte di terzi.
Nulla in questa repository o su www.zypherion.tech costituisce consulenza legale.