
POC per frustrare/sconfiggere gli analisti di malware
Il lavoro che segue è un Proof of Concept (POC) per consentire al malware di "chiave" stessa a una particolare vittima al fine di frustrare gli sforzi degli analisti di malware.
Non mi assumo alcuna responsabilità per l'uso malevolo di qualsiasi idea o codice contenuto in questo progetto. Fornisco questa ricerca per istruire ulteriormente i professionisti della sicurezza informatica e fornire formazione/spunti di riflessione aggiuntivi per Analisti di Malware, Reverse Engineers e Blue Team in generale.
La prima volta che il malware viene eseguito su una vittima, crittografa AES il payload effettivo (una RDLL) utilizzando dati ambientali di quella vittima. Ogni volta successiva in cui il malware viene eseguito, raccoglie le stesse informazioni ambientali, decrittografa AES il payload memorizzato come array di byte all'interno del malware e lo esegue. Se la decrittografia fallisce o il payload non viene eseguito, il malware si elimina da solo. Protezione contro i reverse engineer e gli analisti di malware.


Non mi sentivo soddisfatto di questo progetto, quindi ci sono tornato e ho fatto una riscrittura piuttosto sostanziale. La ricerca originale e le tecniche sono disponibili Qui.
Le modifiche principali sono le seguenti:
Ci sono diverse cose che potrebbero essere prese dal codice sorgente di questo progetto per essere utilizzate altrove. Si spera che possa essere utile a qualcuno.
C'erano alcune carenze nella release originale di BeatRev che ho deciso di cercare di risolvere.
Stage2 era in precedenza un eseguibile autonomo memorizzato come flusso di dati alternativo (ADS) di Stage1. Per ottenere la crittografia AES per vittima e la successiva decrittografia ed esecuzione, ogni volta che Stage1 veniva eseguito, leggeva l'ADS, lo decrittografava, riscriveva nell'ADS, chiamava CreateProcess, e poi ri-crittografava Stage2 e lo riscriveva su disco nell'ADS. C'erano molte operazioni di I/O e la chiamata CreateProcess ovviamente non era ideale.
Mi sono imbattuto nella ricerca di Steven Fewer riguardante le Reflective DLL e sembrava essere una buona soluzione. Stage2 ora è una RDLL; il nostro malware/runner di shellcode/qualsiasi cosa vogliamo proteggere può essere portato in formato RDLL e memorizzato come array di byte all'interno di Stage1 che viene poi decrittografato in fase di esecuzione ed eseguito da Stage1. Questo rimuove tutte le operazioni di I/O e la chiamata CreateProcess della Versione 1 ed è un cambiamento gradito.
Stage1 non aveva alcun tipo di misura di elusione dell'antivirus programmata; questo era intenzionale, poiché è un lavoro extra e non era realmente lo scopo di questa ricerca. Durante la riscrittura l'ho preso come una sfida aggiuntiva e ho aggiunto l'hashing delle API per rimuovere le funzioni dalla tabella degli indirizzi di importazione di Stage1. Questo ha aiutato con il rilevamento e Stage1 ha un tasso di rilevamento di 4/66 su VirusTotal. Mi sentivo a mio agio nel caricare Stage1 dato che è già associato al computer originale su cui è stato eseguito e la firma del file cambia costantemente a causa della crittografia AES che avviene.
Di recente ho iniziato a prestare attenzione all'entropia come mezzo per rilevare il malware; per cercare di abbassare l'entropia altrimenti molto alta che un enorme blob binario crittografato AES dà a un eseguibile, ho esaminato l'integrazione di shellcode memorizzato come UUID. Poiché il binario è memorizzato in rappresentazione stringa, c'è un'entropia complessiva inferiore nell'eseguibile. Utilizzando questa tecnica, l'entropia di Stage0 è ora ~6.8 e Stage1 ~4.5 (su una scala massima di 8).
Infine, è una grande seccatura integrare e produrre uno Stage0 completo a causa di tutti i pezzi che devono essere manipolati. Per semplificare, ho creato un'applicazione builder che accetta un file template Stage0.c, uno stub di Stage1, uno stub di Stage2 e un file raw shellcode (questo è stato costruito attorno a Stage2 come runner di shellcode contenente shellcode CobaltStrike) e produce un payload Stage0 compilato da utilizzare sul target.
Il codice della Reflective DLL di Stephen Fewer contiene alcune istruzioni specifiche del compilatore Visual Studio; sono sicuro che sia possibile portare la tecnica su MingW ma non ho le competenze per farlo. Il problema principale qui è che lo shellcode di CobaltStrike (senza stato è ~265K) deve andare all'interno della RDLL e essere compilato. Per aggirare questo e integrarlo bene con il resto del processo, ho scritto la mia RDLL Stage2 in modo che contenga una variabile globale di memoria delle dimensioni dello shellcode CS; questo blocco di memoria di ~265K ha un piccolo segnaposto al suo interno che può essere individuato nel binario compilato. Il codice in src/Stage2 ha già questo aggiunto.
Una volta compilato, questo Stage2stub viene trasferito su kali dove può essere eseguita una patch binaria per inserire il vero shellcode CS nel posto di memoria che gli spetta. Questo produce lo Stage2 completo.
Per evitare il disastro di I/O e CreateProcess descritto in precedenza, lo Stage2 completo deve anche essere applicato come patch nello Stage1 compilato da parte di Stage0; questo è necessario per permettere a Stage2 di essere crittografato una volta sul target, oltre a impedire che Stage2 venga memorizzato separatamente su disco. Lo stesso concetto descritto in precedenza per Stage2 viene eseguito da Stage0 sul target per assemblare il payload finale Stage1. Va notato che la funzione memmem viene utilizzata per individuare il segnaposto all'interno di ogni stub; questa funzione non è disponibile su Windows, quindi è stata utilizzata un'implementazione personalizzata. Grazie a Foxik384 per il suo codice.
Per eseguire una patch binaria, dobbiamo allocare la memoria necessaria in anticipo; questo ha un effetto cumulativo, poiché Stage1 deve ora essere abbastanza grande da contenere anche Stage2. Con il passaggio aggiuntivo di convertire Stage2 in una stringa UUID, Stage2 aumenta di dimensioni, così come Stage1 per contenerlo. Una RDLL Stage2 con una dimensione compilata di ~290K produce un payload Stage0 di ~1.38M e un payload Stage1 di ~700K.
L'applicazione builder supporta solo la creazione di EXE x64. Tuttavia, con un po' più di lavoro, in teoria si potrebbe fare Stage0 come DLL, così come Stage1, e far sì che l'intero ciclo di vita esista come un hijack di DLL invece di un eseguibile autonomo.
Queste istruzioni ti guideranno nell'utilizzo di questo POC.