Paste your Google Webmaster Tools verification code here
Select Page

Being able to communicate with command and control servers without having to worry about running a sample of malware is a powerful and useful tool. Researchers can now download configuration files, additional modules, and in some cases additional payloads. As a follow-up to my previous post on DanaBot, in which I covered its command and control protocol, I’m going to review what it takes to re-implement DanaBot’s protocol. In this post, I’ll go into detail and describe the code I’ve written to communicate with DanaBot’s command and control servers.

Note: this post is written for those who may not have done much botnet research.

Choices

When reversing DanaBot and taking a closer look at the encryption mechanisms surrounding their communication protocol, I decided to re-implement the bot’s communication process in C.

Generally, I like to hack things together with Python, since using C can turn out to be a bit uglier.

I chose to do this because DanaBot:

  1. Uses Microsoft® key BLOBs for encryption and decryption
  2. Sends Microsoft key BLOBs to the command and control
  3. Uses Windows® VPS for their command and control

The following code segments are not for the faint of heart.

Defining Packet Structure

From my previous post, you can see that the first packet is the only packet with a special structure due to the key exchange that has to happen for further communication.

Figure 1: First two packets sent by the bot to the command and control

From the packet capture in Figure 1, we can deduce that there’s a packet header similar to the first three fields in the packet bodies’ structure. See the code block below for a more formal definition.

struct packet_header
{
	QWORD packetSize;
	QWORD randValue;
	QWORD headerChecksum;
};

Block 1: Packet header structure

When the bot communicates with the command and control servers, it will first send the header for the following packet. The bot will then check to make sure all 24 (0x18) bytes of the header were sent before continuing to send the rest of the packet body.

Figure 2: Send packet header

Figure 3: “Hello” packet body

Enumerating the fields of the packet as members of a struct is easier to visualize and maintain. Doing so with this packet structure yields the following C struct.

struct packet
{
	DWORD packetSize;
	QWORD randValue1;
	QWORD headerChecksum;
	DWORD campaignID;
	DWORD commandID;
	DWORD packetParams;
	DWORD randValue2;
	DWORD constant1;
	DWORD arch;
	DWORD winVer;
	DWORD commandArg;
	DWORD constant2;
	DWORD isAdmin;
	QWORD constant3;
	BYTE deLim1;
	BYTE botID[0x20];
	BYTE deLim2;
	BYTE checksum1[0x20];
	BYTE deLim3;
	BYTE checksum2[0x20];
};

Block 2: Packet body structure

Referencing figure 1, each packet body can be divided into two parts. The first part is the packet struct, while the second part is the AES key used to decrypt the struct. The packet body will contain a DWORD indicating the number of bytes used for AES padding. This is the field within the encrypted packet body that can be considered the delimiter between part one and part two of the packet

Generating an RSA Key Pair

Before building a packet to send to the command and control infrastructure, an RSA key pair needs to be generated for communications with the command and control server. I generated a 1024 bit private key using OpenSSL, and then imported the PEM as a char buffer. Taking a private key and deriving the associated public key is relatively simple using several API calls.

CryptStringToBinaryA

CryptDecodeObjectEx

CryptImportKey

CryptGetKeyParam

CryptExportKey

After deriving the key pair, the public key struct is AES encrypted before being passed to the command and control, and the private key is stored to decrypt data that will be received from the command and control servers.

Generating and Encrypting Each Packet with an AES Key

Each packet sent to DanaBot’s command and control encrypts the data with a different AES key. Our Algid used in CryptGenKey below is for AES (CALG_AES_256).

Figure 4: DanaBot AES CryptGenKey

Knowing this, we can implement a function that will generate an AES key every time it’s called and encrypt the data buffer being passed to it.

Figure 5: RSA Public Key pre AES Encryption

Figure 6: Encrypted RSA Public Key

Note that we also have to return the padding value used during the AES encryption function to inform the command and control how many bytes to disregard after decrypting our packet data.

Encrypting the Generated AES Key

The generated AES key from our previous step is now ready to be encrypted with DanaBot’s hardcoded RSA public key.

/*
char * pem = "-----BEGIN PUBLIC KEY-----"
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyJo2aXOQNP+KeAnWlpOiuMk5W"
"l1An5GorPHqEyFAlRyv6sEylQDjAuSLGsy2LCvKmuzx2AFQ+3IMfqFf3JacY1HmY"
"WuiL1V+R910TohM+6hnLnWx7JNbfzB3S7D1JC/WNUwlVv5NnIIX1i+zIW5BTanU1"
"yQ97xjvokjvZHCHe2wIDAQAB"
"-----END PUBLIC KEY-----";
*/

BYTE bRSAKeyBLOB[] =
{
0x06, 0x02, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x52, 0x53,
0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
0xDB, 0xDE, 0x21, 0x1C, 0xD9, 0x3B, 0x92, 0xE8, 0x3B, 0xC6,
0x7B, 0x0F, 0xC9, 0x35, 0x75, 0x6A, 0x53, 0x90, 0x5B, 0xC8,
0xEC, 0x8B, 0xF5, 0x85, 0x20, 0x67, 0x93, 0xBF, 0x55, 0x09,
0x53, 0x8D, 0xF5, 0x0B, 0x49, 0x3D, 0xEC, 0xD2, 0x1D, 0xCC,
0xDF, 0xD6, 0x24, 0x7B, 0x6C, 0x9D, 0xCB, 0x19, 0xEA, 0x3E,
0x13, 0xA2, 0x13, 0x5D, 0xF7, 0x91, 0x5F, 0xD5, 0x8B, 0xE8,
0x5A, 0x98, 0x79, 0xD4, 0x18, 0xA7, 0x25, 0xF7, 0x57, 0xA8,
0x1F, 0x83, 0xDC, 0x3E, 0x54, 0x00, 0x76, 0x3C, 0xBB, 0xA6,
0xF2, 0x0A, 0x8B, 0x2D, 0xB3, 0xC6, 0x22, 0xB9, 0xC0, 0x38,
0x40, 0xA5, 0x4C, 0xB0, 0xFA, 0x2B, 0x47, 0x25, 0x50, 0xC8,
0x84, 0x7A, 0x3C, 0x2B, 0x6A, 0xE4, 0x27, 0x50, 0x97, 0x56,
0x4E, 0x32, 0xAE, 0xE8, 0xA4, 0xA5, 0x75, 0x02, 0x9E, 0xE2,
0x3F, 0x0D, 0xE4, 0x5C, 0x9A, 0x8D, 0x26, 0xB2
};

Block 3: DanaBot’s Public RSA Key Struct

Storing the key as an array of bytes makes it easier to import the key struct for further use. Every AES key generated will be encrypted with the DanaBot public key. You can see this better in figures 7 and 8.

Figure 7: AES key BLOB in memory before encryption

Figure 8: RSA encrypted AES key struct (128 bytes)

Generating the BotID

We won’t mimic DanaBot’s method for generating the BotID. DanaBot will take the MD5 of various operating system artifacts to develop a unique ID for the infected host. This gives us an opportunity to mess with the threat actors; every time we run our check-in code, we can generate a random MD5. This populates DanaBot’s control panel with false statistics on their infection numbers.

LPBYTE GenerateBotID()
{
// We generate a different BOT ID everytime we run for funsies 
	LPBYTE pBotID = NULL;
	pBotID = (LPBYTE)calloc(1, MD5LEN);
	char buf[] = "0123456789ABCDEF";
	int i = 0;

	while (i < MD5LEN) 
	{
		pBotID[i] = buf[rand() % 16];
		i++;
	}

	return pBotID;
}

Block 4: Generate a random BotID per run

Figure 9: Bot operators seeing all their new infections (probably)

Constructing Packet Headers

Packet header construction is simple. We’re going to populate the members of our “packet_header” struct from block 1.

	ph->packetSize = size;
	ph->randValue = rand();
	ph->headerChecksum = ph->packetSize + ph->randValue;

Block 5: Populating packet header struct members

The size of the packet is based on the structures and BLOBs packed into a packet, so we know it ahead of time. The random value and checksum are assigned and calculated.

Constructing the Packet Body

Building the packet body is more complex due to the checksum calculations needed at the end of the packet. Additionally, we need to consider and implement multiple commands.

Figure 10: DanaBot commands

We will take the commands that the bot uses to tell the command and control what it needs next and implement them as an array.

// Commands: Hello, GetMain, GetModuleList, GetModules
const DWORD commands[] = { 0x12C, 0x12D, 0x12E, 0x12F };

Block 6: DanaBot commands

Additionally, we must select our campaign ID and respective campaign hash value before building our packet to send to the command and control server.

// Campaign IDs
const DWORD campaign_id[] = { 3, 4, 5, 6, 7, 8, 14, 15 };

// Campaign Hashes
const DWORD campaign_hashes[] =
{
	897056567, 645456234, 423676934,
	235791346, 765342789, 342768343,
	653345567, 655222455
};

Block 7: DanaBot Campaign ID’s and Hashes

The fields in the packet structure have been documented several times by multiple organizations. Upon filling out the packet struct, you can view it in memory, similar to Figure 11.

Figure 11: Constructed “Hello” packet

Depending on the type of packet we’re sending, we need to calculate a couple of checksums for the command and control to verify the authenticity of our packet. To further understand the last two fields of significance, checksum1 and checksum2, see the calculations in the next figure.

Figure 12: Verification of packet checksums

After the packet is successfully constructed, it is AES encrypted and ready to be sent to the command and control server.

Decrypting and Parsing Responses

Finally, our RECV function will parse and properly decrypt the response. We know that each packet will have a header associated with it, so we receive and verify the first 24 bytes.

iRecvd = recv(s, packet_header, HEADER_LENGTH, 0);
PacketSize = ParseHeader(packet_header);

Block 8: recv and parse the packet header

After extracting the header values from the response and verifying that our packet checksum is correct, we can then extract the size of the incoming packet from the header for our next recv. Once we receive the packet body, we can then pass the buffer for RSA decryption.

if (!CryptDecrypt(hKey, NULL, TRUE, 0x20, (pbData + 
   (dwEncryptedDataLen - dwRSALen)), &dwRSALen))
{
	printf("RSA CryptDecrypt: %08x\n", GetLastError());
        CryptDestroyKey(hKey);
        CryptReleaseContext(hProv, 0);
	return 0;
}

Block 9: RSA CryptDecrypt

Note that we are only interested in decrypting the RSA encrypted AES key. This is located at the end of the encrypted packet, so we work backwards here. The AES key struct sent back from the command and control server will be 44 bytes in length. We also know where the AES padding value is as in accordance with our RSA encrypted data.

if (!CryptDecrypt(hKeyAES, NULL, TRUE, 0, pAESEncData, 
    &dwEncryptedDataLen))
{
	printf("AES CryptDecrypt: %08x\n", GetLastError());
        CryptDestroyKey(hKeyAES);
	CryptReleaseContext(hProv, 0);
	return 0;
}

Block 10: AES BLOB calculations

The resulting AES buffer can then be passed to our AES decryption wrapper where we can extract the resulting command and control information we requested.

Figure 13: AES CryptDecrypt on the module list

We can now retrieve a list of modules/configuration files from the command and control server and make subsequent requests to retrieve modules by replacing checksum1 with its value from the module list. In a follow up to this post, module and config decryption will be covered along with their subsequent decompression.

Conclusions

Threat intelligence available through botnet monitoring allows researchers to stay competitive in the ever-evolving game of cat and mouse with threat actors. By monitoring the botnet for changes, we can adapt quickly and come up with additional solutions to the changes being implemented. Additionally, when the check-in mechanism used by a bot is similar to DanaBot’s, researchers can poison the metrics displayed in the control panel. This, in turn, would push the bot developer(s) to change up the command and control protocol for their client(s), assuming their clients don’t want to deal with excess noise and want accurate statistics on their infections. Putting stress on this cycle gives researchers even more leverage, since they have the potential to force changes from the developer(s) based on their clients’ needs.

Loader Hashes

64dfdbd906830b83c9216fe42c084e2e5c3949a8454f49d630a9c0af6bd78f77

7aabc39bea44479d5d383a81254a5d0834d12f75d3fb8dfb2fc95730e8915fcd

83cd186314c25148e5bef97a989bd5e04e90e7a51e69103837336a045fe820af

Command and Control IPs

195.123.246.209:443