Tuesday, March 15, 2011

TDSS:TDL-4 - Bootkit - 101 Approach - Part 1

DISCLAIMER: First off, all the information found here is for EDUCATIONAL PURPOSE ONLY. The author is not responsible for any misuse.

IMPORTANT: The intent of my posts is to share knowledge and grow together, so if you find something that you think is inaccurate, feel free to let me know and together we can prepare a better document and learn together. I am not a Hacker, Cracker or something similar, just a hot-blooded Security guy who wants to learn and share. So, if you want to start criticizing without proposing anything, I will just ignore those comments.

Audience: Not for Junior/Senior Malware Analyst but for starters.

Acknowledgments: I want to thank Phil Fuhrer who realized we were dealing with TDL-4 and who found the ROR procedure to encode part of MBR code.


Personally, every time I read a blog from AV guys, it is hard to understand since it is too technical, which is good for those who are working on a daily basis on Malware Analysis-related efforts, but what about the community who wants to start in this field? They will need to know the step-by-step process to start entering into this area and most important to got interest. That is the idea of these "101 Approach" Series... to learn from scratch!!!


I started analyzing the rootkit/bootkit known as TDSS. TDL-4, at the beginning I did not have any idea about the behavior of this threat so we start gathering information from the community and I end up with an excellent
article from Kaspersky explaining this new variant. When I started analyzing the aforementioned article, I realized the writers just got to the point (as expected) and skip too many details of the analysis.

The intent of this "101 Approach" series, is to document the "gray areas" not described in those technical articles, as well as mention the new variants with the Malware I got in my Lab so that, if you are a beginner in this field and not a technical guru, you still will be able to understand/follow the internals of this bootkit.


In this Part 1, I will analyze only the bookit portion of the Malware

Master Boot Record Infection (MBR):

As mentioned by Kaspersky, this new variant infects the MBR, and here is the first "gray area" that need more explanation. In order to understand how the MBR is infected, you first must understand how MBR works. An Examination of the Standard MBR is an old but excellent article where you can understand the way MBR works as well as the assembly code associated.

When a BIOS-based (Basic Input Output System) computer boots, the first code it executes is called the BIOS, which is encoded into the computer's ROM. The BIOS selects a boot device, reads the device' MBR into memory (see point 3 below), and transfers control to the code in the MBR.

For the purpose of this analysis, these are the key points to keep in mind about MBR:

  • The MBR is assembly code stored at the first sector of the hard disk (offset 0000).
  • A sector contains 512 bytes and therefore this is the size of MBR code.
  • MBR is used to load the active partition which contains the boot instructions to load the Operating System.
  • The BIOS will load the MBR block (512 bytes) into memory at offset 7C00.
  • The BIOS transfers execution to MBR after loading it into memory.

Analyzing MBR code:
Get the first 512 bytes of the infected hard drive and open it with IDA Pro by choosing:

Varios Files-> Binary/Raw File option

And very important, when IDA Pro asks "Do you want to disassemble it as 32 bit code?"
Click "NO" since MBR code is 16 bit syntax (bp instead of ebp, sp instead of esp, so on).

Another point to consider, once the code is loaded into IDA, go to the first assembly instruction and hit letter "C" (to set the Entry Point) so that IDA can analyze the code and create the proper assembly operands, otherwise you will see cod
e that makes no sense. This needs to be done every time you need IDA to interpret a piece of code being analyzed.

The image below shows the extract
of the first instructions of MBR via IDA Pro. By reviewing these lines of code, we realized, the bootkit did not change its initial behavior.

What this code does is to copy MBR code itself from memory location 7c00h to 600h. This is done since later on MBR code will load the Boot Sector of the Active Partition in to the same area of memory (7C00h) that it was first loaded into.

Basically, the code uses movsb instruction to move one single byte at a time, from SI = 7C00h to DI= 600h, so in order to move 512 bytes, CX register is used as the counter 200h=512d and the REP (repeat) instruction to loop and copy the whole sector into the new location.

Decrypting MBR code:

By looking at the next instructions within the MBR sector, we confirmed the bootkit was using a basic ROR (Rotate right) method to obfuscate/encrypt some code as explained by Kaspersky blog, so let's analyze how this obfuscation works and the address space affected. Below is the chunk of code that implements the encryption process:

Before starting our analysis, make a note of the instructions started at offset 2A, those are currently encrypted, you will see the difference below once those are decrypted, so far, those instructions make no sense, right?

001E: CX is set to 132h, this number contains the chunk size of memory to be decrypted and the key used for decryption. I will explain this in detail below.

0021: BP is set to 62Ah, this is the offset in memory where the ROR will start decrypting the data. If you remember, MBR copied itself to 600h which means, 2A bytes below is where the encrypted code is located which is right after the loop instruction.

0024: ROR instruction will be executed ag
ainst the data located at 62Ah with the key located at CL register, in the first loop it will be 32. Next loop will decrypt data at 62Ah + 1 with CL=31 and so on.

0028: Loop until CL becomes 0 and therefore the loop ends (loop instruction decrements CX by default).

*NOTE: In order to avoid "Pattern recognition", Malware creators change the registers and or keys of the malicious code. If you compare the decryption routine described by Kaspersky guys and mine you will notice one change, the decryption key/size is different:

Kaspersky = CX = 137 h
Mine = CX = 132h
With this small change, all the encrypted code looks totally different.

You could try to decrypt the whole chunk by applying the ROR in every single byte that is tedious and prone to errors so I took the opportunity and created a simple Perl script (Regalado-ROR.pl) to do the ROR decryption for us.
Basically what my script expects as parameters are:

  • The image/raw file being analyzed
  • The CX value (in this case 132h)
  • The BP value (offset to start decrypting).

Regalado-ROR.pl will create and output file called "testiculo.bin" (don't ask me what this means in Spanish LoL) with the chunk of code decrypted, then you can open this file via IDA Pro to keep analyzing further instructions.

So, the way I executed the script was:

./Regalado-ROR.pl -f MBR-dump.raw -k 132 -o 2A

Outputfile = testiculo.bin

The image below shows the new raw file created by Regalado-ROR.pl with the decrypted instructions started at offset 2A which NOW makes more sense, you can start seeing "int 13h" (explain letter) and the ldr16 string mentioned by Kasp
ersky, so that means we are heading to the right direction!!

The Regalado-ROR.pl script is shown below, for you to use it in new variants of TDL-4.


#Regalado-ROR.pl implementation to unencrypt MBR
#Author: Daniel Regalado aka Danux Mitnick from Neza to the World!!!!
#Email: danuxx at gmail.com
#Date: 03/11/2011 - 3:36 AM
use Getopt::Std;
if (!defined ($args{'k'}) or !defined ($args{'f'}) or !defined ($args{'o'}) ){
print "\n\tUsage: Regalado-ROR.pl -f -k -o \n" ;
my @main; #Array which contains the raw encrypted chunk
my $file = $args{'f'};
load_file(); #Let's load the raw file into @main array.
my @dec = @main; #Array which contains the raw decoded chunk
my $key = hex($args{'k'});
my $off = hex($args{'o'});
my $i =0;
for (1 .. $key){
#obteniendo solo los primeros 8 bits de la llave
my $cl = $key & 0b11111111;
#print "CL => $cl \n";
$key --;
#print "Raw byte=> $main[$off] \n";
$dec[$off] = &ror(hex($main[$off]), $cl) ;
write_file(@dec); #Write decoded chunk to be analyzed by IDA Pro

sub ror {
# Usage: &ror(number, n)
# Rotate 'number' by 'n' bits right
my $number = shift;
my $bits2rotate = shift;
for (1..$bits2rotate) {
# Get right-most bit
my $rmb = $number & 0b00000001;
#print "rmb = $rmb\n";
# Shift right 1 bit
$number = $number >> 1;
#print "number = $number\n";
# Set left-most bit if the right-most bit of the number was == 1
if ($rmb == 1) {
$number = $number | 0b10000000;
return sprintf("%02x",$number);
#Imprime el arreglo que contiene el raw file en el file system.
#Param 1: Arreglo que contiene el raw file
sub write_file(){

my @final = @_;
open (FILE2,">testiculo.bin") or die $!;
for my $n (0 .. $#final){
print FILE2 sprintf("%c", hex($final[$n]) );
close (FILE2);
sub load_file(){
open(FILE, "<$file") or die $!;
my $char = "";
my $i =0;
while (1) {
$char = getc(FILE);
$main[$i] = sprintf("%02x", ord($char));
if (eof(FILE)){
$fin = $i;#Saving the length of the file.
$i +=1;
#END of Script

Understanding decrypted code

Next step is to understand what the decrypted code is going to do, as per Kasperky blog: "
The main function of the MBR loader, which is small in size, is to search the rootkit’s encrypted partition for the ldr16 component, load it into RAM and pass control to it."
But again, let's analyze this "gray area" since there are many internal steps before taking above conclusion.

The main feature to understand here is the interruption 13h or INT 13 which basically uses different functions to read, write, lock, unlock, eject, etc, hard disks and removable media.
There are two types of int 13h: 1. The legacy INT 13
  • Which was designed in the early 1980's.
  • The maximum theoretical capacity (disk size) of this API is 8.4 GB.
  • Uses function numbers 1-15h and Cylinder-Head-Sector (CHS) oriented.
The Extended INT 13 interface
  • Replace CHS addressing with Logical Block Addressing (LBA).
  • Give the BIOS better control over how this data is used.
  • Uses functions numbers 41h-48h.
  • It uses a data structure called "Disk Address Packet".
We will concentrate our analysis in the second option, the Extended INT 13 interface since our Malware uses the options 42h and 48h to commit its malicious actions.

The article "Enhanced Disk Drive Specification (EDDS)" details all the futures of this interface, as well as the "Disk Address Packet" structure mentioned above. Moving forward, let's analyze the first chunk of the decrypted data, basically, you will notice that the INT 13 is being called with the extension passed via AH register, in this case 48h, which means, Get the Drive Parameters. See image below:

Extension 48h - Get Driver parameters:003C -

AH is set to the extension number required, in this case 48h.

:003E - Offset in memory to store the result buffer. The result buffer is were all the parameters of the hard disk will be stored and it is explained in detail in the EDDS article.
:0041 - Maximum buffer size: Located at offset 0 (86Fh).

As explained above, the result buffer will be loaded starting at memory offset 86Fh. Sixteen bytes ahead of this offset (at 87Fh) is located the 8-byte value of the number of sectors of the disk, by reviewing the memory dump of the affected system, we confirm that this value is: 9502F90, as shown below:

But, wait a second, why does the Malware need the hard disk parameters?
Response: The total number of sectors of the hard disk is going to be a parameter used by the INT 13 extension 42h to know the last sector to be loaded in memory! See next section for more details.

So, let's analyze the INT 13 with the extension 42h, which means, load data from disk into memory.

Decryption Process - Function sub_76 Extension 42h - Load disk data into memory

The function sub_76 (name represented by IDA) contains the logic to the decrypt the sectors stored in the hard disk, as an overview, the main steps of this function are shown below:

  • Load next sector from disk to memory.
  • Decrypt the sector loaded (via XOR function).
  • Copy the decrypted chunk to another section in memory.
  • Is this the last sector to be copied? if no, go to point 1.
  • If yes, jump to offset of the decrypted code in memory.
So, as mentioned above, the first step in this process is to load those sectors in memory as shown in the chunk below:

You will need to check the EDDS article to understand the parameters required for this extension.
Note: To help in your calculations, DS = 0.

Code explanation:

007C: Number of sectors to transfer. It is set at offset 2 of the Device packet driver which corresponds to offset 861h in memory.
0086: Address of Transfer buffer: Address where the sector will be loaded in memory: 88Dh.
008C - 009B - Starting logical block address - LBA: The sector from disk to be loaded. The 8-byte-syze address is copied from offset 87Fh to 867h in memory via push and pop instructions.

By looking at the memory dump we found this LBA value: 000009502F59 at offset 8, but that does not mean this is the only one, actually, my assumption is that this is the last one copied from disk to memory (we confirmed this below), keep in mind the Malware will load different sectors from disk to memory.
So, as explained above (see section 2.1), we confirmed that the last sector of the hard disk is the first one loaded in memory, in our case the value is: 9502F90 hex = 156250000 dec.

00A2: Every time a sector is going to be loaded in memory, the total number of sectors is reduced by EAX times (1, 2, 3 and so on), this way, some sectors from the disk (starting from the final one) are copied to memory in every loop.

00A7: The 4 Higher bits of the LBA address are subtracted by 1 in case there is negative number: The sbb instruction is used since we are dealing with 16 bit registers and therefore we need to take care of the borrow when a negative number is reached.

To better understand this, See below example:
AH = 876D
AL= 0000
If AL is subtracted by 1 it will generate a borrow.
AL = 0000 - 0001 = FFFF; Since this generates a borrow, AH needs to be subtracted by one
AH = 876D - 1 = 876C or sbb AH, 0

Final result : 876CFFFF - Got it????????

In our code shown in the figure above, the four higher bits start at offset 86B since it is in little-endian order.

00AF: - The int 13h will use a structure called "Device Address Packet" which its offset is at DS:SI in memory. Here we can see the offset is at 85Fh.

Quick note:
a) Number of bytes per sector of your hard disk: Can be found by looking at hard disk properties (you can use the linux command dmesg to get this info) in our case this value is 512 bytes per sector. b) Number of sectors per disk: via dmesg command or: total bytes of hard disk / 512.

"With LBA, instead of referring to a drives cylinder, head and sector number geometry in order to access or "address" it, each sector is assigned a unique "sector number". In essence, LBA is a means by which a drive is accessed by linearly addressing sector addresses, beginning at sector 1 of head 0, cylinder 0 as LBA 0, and proceeding on in sequence to the last physical sector on the drive, which, for instance, on a standard 540 Meg drive would be LBA 1,065,456." by http://www.dewassoc.com/kbase/hard_drives/lba.htm

Below is the memory dump showing the confirmation of the new values identified:

Which means, the data is being loaded at offset 88Dh in the memory space.

Something to keep in mind is that the LBA address found at memory was
09502F59 and the last sector is 09502F90, this could be the range copied (backwards) from disk to memory, we will confirm this later. Quick tip-Getting sectors with dd tool:

dd if=/dev/sdb bs=512 skip=156249944 count = 2 >out Where
156249944d = 09502F59h the block detected in memory
Decrypt the sector loaded (via XOR function)
Once the encrypted sectors has been loaded in memory, the funny part begins, which is the code related to the decryption process as shown below:

00C4: The code will zero out all the bytes starting at offset 75D and until 85C, why only FFh (255 bytes)? because the loop is calculated based on BL register which is 8-bytes length and therefore after FFh the next value is 100 (BH=01 , BL=00) which sets bl to 0 and the loop ends. From 00CC to 00ED the same range (75D - 85C) is set with different values based on multiple calculations.
Decryption loop:
In the first two instructions of below code we can see that since the last byte affected by previous code (see above) was at offset 85C, now, the next byte to be change is at offset 85D and then 85E, this shows how the bootkit is preparing the proper bytes to be used during decryption process.

The decryption loop starts at offset 0109, some calculations are performed to calculate the encryption key, which is going to be stored at register CL,
and finally at instruction 012B the XOR function is executed against the content where SI is pointing to which is (see above) the offset 88D where the first byte from disk was loaded. A sector of 512 bytes is going to be decrypted as expected, we can confirm this by checking the value of register DX which is the counter of this loop and is set to 200h (512 bytes) at the instruction 00FA.

Jumping to the decrypted code in Memory

all the sectors loaded from disk have been decrypted in memory, it is time to jump to the new instructions. In the code below we can see two important things:

1. 060 - 067: Decrypted code is being moved from SI (893h) to DI (calculated at runtime) CX number of times. Here is where decrypted code is being loaded into memory, this step happens every time one sector has been decrypted.

2. 069 - 071: The value at offset 891h in memory will call the decryption function if more sectors need to be decrypted, otherwise will jump to the decrypted code in memory.

Next Steps

The next step will be to create a perl script to decrypt the hard disk sectors and come up with the next set of instructions. Specifically, we will be analyzing how the INT 13 Hooking is being implemented!!!! and how the malicious kernel-level drivers are loaded before the OS starts.

Currently I am swamped with other stuff but I will try to dedicate some hours in the near future. Meanwhile, please share any thoughts.



  1. I really enjoyed this, very interesting. I'm somewhere between the beginner and junior analyst role. Will be bookmarking this for the upcoming posts :)

  2. Danux, this is a very cool post, looking forward for next articles..

  3. Thanks henthi and Elad, sure I will keep updating and adding new and fun stuff. Stay tuned!

  4. This comment has been removed by the author.

  5. Hey it's very good !

    I personally think this tutorial should be an independent website (or PDF, just not a blog entry), where the reader could open extra windows about explaining int 13h, etc... and maybe a screencast, as not so many people actually step through a bootkit, but anyway, it was good.

    keep up the good work ;)

  6. Totally agree with you Ange, I will consider the screencast next time for a better understanding.

    Thanks again.


Note: Only a member of this blog may post a comment.