Communications of the Bolek Trojan

A few weeks ago CERT Polska released a short blog post introducing a new malware family now known as Bolek. PhishMe and Dr.Web have since added some additional insight into the family. Browsing through a memory dump of the malware, a Webinjects section sticks out. Webinjects usually imply banking malware, so it seems Bolek picks up where its predecessor, Carberp, leaves off. This post takes a closer look at its command and control (C2) mechanism and what it takes to elicit a configuration file from its C2 servers.

Samples

The sample used for reverse engineering is here. As the above analyses note, Bolek has a readily identifiable BotConfig JSON object that contains C2 servers and their ServerPub (C2 server’s public key which will be discussed below):

{

  "BotConfig": {

    "BotCommunity": "group_102",

    "FailPeriod": 600,

    "Hosts": [

      "mensabuxus[.]net",

      "ogrthuvwfdcfri5euwg[.]com",

      "ogrthuvfewfdcfri5euwg[.]com"

    ],

    "ServerPub": "44DCF35866EB4992264E809EDD001737C65E28BB4DAB8DC7DA5CFA7F1AA05619",

    "TaskPeriod": 600

  }

}

At the time of this research, the C2 servers were down (one of them was a sinkhole already), so a second sample was also used. Its BotConfig is:

{

  "BotConfig": {

    "BotCommunity": "EXE1",

    "FailPeriod": 600,

    "Hosts": [

      "91[.]215[.]154[.]155"

    ],

    "ServerPub": "17C94201E5BA1FB36A0EEE839B5F350DAA9FD6CAA1E8C6D500F113A9A825003F",

    "TaskPeriod": 600

  }

}

Command and Control Protocol

C2 communications are via HTTP POST requests. An example request looks like:

first_request

Here is its response:

first_response

Both the POST and response data contain a number of cryptographic elements that can be organized into the following structure:

post_data_fmt

The fields are:

  • 32-byte public key
  • 16-byte AES IV
  • 20-byte HMAC-SHA1 digest of the encrypted header and data
  • 512-byte AES encrypted header
  • AES encrypted data (length varies)
  • Random data (length varies)

The first request sent to the C2 server contains system information of the infected computer. It is wrapped up in a BotStatus JSON object (most of the key/value pairs are self-describing, but some could use further research):

{

"BotStatus": {

   "Antivirus": [],

   "BotCommunity": "group_102",

   "ComputerName": "JOHN-PC",

   "Domain": "",

   "FamilyID": "{39C4F8B7-9D7F-C831-D4A5-C9B8C9B01E3C}",

   "Firewall": [],

   "HostName": "John-PC",

   "IdleTime": "0",

   "InfectedByID": "{00000000-0000-0000-0000-000000000000}",

   "IntegrityLevel": "System",

   "Is64Bit": false,

   "KernelMode": false,

   "LocalUsers": [

     {

       "Groups": [

         {

           "Name": "HomeUsers"

         }

       ],

       "IsAdmin": false,

       "Username": "HomeGroupUser$"

     },

     {

       "Groups": [

         {

           "Name": "HelpLibraryUpdaters"

         },

         {

           "Name": "HomeUsers"

         },

         {

           "Name": "Administrators"

         }

       ],

       "IsAdmin": true,

       "Username": "John"

     }

   ],

   "Locale": "0409",

   "NetworkShares": [

     {

       "Comment": "",

       "Name": "JOHN-PC",

       "PlatformID": "500",

       "Shares": [

         {

           "Netname": "ADMIN$",

           "Remark": "Remote Admin",

           "Type": "2147483648"

         },

         {

           "Netname": "C$",

           "Remark": "Default share",

           "Type": "2147483648"

         },

         {

           "Netname": "IPC$",

           "Remark": "Remote IPC",

           "Type": "2147483651"

         },

         {

           "Netname": "Users",

           "Remark": "",

           "Type": "0"

         }

       ],

       "Type": "331779",

       "VersionMajor": "6",

       "VersionMinor": "1"

     }

   ],

   "OSInfectedCount": "1",

   "OSMajorVersion": "6",

   "OSMinorVersion": "1",

   "OSProductType": "1",

   "OSServicePack": "1",

   "Programs": [

     {

       "DisplayName": "Mozilla Firefox 21.0 (x86 en-US)",

       "DisplayVersion": "21.0",

       "InstallLocation": "C:\\Program Files\\Mozilla Firefox"

     },

     {

       "DisplayName": "WinSCP 5.5.1",

       "DisplayVersion": "5.5.1",

       "InstallLocation": "C:\\Program Files\\WinSCP\\"

     },

     {

        "DisplayName": "Java 7 Update 51",

       "DisplayVersion": "7.0.510",

       "InstallLocation": "C:\\Program Files\\Java\\jre7\\"

     }

   ],

   "StillLoader": false,

   "SystemTime": "130790246365655056",

   "Uloader32Hash": "0A0D933C0201612BE170234C872BE4872CDED4D1",

   "Uloader32Version": "16777472",

   "Uloader64Hash": "1DB2031C11403BD138D57C3AB5505E42C5DD3A10",

   "Uloader64Version": "16777472",

   "Uptime": "1966259856",

   "Version": "16777472"

},

"Filesystem": {

   "Files": [

     {

       "Hash": "7BCCAEC0A19E3B5AECC50498B62F87F0CAC40AEC",

       "LastWrite": "130790244399495344",

       "Path": "\\USER.ID",

       "Size": "16"

     }

   ],

   "Space": "33422848",

   "UsedSpace": "2560"

}

}

The system info JSON will then be Zlib compressed and a 512-byte header prepended to it. For the first request the header looks like this:

data_hdr

The fields are:

  • command1-3 are 4-byte commands
  • 4-byte length of the compressed data (padded to be 16-byte aligned)
  • 20-byte SHA1 hash digest of the uncompressed data
  • Another 32-byte public key
  • 16-byte ID (relict from the Carberp source code)
  • 8-byte timestamp when the header was created (FILETIME format)
  • 4-byte flag indicating the data is Zlib compressed
  • 20 bytes of zero
  • 4-byte command
  • 8-byte timestamp when the data was created (FILETIME format)
  • 384 bytes of zero

As the ServerPub from the BotConfig and the two (mypublic) public keys imply, Bolek uses public key cryptography. Instead of following the trend of some other banking malware and using RSA for its crypto needs, Bolek uses elliptic curve cryptography. It specifically uses Curve25519 as a key exchange mechanism between the infected hosts and their C2 server.

Two Curve25519 key pairs are created by the malware and the public keys are sent to the C2 server—one (mypublic) in the post_data_format structure and the other (mypublic2) in the data_header structure. Using the myprivate private key and the C2 server’s ServerPub public key, a 32-byte shared secret (mysecret) is generated. The first 16 bytes of mysecret are used as an AES-128 key to encrypt the 512-byte header. CBC mode is used so a 16-byte random value is generated for the IV—the IV is sent along in the post_data_format structure. The compressed data is AES encrypted with the same key, but using a different IV. Its IV comes from the last 16 bytes of the encrypted header.

To ensure the integrity of the encrypted data, HMAC-SHA1 is used with its key being the last 16 bytes of mysecret—the hash digest is also sent in the post_data_format structure.

Using pseudo-Python snippets the whole encryption process looks something like this:

compressed_data = zlib.compress(data)

mysecret = curve25519._curve25519.make_shared(keypair["myprivate"], ServerPub)

iv = self.gen_rand_bytes(16)
aes = AES.new(mysecret[:16], AES.MODE_CBC, iv)
encrypted_header = aes.encrypt(header)

new_iv = encrypted_header[-16:]
aes = AES.new(mysecret[:16], AES.MODE_CBC, new_iv)
encrypted_data = aes.encrypt(compressed_data)

hmac_sha1 = HMAC.new(mysecret[16:], digestmod=SHA)
hmac_sha1.update(encrypted_header+encrypted_data)

The response from the C2 server is similarly structured and encrypted as above however the second generated (mypublic2) Curve25519 key pair is used for key exchange. Using pseudo-Python snippets again the decryption process looks something like:

hispublic = response[:32]
mysecret2 = curve25519._curve25519.make_shared(keypair["myprivate2"], hispublic)

iv = response[32:32+16]
aes = AES.new(mysecret2[:16], AES.MODE_CBC, iv)
header = aes.decrypt(response[68:68+512])
data_len = struct.unpack("I", header[12:12+4])[0]

new_iv = response[564:564+16]
aes = AES.new(mysecret2[:16], AES.MODE_CBC, new_iv)
compressed_data = aes.decrypt(response[580:580+data_len])
data = zlib.decompress(compressed_data)

After all that work the plain text data of the first response is one zero byte.

The second exchange is a bit more fruitful though. Its request data is a single zero byte and its header has a few changes:

data_hdr2

The plaintext data of this response contains a Tasks JSON object that contains a list of tasks. Most tasks contain the following keys:

  • Command
  • Hash (SHA1)
  • Time
  • Path
  • Data

Data is base64 encoded and depending on the command contains either binary data or another JSON object. Here are the tasks being sent by this C2 server at the time of this research:

Command: UpdateFile

Hash: 145c9b79efd10718118ce5c58cf0af2618c9e39c

Time: 131097825775904000

Path: \JUPITER.32

Data: 278028 bytes of binary data. Contains a PE file starting at offset 524.

Command: UpdateFile

Hash: d85668e9ba963bb476f7b919d22bbf24bf993835

Time: 131097825775904000

Path: \JUPITER.64

Data: 323084 bytes of binary data. Contains a PE file starting at offset 524.

Command: UpdateFile

Hash: 33ba0092f1ceb4426142b23fddfc5c28f75a322d

Time: 131081361515904000

Path: \JUPITER.JSON

Data: JSON object:

{
"Filefilters": [
   {
     "Mask": "*wallet*",
     "MaxSize": 5000000
   },
   {
     "Mask": "*.key*",
     "MaxSize": 1000000
   },
   {
     "Mask": "*key*.dat*",
     "MaxSize": 1000000
   }
],
"Screencapfilters": [
   {
     "Count": 1,
     "Mask": "*bitcoin*"
   },
   {
     "Count": 1,
     "Mask": "*BSS*"
   },
   {
     "Count": 1,
     "Mask": "*Банк*"
   },
   {
     "Count": 1,
    "Mask": *ЗАО*
   },
   {
     "Count": 1,
     "Mask": *Клиент*
   },
   {
     "Count": 1,
     "Mask": "*eToken*"
   },
   {
     "Count": 1,
     "Mask": "*Remote Desktop*"
   },
   {
     "Count": 1,
     "Mask": *Бухгалтерия*
   },
   {
     "Count": 1,
     "Mask": "*iBank2*"
   },
   {
     "Count": 1,
     "Mask": "*ts.letok2.ru*"
   },
   {
     "Count": 1,
     "Mask": *Кассир*
   },
   {
     "Count": 1,
     "Mask": "*KASSA*"
   },
   {
     "Count": 1,
     "Mask": *Internet-Банкинг*
   },
   {
     "Count": 1,
     "Mask": *Банкинг*
   },
   {
     "Count": 1,
     "Mask": "*jp2launcher.exe*"
   }
],
"Webfilters": [
   "!*.facebook.com/*",
   "!*.skype.com/*",
   "!*plus.google.com/*",
   "!*plus.youtube.com/*",
   "!*.yandex.ru/*"
],
"Webinjects": [
   {
     "FLAG_CAPTURE_NOTPARSE": false,
     "FLAG_CONTEXT_CASE_INSENSITIVE": false,
     "FLAG_IS_CAPTURE": true,
     "FLAG_IS_INJECT": false,
     "FLAG_REQUEST_GET": true,
     "FLAG_REQUEST_POST": false,
     "FLAG_URL_CASE_INSENSITIVE": false,
     "Injects": [
       []
     ],
     "URL": "https://blockchainbdgpzk.onion/wallet/*format=json*"
   },
   {
     "FLAG_CAPTURE_NOTPARSE": false,
     "FLAG_CONTEXT_CASE_INSENSITIVE": false,
     "FLAG_IS_CAPTURE": true,
     "FLAG_IS_INJECT": false,
     "FLAG_REQUEST_GET": true,
     "FLAG_REQUEST_POST": false,
     "FLAG_URL_CASE_INSENSITIVE": false,
     "Injects": [
       []
     ],
     "URL": "https://blockchain.info/wallet/*format=json*"
   },
   {
     "FLAG_CAPTURE_NOTPARSE": false,
     "FLAG_CONTEXT_CASE_INSENSITIVE": false,
     "FLAG_IS_CAPTURE": false,
     "FLAG_IS_INJECT": true,
     "FLAG_REQUEST_GET": true,
     "FLAG_REQUEST_POST": true,
     "FLAG_URL_CASE_INSENSITIVE": false,
     "Injects": [
       {
         # hex decodes to <bod*>
         "Before": "3c626f642a3e",
         # hex decodes to <script type="text/javascript">\nalert("www.hh.com")\n</script>
         "Inject": "3c73637269707420747970653d22746578742f6a617661736372697074223e0a616c65727428227777772e68682e636f6d22290a3c2f7363726970743e"
       }
     ],
     "URL": "http://hh.com*"
   }
]
}

Command: UpdateInjects

Hash: af681805ebaa86e886d25c1a210b2900843c9040

Time: 131085075165904000

Data: JSON object:

{
"InjectConfig": [
   {
     "DllName": "JUPITER.32",
     "Modules": [
       "*"
     ]
   },
   {
     "DllName": "JUPITER.64",
     "Modules": [
       "*"
     ]
   }
]
}

The above JUPITER.* files contain the DLLs and rules for webinjects, screen captures, file filters, and web filters. The rule format is reminiscent of Zeus’ format.

Command: UpdateWormConfig

Hash: 82de50660a03c6871a66872d92e3cc7e8f1cce79

Time: 131085075165904000

Data: JSON object:

{
"WormConfig": {
   "NetworkEnabled": true,
   "USBEnabled": true
}
}

Command: UpdateBackconnectConfig

Hash: 58213d91d17a7569916a3d82831e670f191cc255

Time: 131085075165904000

Data: JSON object:

{
"BackconnectConfig": {
   "RetryPeriod": 300,
   "Servers": [
     {
       "Host": "91[.]215[.]154[.]155",
       "Port": 443
     }
   ]
}
}

Command: UpdateFile

Hash: 8babe4cec02e1107335b75f2e11ec402c00c70a9

Time: 131097825775904000

Path: \SKELETON.KEY

Data: 16 bytes of binary data:

00000000 1f f7 01 a6 c8 7a c2 d0 0f 72 73 74 a0 ee 03 d7 |…..z…rst….|

00000010

Command: UpdateCore

Hash: 0b04cb54f29a2b2eceb24258e6923573040e7307

Data: 470012 bytes of binary data.

Conclusion

This post has taken a closer look at the C2 mechanism of a new malware family known as Bolek. While the specific Bolek botnet analyzed in this post is currently focusing on Russian banks and Bitcoin related sites, the Bolek malware itself has the advanced functionality to become a powerful banking malware that is capable of targeting all sorts of financial institutions. With only a handful of C2s and samples available at the time of this writing though, it is too soon to make a judgment of how active and widespread this threat is or will become.

  • Posted in Bolek, Malware
  • Comments Off on Communications of the Bolek Trojan

Comments are closed.