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
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
Let us take a closer look at its contents:
Its insides contain one interesting system call
CreateFile and couple procedures
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:
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. .
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:
- 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
- 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
other 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
NULLvalue is used;
- dwCreationDistribution – is responsible for deciding actions when file does or does not exist, in our case a value of
open 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
- Upon success the function returns new file’s handler to
ЕАХAnd if function failed a
NULLis written into
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.
We got our handler so let us proceed to the next call,
Same as before we look inside the procedure and see another interesting call:
A look into stack right before calling this function:
- hDevice – device descriptor (we recognize a familiar guy here
- dwIoControlCode – operation code. In our case the value is
IOCTL_DISK_GET_DRIVE_GEOMETRYor 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
- nInBufferSize – size of the input buffer in bytes equal to
0in our case;
- lpOutBuffer – pointer to output buffer. Its type is set by dwIoControlCode parameter
- nOutBufferSize – output buffer size in bytes
- lpBytesReturned – a pointer to a variable that contains the amount of bytes that are written to output
- 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
Let’s interpret the answer:
- At the address of
0x0011FCA4hwe 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
0x0011FCA8hwe’ve got information on first physical disk geometry (
- amount of cylinders –
- media type –
0x0Ch(12) meaning fixed hard disk.
- tracks per cylinder –
- sectors per track –
- bytes per sector –
- amount of cylinders –
Now we refer to procedure three,
Inside the procedure two calls immediately attracted our attention, specifically
A hint about the function:
As per established tradition we look into the function call stack:
- hfile – our familiar device descriptor
- liDustanceToMove – initial position to for beginning a write
- 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:
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
- 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
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
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
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
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
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.
 – Hacker Disassembling Uncovered. Kris Kaspersky, 2009.
Translated from original by Andrii Bezverkhyi | CEO SOC Prime