Dismantling KillDisk: reverse of the BlackEnergy destructive component
Let us skip the long introduction on BlackEnergy threat and go straight to studying the malware component called “ololo.exe” also known to the public as KillDisk. KillDisk is a module of BlackEnergy framework aimed at data destruction and creating havoc / distraction during the APT operations.
The main tools used in our analysis today are Process Monitor that is a part of SysInternals utilities by Mark Russinovich and IDA Pro Disassembler. All manipulations will be performed in virtual environment based on Windows XP operating system. We start with making a quick initial setup of test VM, power on the machine and create a snapshot called “Before infection”. Let us begin!
In order to keep track of all events related to our subject of study we launch Process Monitor and make sure it stays visible:
And that it tracks the needed process:
Next, we load the virus into IDA Pro Disassembler and see the following picture.
This is the WinMain function, i.e. the main function so we begin its analysis and see that the first (and only) thing it does will be procedure call named sub_40E070
:
So let us head straight to that procedure. Here we can find procedures responsible for extraction of file extensions into memory and the first procedure right after them that calls an interesting thingy called sub_40E080
:
Let us take a closer look at its contents:
Its insides contain one interesting system call CreateFile
and couple procedures sub_40C390
and sub_40C400
. So it is about time to take a snapshot of our virtual lab and continue our journey!
Let us examine function call CreateFile, set the BreakPoint on it and execute our study sample:
A little hint regarding this function:
CreateFile function creates or opens a file or I/O device. The most commonly used I/O devices are as follows: file, file stream, directory, physical disk, volume, console buffer (CONIN$ or CONOUT$), tape drive, communications resource, mailslot, and named pipe. The function returns a handle that can be used to access the file or device for various types of I/O depending on the file or device and the flags and attributes specified.
As we know most of compilers pass argumetns to a function through a stack and since we are dealing with a sample initially written in C, and according to C directives arguments are pushed onto the stack from right to left in a way that first function argument is pushed last onto the stack and ends up being the first element on it’s top. [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:
A look into the stack:
The last argument that got pushed into the stack (one on its top) is the first argument accepted by the function (according to syntax of CreateFile
function) and it contains file name in our case \\.\PHYSICALDRIVE0
, thus the target is first physical drive.
Now we analyze other arguments sent to the function by taking their values from the stack:
Some conclusions:
- lpFileName – pointer to ASCIIZ-string containing name (path) of the file being opened or created (as we did already find out);
- dwDesiredAccess – access type to the file: in our case the value is
0C0000000h
that meansGENERIC_READ+GENERIC_WRITE
orread-write access
; - DwShareMode – a mode that enables sharing of the files with different processes and this parameter can have different values in our case the value pf 00000003b translates into of
FILE_SHARE_READ+FILE_SHARE_WRITE
orother processes can open file for read/write
; - IpSecurityAttributes – pointer to SecurityAttributes structure (file winbase.h) that defines security settings related to kernel file object, if security is not set a
NULL
value is used; - dwCreationDistribution – is responsible for deciding actions when file does or does not exist, in our case a value of
3
meansOPEN_EXISTING
oropen file if it exists, and return an error if it does not exist
; - DwFlagsAndAttributes – flags and attributes; this parameter is used to set characteristics of created file and is ignored in read mode 0.
- hTemplateFile – parameter is used only when new file is created
0
. - Upon success the function returns new file’s handler to
ЕАХ
And if function failed aNULL
is written intoЕАХ
register.
Alright so after calling CreateFile
we get a non-zero value for EAX which means that it contains the handler of requested file e.g. \\.\PHYSICALDRIVE0
:
We got our handler so let us proceed to the next call, sub_40C390
:
Same as before we look inside the procedure and see another interesting call:
Function hint:
DeviceIoControl function sends a control code directly to a specified device driver, causing the corresponding device to perform the corresponding operation.
A look into stack right before calling this function:
Conclusions:
- hDevice – device descriptor (we recognize a familiar guy here
\\.\PHYSICALDRIVE0
); - dwIoControlCode – operation code. In our case the value is
0x70000h
that meansIOCTL_DISK_GET_DRIVE_GEOMETRY
or retrieval of the information regarding disks geometry (amount of cylinders, type of media, tracks on cylinder, sectors per track, bytes per sector); - lpInBuffer – buffer with input data. We don’t need any input data so
NULL
it is; - nInBufferSize – size of the input buffer in bytes equal to
0
in our case; - lpOutBuffer – pointer to output buffer. Its type is set by dwIoControlCode parameter
0x0011FCA8h
; - nOutBufferSize – output buffer size in bytes
0x18h
(24); - lpBytesReturned – a pointer to a variable that contains the amount of bytes that are written to output
0x0011FCA4h
; - lpOverlapped – pointer to a structure OVERLAPPED;
- As the call is processed we look into the buffer (at the address that was sent as argument to lpOutBuffer precisely
0x0011FCA8h
):
Let’s interpret the answer:
- At the address of
0x0011FCA4h
we have a return of the amount of bytes written into output buffer as intended by the function (marked with green) we got as many as we asked for 24 symbols. - At the address of
0x0011FCA8h
we’ve got information on first physical disk geometry (\\.\PHYSICALDRIVE0
):- amount of cylinders –
0x519h
(1305) - media type –
0x0Ch
(12) meaning fixed hard disk. - tracks per cylinder –
0x0FFh
(255) - sectors per track –
0x3Fh
(63) - bytes per sector –
0x200
(512)
- amount of cylinders –
Now we refer to procedure three, sub_40C400
:
Inside the procedure two calls immediately attracted our attention, specifically SetFilePointerEx
and WriteFile
:
A hint about the function:
As per established tradition we look into the function call stack:
- hfile – our familiar device descriptor
\\.\PHYSICALDRIVE0
; - liDustanceToMove – initial position to for beginning a write
0
; - lpNewFilePointer – pointer to a variable that retrieves a new position pointer from the file
0x0011FCA8h
. In a case when parameter is EMPTY (NULL) a new pointer is not returned in the file; - dwMoveMethod – size of input buffer in bytes. In our case
0x200h
(512) – bytes in sector;
Now our “beast” uses the WriteFile
function and to fill up file with zeros to which it already established direct and reliable access.
A function hint:
A WriteFile function Writes data to the specified file or input/output (I/O) device starting with the position specified in the file. This function is designed for both synchronous and asynchronous operation.
Function arguments list:
Alright, to make sure we do not miss anything interesting we take a look at the dashboard and among all things we pay attention to the stack:
All set for action:
- 1 – staying on function call
WriteFile
- 2 – Process Monitor is anxiously anticipating any slightest twitch of our “beast’s” muscles
- 3 – arguments pushed from registers to the stack
- 4 – and well, the stack itself
- A dedicated square in the Hex View-EBP window we marked an area that is being addressed by second function argument (data buffer) and you can take my word on this one – it contains exactly
0x200h
(512
) of zero’s.
We execute the command by pressing F8 key and observe the change:
As we expected a keen eye of Process Monitor is spot on and fixes the writing of 512 bytes beginning from position 0.
Then procedure exits and is called again but now with different values:
Now we take the next 512 bytes which is particularly visible at 0x200 (unlike the previous arguments stack that we marked by a blue frame)
And they get the same fate as previous ones:
This merry-go-round goes on until the check of 0x40C865h
address returns the value of EBX
register equal to 0x100h
(255 times
):
To make sure this happened we put a stop point on the instruction that follows our vicious cycle of “destruction”:
As a total we receive a 256
of re-write operations:
That’s basically it, our “beast” has done its dirty work and closes the file handler by calling the CloseHandle
function.
Function hint:
Function arguments:
Our traditional view of the stack preparation:
And what do we have in ESI register?
As expected our familiar descriptor 0x44h
is passed to stack as function argument.
We see values returned by the function in the EAX register:
If function ends with success the returned value non-null
.
If function exists with an error the returned value is null.
This action did not miss the watchful eye of our Process Monitor:
Everything worked out as normal (if you can call the destruction of the most important part of the system drive normal) ) and the file is closed.
Let us go on:
For a more comfortable perception I renamed function to Eraser
. After it successfully destroyed the data on PhysicalDrive0
, counter located in ESI
register is increased by 1
, checked with value of 0x0Ah
(10
) and launches re-write operation with a new value:
and so on until 10 is reached.
This way the beast keeps walking across all physical drives and erasing first 512 * 255 = 128kb
(this depends on the amount of bytes in a sector that “beast” learned from knowing disk’s geometry).
This time however, result of the function CreateFileW
will be a following value:
Which means that in our «improvised lab» there is no second physical disk (or well not any kind of disk with sequence number greater than 0) exists and the cycle has nothing to re-write.
Now that we are armed with a freshly gained knowledge let us try to disable the destructive function we just learned by modifying the code in a way that our modification can allow to bypass the re-writing of the data:
Let us remember procedure sub_40E080
that contains function call CreateFileW
. Since we know that value of 0xFFFFFFFFh
would appear after completion of function CreateFileW
only in the case when physical drive is not present and in such scenario the “data destruction” function performs an exit, let us change a conditional branch instruction located at the 0x0040C7E4h
address to an unconditional one:
IDA Pro is a cool instrument so we just have to write a command.
The only thing we need to consider is the command size before and after so let us check out how many bytes are used by conditional sequence:
As we can see a conditional branch instruction equals to 6
bytes while the unconditional sequence will only take 5
and thus we shall add another command – NOP, in order to keep things equal:
We NOP next command regardless of what it contained before (even though we see a 0 there):
We do a double-check that command addresses are now equalized based on address of the following command:
and press cancel. Done.
After these manipulations the procedure will look simple: enter and exit, making an empty loop (since we do not know what other parts of the program may hook into it we just leave it be in such state).
The final proof spotted by Process Monitor indicates that no physical drives were harmed in result of the dissected KillDisk’s execution:
Malware has read the information about the disk, studied it and continued on with its business that we will analyze thoroughly in our next articles.
Sources:
[1] – Hacker Disassembling Uncovered. Kris Kaspersky, 2009.
Translated from original by Andrii Bezverkhyi | CEO SOC Prime