Smantellamento di KillDisk: il rovescio della componente distruttiva di BlackEnergy
Saltiamo la lunga introduzione sulla minaccia BlackEnergy e passiamo direttamente a studiare il componente malware chiamato “ololo.exe”, noto al pubblico anche come KillDisk. KillDisk è un modulo del framework BlackEnergy mirato alla distruzione dei dati e alla creazione di caos/distrazione durante le operazioni APT.
https://www.virustotal.com/en/file/11b7b8a7965b52ebb213b023b6772dd2c76c66893fc96a18a9a33c8cf125af80/analysis/Gli strumenti principali utilizzati nella nostra analisi di oggi sono Process Monitor che fa parte delle utility SysInternals di Mark Russinovich e IDA Pro Disassembler. Tutte le manipolazioni saranno eseguite in un ambiente virtuale basato sul sistema operativo Windows XP. Iniziamo con una rapida configurazione iniziale della VM di test, accendiamo la macchina e creiamo uno snapshot chiamato “Prima dell’infezione”. Cominciamo!
Per tenere traccia di tutti gli eventi relativi al nostro oggetto di studio lanciamo Process Monitor e assicuriamo che rimanga visibile:E che tracci il processo necessario:
Successivamente, carichiamo il virus in IDA Pro Disassembler e vediamo la seguente immagine.Questa è la funzione WinMain, cioè la funzione principale, quindi iniziamo la sua analisi e vediamo che la prima (e unica) cosa che fa sarà chiamare la procedura denominata
sub_40E070
:
Quindi dirigiamoci direttamente a quella procedura. Qui possiamo trovare procedure responsabili per l’estrazione delle estensioni dei file in memoria e la prima procedura subito dopo di esse che chiama una cosa interessante chiamata sub_40E080
:
Diamo un’occhiata più da vicino ai suoi contenuti:
Il suo interno contiene una interessante chiamata di sistema
CreateFile
e un paio di procedure sub_40C390
and sub_40C400
. Quindi è ora di fare uno snapshot del nostro laboratorio virtuale e continuare il nostro viaggio!
Esaminiamo la chiamata alla funzione CreateFile, impostiamo il BreakPoint su di essa ed eseguiamo il nostro campione di studio:
Un piccolo suggerimento riguardo questa funzione:
Come sappiamo la maggior parte dei compilatori passa gli argomenti a una funzione attraverso uno stack e dato che stiamo trattando con un campione inizialmente scritto in C, e secondo le direttive C gli argomenti sono spinti sullo stack da destra a sinistra in modo tale che il primo argomento della funzione sia spinto per ultimo nello stack e finisca per essere il primo elemento sulla sua cima. [1].
There is a special register for working with the stack – ESP (Extended Stack Pointer). The ESP, by definition, always points to the top of the stack:
Uno sguardo nello stack:
L’ultimo argomento che è stato spinto nello stack (uno sulla sua cima) è il primo argomento accettato dalla funzione (secondo la sintassi della
CreateFile
funzione) e contiene il nome del file nel nostro caso \.PHYSICALDRIVE0
, quindi il target è il primo drive fisico.Ora analizziamo altri argomenti inviati alla funzione prendendo i loro valori dallo stack:
Alcune conclusioni:
- lpFileName – puntatore a una stringa ASCIIZ contenente il nome (percorso) del file aperto o creato (come abbiamo già scoperto);
- dwDesiredAccess – tipo di accesso al file: nel nostro caso il valore è
0C0000000h
che significaGENERIC_READ+GENERIC_WRITE
oraccesso in lettura-scrittura
; - DwShareMode – una modalità che permette la condivisione dei file con diversi processi e questo parametro può avere diversi valori nel nostro caso il valore di 00000003b si traduce in
FILE_SHARE_READ+FILE_SHARE_WRITE
oaltri processi possono aprire il file per lettura/scrittura
; - IpSecurityAttributes – puntatore alla struttura SecurityAttributes (file winbase.h) che definisce impostazioni di sicurezza relative all’oggetto file del kernel, se la sicurezza non è impostata viene usato un valore
NULL
; - dwCreationDistribution – è responsabile per decidere le azioni quando il file esiste o non esiste, nel nostro caso un valore di
3
significaOPEN_EXISTING
orapri il file se esiste, e restituisci un errore se non esiste
; - DwFlagsAndAttributes – flag e attributi; questo parametro è utilizzato per impostare le caratteristiche del file creato ed è ignorato in modalità di lettura 0.
- hTemplateFile – parametro utilizzato solo quando viene creato un nuovo file
0
. - Al successo la funzione restituisce il nuovo handle del file a
ЕАХ
E se la funzione fallisce unNULL
viene scritto nelЕАХ
registro.
Bene quindi dopo aver chiamato CreateFile
otteniamo un valore non zero per EAX il che significa che contiene l’handle del file richiesto e.g. \.PHYSICALDRIVE0
:
Abbiamo ottenuto il nostro handle quindi procediamo alla chiamata successiva,
sub_40C390
:
Come prima diamo un’occhiata dentro la procedura e vediamo un’altra chiamata interessante:
Suggerimento della funzione:
Uno sguardo nello stack proprio prima di chiamare questa funzione:Conclusioni:
- hDevice – descrittore del dispositivo (riconosciamo un vecchio amico qui
\.PHYSICALDRIVE0
); - dwIoControlCode – codice dell’operazione. Nel nostro caso il valore è
0x70000h
che significaIOCTL_DISK_GET_DRIVE_GEOMETRY
o recupero delle informazioni riguardanti la geometria del disco (quantità di cilindri, tipo di media, tracce per cilindro, settori per traccia, byte per settore); - lpInBuffer – buffer con dati di input. Non abbiamo bisogno di alcun dato di input quindi
NULL
lo è; - nInBufferSize – dimensione del buffer di input in byte pari a
0
nel nostro caso; - lpOutBuffer – puntatore al buffer di output. Il suo tipo è impostato dal parametro dwIoControlCode
0x0011FCA8h
; - nOutBufferSize – dimensione del buffer di output in byte
0x18h
(24); - lpBytesReturned – un puntatore a una variabile che contiene la quantità di byte che sono scritti nell’output
0x0011FCA4h
; - lpOverlapped – puntatore a una struttura OVERLAPPED;
- Mentre la chiamata viene elaborata guardiamo nel buffer (all’indirizzo che è stato inviato come argomento a lpOutBuffer precisamente
0x0011FCA8h
):
- All’indirizzo di
0x0011FCA4h
abbiamo un ritorno della quantità di byte scritti nel buffer di output come previsto dalla funzione (contrassegnato in verde) ne abbiamo ricevuti quanti ne abbiamo chiesti 24 simboli. - All’indirizzo di
0x0011FCA8h
abbiamo ottenuto informazioni sulla geometria del primo disco fisico (\.PHYSICALDRIVE0
):- quantità di cilindri –
0x519h
(1305) - tipo di media –
0x0Ch
(12) che significa disco rigido fisso. - tracce per cilindro –
0x0FFh
(255) - settori per traccia –
0x3Fh
(63) - byte per settore –
0x200
(512)
- quantità di cilindri –
Ora ci riferiamo alla procedura tre, sub_40C400
:
All’interno della procedura due chiamate hanno immediatamente attirato la nostra attenzione, specificamente
SetFilePointerEx
eWriteFile
:
Un suggerimento sulla funzione:
Come da tradizione consolidata guardiamo nello stack della chiamata alla funzione:
- hfile – il nostro familiare descrittore del dispositivo
\.PHYSICALDRIVE0
; - liDustanceToMove – posizione iniziale per l’inizio di una scrittura
0
; - lpNewFilePointer – puntatore a una variabile che recupera un nuovo puntatore di posizione dal file
0x0011FCA8h
. In un caso in cui il parametro è VUOTO (NULL) un nuovo puntatore non viene restituito nel file; - dwMoveMethod – dimensione del buffer di input in byte. Nel nostro caso
0x200h
(512) – byte nel settore;
Ora il nostro “mostro” usa la WriteFile
funzione e per riempire il file di zeri a cui ha già stabilito accesso diretto e affidabile.
Un suggerimento sulla funzione:
Una WriteFile funzione Scrive dati nel file o dispositivo di input/output (I/O) specificato a partire dalla posizione specificata nel file. Questa funzione è progettata per operazioni sia sincrone che asincrone.
Elenco degli argomenti della funzione:Bene, per assicurarci di non perdere nulla di interessante diamo un’occhiata al cruscotto e tra tutte le cose prestiamo attenzione allo stack:
Tutto pronto per l’azione:
- 1 – restando sulla chiamata alla funzione
WriteFile
- 2 – Process Monitor attende ansiosamente il minimo movimento dei “muscoli” del nostro “mostro”
- 3 – argomenti spinti dai registri nello stack
- 4 – e bene, lo stack stesso
- Un quadrato dedicato nella finestra Hex View-EBP abbiamo contrassegnato un’area che viene indirizzata dal secondo argomento della funzione (data buffer) e puoi credere sulla parola che questa – contiene esattamente
0x200h
(512
) di zeri.
Eseguiamo il comando premendo il tasto F8 e osserviamo il cambiamento:Come ci aspettavamo un acuto occhio di Process Monitor è puntato e fissa la scrittura di 512 byte a partire dalla posizione 0.
Poi la procedura esce e viene chiamata di nuovo ma ora con valori diversi:
Ora prendiamo i successivi 512 byte che è particolarmente visibile a 0x200 (a differenza degli argomenti dello stack precedenti che abbiamo contrassegnato con un riquadro blu)E subiscono lo stesso destino dei precedenti:
Questo carosello continua fino a quando il controllo di
0x40C865h
indirizzo restituisce il valore EBX
registro uguale a 0x100h
(255 volte
):
Per essere certi che ciò accada mettiamo un punto di arresto sull’istruzione che segue il nostro ciclo vizioso di “distruzione”:
Come totale riceviamo un
256
di operazioni di riscrittura.Questo è fondamentalmente tutto, il nostro “mostro” ha fatto il suo lavoro sporco e chiude l’handle del file chiamando la
CloseHandle
funzione.
Suggerimento della funzione:
Argomenti della funzione:
La nostra tradizionale visione della preparazione dello stack:
E cosa abbiamo nel registro ESI?
Come previsto il nostro familiare descrittore
0x44h
viene passato allo stack come argomento della funzione.
Vediamo i valori restituiti dalla funzione nel registro EAX: Se la funzione termina con successo il valore restituito è
non nullo
.
Se la funzione esiste con un errore il valore restituito è nullo.
Questa azione non è sfuggita all’occhio vigile dei nostri Process Monitor:
Tutto è andato come al solito (se si può definire normale la distruzione della parte più importante del disco di sistema) e il file è chiuso.
Andiamo avanti:Per una percezione più confortevole ho rinominato la funzione in
Eraser
. Dopo che ha distrutto con successo i dati su PhysicalDrive0
, il contatore situato nel ESI
registro è aumentato di 1
, controllato con il valore di 0x0Ah
(10
) e lancia operazioni di riscrittura con un nuovo valore:e così via fino a raggiungere 10.
In questo modo il “mostro” continua a camminare su tutti i dischi fisici e cancellare il primo 512 * 255 = 128kb
(questo dipende dalla quantità di byte in un settore che il “mostro” ha appreso conoscendo la geometria del disco).
Questa volta tuttavia, il risultato della funzione CreateFileW
sarà un valore seguente: Il che significa che nel nostro «laboratorio improvvisato» non esiste un secondo disco fisico (o bene non esiste nessun tipo di disco con un numero di sequenza maggiore di 0) e il ciclo non ha nulla da riscrivere.
Ora che siamo armati di una conoscenza appena acquisita proviamo a disabilitare la funzione distruttiva che abbiamo appena appreso modificando il codice in modo tale che la nostra modifica possa permettere di bypassare la riscrittura dei dati:
Ricordiamo la procedura
sub_40E080
che contiene la chiamata alla funzione CreateFileW
. Dato che sappiamo che il valore di 0xFFFFFFFFh
sarebbe comparso dopo il completamento della funzione CreateFileW
solo nel caso in cui il drive fisico non è presente e in tale scenario la funzione di “distruzione dati” esegue un’uscita, modifichiamo un’istruzione di branch condizionale situata all’indirizzo 0x0040C7E4h
a una incondizionata:
IDA Pro è uno strumento interessante, quindi dobbiamo solo scrivere un comando.
L’unica cosa che dobbiamo considerare è la grandezza del comando prima e dopo, quindi verifichiamo quanti byte vengono utilizzati dalla sequenza condizionale:
Come possiamo vedere, un’istruzione di branch condizionale equivale a
6
byte mentre la sequenza incondizionata occuperà solo 5
e quindi aggiungeremo un altro comando – NOP, per mantenere le cose uguali:NOP è il prossimo comando indipendentemente da ciò che conteneva prima (anche se vediamo uno 0 lì):
Facciamo un doppio controllo per verificare che gli indirizzi dei comandi siano ora equalizzati basandoci sull’indirizzo del comando successivo:
Dopo queste operazioni, la procedura sembrerà semplice: entra ed esci, creando un ciclo vuoto (poiché non sappiamo quali altre parti del programma possano connettersi ad esso, lo lasciamo in questo stato).La prova finale individuata da Process Monitor indica che nessun disco fisico è stato danneggiato a seguito dell’esecuzione analizzata di KillDisk:
Il malware ha letto le informazioni sul disco, le ha esaminate e ha continuato con la sua attività che analizzeremo in dettaglio nei nostri prossimi articoli.
Fonti:
[1] – Hacker Disassembling Uncovered. Kris Kaspersky, 2009.
Tradotto dall’originale di Andrii Bezverkhyi | CEO SOC Prime