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:

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:


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:

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:

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:

- 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 è
0C0000000hche significaGENERIC_READ+GENERIC_WRITEoraccesso 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_WRITEoaltri 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
3significaOPEN_EXISTINGorapri 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 unNULLviene 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:


Uno sguardo nello stack proprio prima di chiamare questa funzione:
- hDevice – descrittore del dispositivo (riconosciamo un vecchio amico qui
\.PHYSICALDRIVE0); - dwIoControlCode – codice dell’operazione. Nel nostro caso il valore è
0x70000hche significaIOCTL_DISK_GET_DRIVE_GEOMETRYo 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
NULLlo è; - nInBufferSize – dimensione del buffer di input in byte pari a
0nel 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
0x0011FCA4habbiamo 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
0x0011FCA8habbiamo 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:

SetFilePointerExeWriteFile:

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:

- 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:
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)

0x40C865h indirizzo restituisce il valore EBX registro uguale a 0x100h (255 volte ):


256 di operazioni di riscrittura.
CloseHandle funzione.
Suggerimento della funzione:
Argomenti della funzione:


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:

Andiamo avanti:
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:
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.


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:

6 byte mentre la sequenza incondizionata occuperà solo 5 e quindi aggiungeremo un altro comando – NOP, per mantenere le cose uguali:

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).

Fonti:
[1] – Hacker Disassembling Uncovered. Kris Kaspersky, 2009.
Tradotto dall’originale di Andrii Bezverkhyi | CEO SOC Prime




