
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.
gcc -o builder src/Builder/BeatRevV2Builder.csc_length in src/Stage2/dll/src/ReflectiveDLL.c in modo che corrisponda alla lunghezza del file raw shellcode utilizzato con il builder (ho incluso fakesc.bin come esempio)stage2stub.dll compilato su kali, modifica src/Stage1/newstage1.c e definisci stage2size come la dimensione di stage2stubx86_64-w64-mingw32-gcc newstage1.c -o stage1stub.exe -s -DUNICODE -Os -L /usr/x86_64-w64-mingw32/lib -l:librpcrt4.a./builder src/Stage0/newstage0_exe.c x64 stage1stub.exe stage2stub.dll shellcode.bindropper.exe. Questo è un payload Stage0 formattato e compilato da utilizzare sul target.
Circa 6 mesi fa mi sono reso conto che, sebbene avessi imparato e fatto molto riguardo al malware e all'elusione di AV/EDR, avevo speso pochissimo tempo a preoccuparmi di eludere o sconfiggere il reverse engineering e l'analisi del malware. Questo per alcune buone ragioni:
Tuttavia, è stato un interessante esperimento mentale e avevo alcuni colleghi che SAPEVANO di analisi del malware con cui confrontarmi. Sembrava una sfida di una portata completamente diversa rispetto all'elusione di AV/EDR e ho deciso di tentare.
La mia premessa iniziale era che il malware, la prima volta che veniva eseguito, si sarebbe in qualche modo "chiave" a quella macchina vittima; eventuali tentativi successivi di eseguirlo avrebbero valutato qualcosa nell'ambiente target e lo avrebbero confrontato con una corrispondenza nel malware. Se questi due fattori corrispondevano, veniva eseguito come previsto. Se non corrispondevano (come nel caso in cui il campione fosse stato trasferito in una sandbox di un analista di malware), il malware si eliminava da solo (ancora una volta basandomi pesantemente sul lavoro di LloydLabs e il suo delete-self-poc).
Questa "chiave" deve essere qualcosa di "unico" per il computer vittima. Idealmente sarà una combinazione di diverse informazioni, e ulteriormente offuscata. Ad esempio, potremmo raccogliere il nome host del computer e la quantità di RAM installata; questi due valori possono poi essere concatenati (es. Client018192MB) e quindi hashizzati utilizzando una funzione definita dall'utente per produrre un numero (es. 5343823956).
Ci sono una miriade di scelte su quali informazioni raccogliere, ma bisogna considerare quali valori un Blue Teamer potrebbe facilmente falsificare; un indirizzo MAC, ad esempio, può sembrare un identificatore "unico" attraente per una vittima, ma gli indirizzi MAC possono essere facilmente impostati manualmente in modo che un Reverse Engineer possa far corrispondere la propria sandbox alla vittima originale. Idealmente, i valori scelti e enumerati dovrebbero essere quelli difficili da replicare per un reverse engineer nel proprio ambiente.
Con un po' di magia di auto-eliminazione, il malware potrebbe leggere se stesso in un buffer, individuare una variabile segnaposto e sostituirla con questo numero, eliminarsi, e poi riscrivere il malware modificato su disco nella stessa posizione. Combinato con un'istruzione if/else in Main, la volta successiva che il malware viene eseguito, rileverà che è già stato eseguito in precedenza e quindi andrà a raccogliere di nuovo il nome host e la quantità di RAM per produrre il numero hashizzato. Questo verrebbe quindi valutato rispetto al numero memorizzato nel malware durante la prima esecuzione (5343823956). Se corrisponde (come nel caso in cui il malware venga eseguito sulla stessa macchina originale), viene eseguito come previsto, ma se viene restituito un valore diverso, chiamerà nuovamente la funzione di auto-eliminazione per rimuovere se stesso dal disco e proteggere l'autore dall'analista di malware.
Questa sembrava un'idea valida in teoria finché non ho parlato con un collega che ha una reale esperienza di analisi del malware e reverse engineering. Mi è stato detto che un reverse engineer sarebbe in grado di osservare l'istruzione condizionale nel malware (if ValorePrimaEsecuzione != GetNomeHostERam()), e visto che il valore atteso è hard-coded da un lato dell'istruzione condizionale, semplicemente modificare i registri per contenere il valore atteso, bypassando completamente l'intero meccanismo di protezione.
Questa nuova conoscenza ha completamente deragliato l'esperimento mentale e, visto che non avevo comunque un uso concreto per una capacità del genere, il progetto si è fermato per circa 6 mesi.
Questo progetto è riemerso alcune volte nei 6 mesi successivi, ma ogni volta è stato poco più che un pensiero fugace, poiché non avevo acquisito nuove conoscenze sul reverse engineering/analisi del malware e ancora non avevo bisogno di una tale capacità. Qualche giorno fa l'idea è riemersa e, sebbene nessuno di questi fattori sia realmente cambiato, suppongo di avere un po' più di conoscenze e questa volta non ho potuto lasciar perdere l'idea.
Tenendo presente il problema menzionato riguardo all'hard-coding dei valori, ho deciso di optare per un design multi-stadio. Li chiamerò Stage0, Stage1 e Stage2.
Stage0: Configurazione. Eseguito sull'infezione iniziale e cancellato successivamente
Stage1: Esecutore. Eseguito ogni volta successiva in cui il malware viene eseguito
Stage2: Payload. Il malware che si desidera proteggere. Genera un processo e inietta shellcode per restituire un Beacon.
Stage0 è l'eseguibile fresco consegnato al target dall'attaccante. Contiene Stage1 e Stage2 come array di byte crittografati AES; questo viene fatto per proteggere il malware in transito, o nel caso in cui un difensore riesca in qualche modo a ottenere una copia di Stage0 (cosa che non dovrebbe accadere). La chiave AES e il IV sono contenuti in Stage0, quindi in realtà questo non proteggerà Stage1 o Stage2 da un Blue Teamer competente.
Stage0 esegue le seguenti azioni:
Alla conclusione di questa sequenza di eventi, Stage0 termina. Poiché è stato eliminato dal disco al passo 2 e non è più in esecuzione in memoria, Stage0 è effettivamente sparito; senza una conoscenza preventiva di questa tecnica, il resto del ciclo di vita del malware sarà molto più confuso di quanto già non sia.
Nel passo 4 vengono raccolti il nome del processore e il ProductID di Microsoft; il ProductID viene recuperato dal Registro, e questo valore può essere modificato manualmente, il che offre una facile opportunità per un Blue Teamer di far corrispondere la propria sandbox all'ambiente target. A seconda delle informazioni ambientali raccolte, questo può diventare più facile o più difficile.
Stage1 è stato rilasciato da Stage0 e si trova esattamente nella stessa posizione di Stage0 (incluso il nome). Stage2 è memorizzato come ADS di Stage1. Quando l'attaccante/persistenza esegue successivamente il malware, sta eseguendo Stage1.
Stage1 esegue le seguenti azioni:
Si noti che Stage2 DEVE terminare per poter essere sovrascritto; il trucco dell'auto-eliminazione non sembra funzionare su file che sono già ADS, poiché la tecnica di auto-eliminazione si basa sulla rinomina del flusso di dati primario dell'eseguibile. Stage2 sarà idealmente un eseguibile di iniezione o spawn+iniezione.
Ci sono due punti in cui Stage1 potrebbe rilevare di non essere eseguito dalla stessa vittima e eliminare se stesso/Stage2 per proteggere l'attaccante. Il primo è il controllo dell'intestazione eseguibile dopo aver decrittografato Stage2 utilizzando le informazioni ambientali raccolte; in teoria questo passo potrebbe essere bypassato da un reverse engineer, ma è un primo buon controllo. Il secondo punto di protezione è il risultato della chiamata CreateProcess: se fallisce perché Stage2 non è stato decrittografato correttamente, il malware viene eliminato allo stesso modo. Il risultato di questa chiamata potrebbe anche essere modificato per prevenire l'eliminazione da parte del reverse engineer, ma ciò non cambia il fatto che Stage2 è crittografato e inaccessibile.
Stage2 è il cuore della catena del malware; è un vero e proprio runner di shellcode/pezzo di malware. Crittografandolo e proteggendolo nel modo in cui abbiamo fatto, le azioni del malware finale sono molto meglio offuscate e protette da reverse engineer e analisti di malware. Durante lo sviluppo ho utilizzato uno dei miei runner di shellcode esistenti contenente shellcode CobaltStrike, ma potrebbe essere qualsiasi cosa l'attaccante voglia eseguire e proteggere.
Quindi cosa si ottiene effettivamente con un ciclo di vita del malware come questo? Ci sono alcune caratteristiche interessanti di cui parlare.
I flussi di dati alternativi sono una caratteristica unica dei file system NTFS; ciò significa che la maggior parte dei modi di trasferire il malware dopo l'infezione iniziale rimuoveranno e perderanno Stage2 perché è un ADS di Stage1. Bisognerebbe prestare particolare attenzione per trasferire il campione in modo da preservare Stage2, poiché senza di esso molti reverse engineer e analisti di malware saranno molto confusi su cosa stia accadendo. Gli archivi RAR sono in grado di preservare gli ADS e strumenti come 7Z e Peazip possono estrarre file e i loro ADS.
Come accennato in precedenza, quando il malware che utilizza questo ciclo di vita arriva a un Blue Teamer, dovrebbe essere a Stage1; Stage0 è già passato e Stage2 è già crittografato con le informazioni ambientali raccolte da Stage0. Non sapere che Stage0 sia mai esistito aggiungerà una notevole incertezza alla comprensione del ciclo di vita e alla decrittografia di Stage2.
In teoria (poiché ancora non ho esperienza di reverse engineering), Stage1 dovrebbe poter essere sottoposto a reverse engineering (dopo che i Blue Teamer avranno esaminato alcune copie perché continua a eliminarsi) e le informazioni che Stage1 raccoglie dal sistema target dovrebbero poter essere identificate. A condizione di una risposta ben orchestrata, il Blue Team dovrebbe essere in grado di identificare la vittima da cui proveniva il malware e andare a raccogliere quelle informazioni da essa e inserirle nel programma in modo che possano essere trasformate appropriatamente nella chiave AES/IV che decrittografa Stage2. Ci sono tuttavia molti "se" legati alla competenza relativa del reverse engineer e alla disponibilità della macchina vittima per recuperare tali informazioni.
La whitelisting delle applicazioni frustrerebbe significativamente questo ciclo di vita. Stage0/Stage1 potrebbero essere sideloadati come DLL, tuttavia sospetto che Stage2 come ADS presenterebbe alcuni problemi. Non ho un ambiente per testare il malware contro AWL né mi sono preoccupato di portare tutto in formato DLL, quindi non posso dirlo. Sono sicuro che ci siano modi creativi per aggirare questi problemi.
Sono anche abbastanza fiducioso che ci siano modi più intelligenti per eseguire Stage2 che non siano rilasciarlo su disco e chiamare CreateProcess; mappare manualmente l'eseguibile o utilizzare uno strumento come Donut per trasformarlo in shellcode sembrano idee ragionevoli.
Durante lo sviluppo ho creato un'applicazione Builder in cui possono essere inseriti Stage1 e Stage2 per produrre uno Stage0 funzionale; questo non verrà fornito, tuttavia fornirò la maggior parte del codice sorgente per stage1 poiché è il pezzo che sarebbe più visibile a un Blue Teamer. Stage0 sarà escluso come esercizio per il lettore, e stage2 è qualsiasi eseguibile autonomo tu voglia eseguire e proteggere. Questo POC può essere ulteriormente ricercato a discrezione e sforzo dei lettori capaci.Fornirò una copia compilata di questo malware come Dropper64.exe. Dropper64.exe è compilato per x64. Dropper64.exe è Stage0; contiene Stage1 e Stage2. All'esecuzione, Stage1 e Stage2 verranno scritti su disco ma NON verranno eseguiti automaticamente, dovrai eseguire di nuovo Dropper64.exe (ora Stage1). Stage2 è una versione x64 di calc.exe. Lo includo per chiunque dei Blue Teamers voglia dare un'occhiata, ma tieni presente che in uno scenario di risposta agli incidenti il 99& delle volte riceverai Stage1/Stage2, Stage0 sarà sparito.
Questo è stato un interessante progetto personale che mi ha impegnato un lungo fine settimana. Sono sicuro che sarebbe stato molto più avanzato/completo se avessi avuto esperienza con un debugger e un disassemblatore, ma si fa del proprio meglio con ciò che si ha. Non vedo l'ora di sentire cosa ne pensano i Blue Teamers e altri sviluppatori di malware. Sono certo di aver reinventato la ruota in modo eccessivamente complicato rispetto a ciò che fanno gli APT reali, ma lungo il percorso ho imparato alcune cose. Grazie per aver letto!