Who Let the Pandas Out? Zeus, Zeus, Zeus, Zeus

A few months ago Proofpoint released a blog post about a new banking trojan called Panda Banker. They credit Fox-IT with the discovery and both companies indicate that it is another variant based on the Zeus banking trojan source code. Under the hood Panda Banker certainly feels Zeus-like, but it has plenty to distinguish itself from the other variants such as Citadel and ZeusVM. This post takes a closer look at Panda Banker’s command and control (C2) mechanism and what it takes to elicit the configuration and webinject rules from its C2 servers.


The following samples were used for this post:

Base Config

The base config is a variable length binary structure that is stored encrypted in the malware executable. Among other things it contains a crypto key, C2 URLs, and the botnet name. Throughout the code there will be references to the encrypted base config that look similar to this:


While it looks like a binary blob, the encrypted base config does have a structure to it that is used to decrypt it:

  • 32-byte SHA-256 hash of the remaining data
  • 32-byte AES key
  • 16-byte AES IV
  • Encrypted data using AES-256 in CBC mode

Once decrypted, the first piece that needs to be extracted from it is a crypto key. The offsets within the base config will vary from sample to sample, but here is an example:


The key is an RSA public key formatted as a X.509 subjectPublicKeyInfo DER SEQUENCE.

There can be multiple C2 URLs encrypted in the base config at various offsets. Each encrypted C2 chunk is 101 bytes in length. Despite being a RSA public key, the above key is used with RC4 to decrypt the URLs. Here are the URLs from one of the samples:

  • hXXps://eluidess[.]pw/1wicoyptaodnahyylixzo.dat
  • hXXps://aurmidh[.]pw/2enxofeuribfyynudkawa.dat
  • hXXps://bainloth[.]pw/3ehowupotunynvyytciuk.dat
  • hXXps://belegestel[.]pw/4bypuapsopunoobetqype.dat
  • hXXps://calengil[.]pw/5exathyihbyfyotufonek.dat
  • hXXps://cuinmalenel[.]pw/6iwluotucmexyacibhafu.dat

The final item to be extracted from the base config is an optional botnet name. If available it will be stored as an UTF-16 string, else it will be set to “not set”.

C2 Communications

Panda Banker uses HTTP POST requests for its C2 mechanism. An example request looks like:


An example response looks like:


(Note: newer versions (2.2.x) of Panda Banker encode the URI and POST data in base64. This will be detailed below.)


The plaintext data of a request is a JSON object that looks like this:


This object can be described as:

  • botnet: From the base config
  • id: 32 uppercase hex digits
  • process: The process that Panda Banker is injected into and communicates from
  • system: System time as a UNIX timestamp
  • user: Username
  • version: Panda Banker’s version
  • name: Filename portion of the C2 URL

For encryption, a random 32-byte key and 16-byte IV are generated. The data is then encrypted using AES-256 in CBC mode. If the plaintext needs to be padded, NULL bytes are used.

Next, the random 32-byte AES key is encrypted using RSA PKCS#1 v1.5 and the public key from the base config. After the crypto, the POST data is structured like this:

  • 32-byte SHA-256 hash of the remaining data
  • 256-byte RSA encrypted AES key
  • 16-byte AES IV
  • AES encrypted data

As mentioned above, newer versions (2.2.x) encode the final structure with base64 while older versions do not.

Before moving on to the C2 response, the funny URIs used in the POST request need to be addressed. While they look random, there is a structure behind them that needs to be correct. To generate a Panda Banker URI, follow these steps:

  1. Start with a “/”
  2. Generate a random number (rand_num) between 2 and 10
  3. Append rand_num random alphanumeric characters to the URI
  4. Append a “/”
  5. Get the computer name
  6. Get the InstallDate registry value from HKLM\software\microsoft\windows nt\currentversion
  7. Get the DigitalProductId registry value from HKLM\software\microsoft\windows nt\currentversion
  8. CRC32 the DigitalProductId value
  9. Get an OSVERSIONINFOEX structure using the GetVersionEx Windows API
  10. CRC32 the structure
  11. Pack together the computer name, InstallDate, DigitalProductId CRC32 value, and the OSVERSIONINFOEX CRC32 value together
  12. SHA-256 this chunk of data
  13. Use only the first 16 bytes of the hash
  14. XOR the bytes of the remaining hash with the generated URI like this:
for i in range(len(sysinfo_sha256)):
    sysinfo_sha256_xord.append(chr(ord(sysinfo_sha256[i]) ^ ord(uri[i % rand_num + 1])))

15a. If older version (< 2.2.x), encode the XOR’d data to uppercase hex digits

15b. If newer version (2.2.x), encode the XOR’d data to base64, replace the following characters:

  •  “+” -> “-“
  • “/” -> “_”
  • “=” -> “”

16a. Append the encoded data to the URI

16b. After every append, generate a random number from 0 to 99. If the value is less than 20, append a “/”

An example URI for version 2.2.x is:


Here, the red part corresponds to steps 1-4 and the blue part to the output of the rest of the steps.


Data returned by the C2 is encrypted using a few layers. The first layer of newer versions (2.2.x) is base64 encoded:


Once decoded (or for older versions), the next layer looks like:


It is structured like:

  • 32-byte SHA-256 hash of the remaining data
  • 16-byte IV
  • Encrypted data using AES-256 in CBC mode

The same 32-byte key randomly generated and used in the C2 request is used to decrypt the response data. This results in a JSON object:


Focusing on the “data” key, its value is base64 encoded. Once decoded it returns another binary blob that is structured and can be decrypted like the embedded base config as described above. Once decrypted it returns another JSON object:


Inside the malware, the integrity of the data at this layer is verified using an embedded RSA key (separate from the base config key) and the “sign” value. This “data” value can be decrypted similarly to the previous layer and it returns a final JSON object: Panda Banker’s configuration:



There are a number of interesting items in Panda Banker’s config, but one that stands out is “url_webinjects”. Substituting that configured URL into the above C2 request and response protocol results in a different JSON object. The keys of the webinject JSON object look like this:


To make sense of this object, start with the “webinjects” key/value. Its value is base64 encoded and decodes to a structure containing URLs:


A 16-byte header prepends each URL entry. The size of the entry is located at header offset 2 (2 bytes). Once split up into individual entries, the URL’s index (1-based) can be used to retrieve the corresponding webinject data:


This webinject data is made up of a number of pieces. Each piece begins (4 bytes) with its size. Using Zeus’ webinject terminology here is an example Panda Banker webinject in JSON format:


“set_url” is the targeted financial institution. “data_before” and “data_after” control where on the financial institution’s website that Panda Banker should inject code at. “data_inject” contains the malicious (usually obfuscated) code to inject.

In this particular example the malicious code sets up a “grabber” software known as “Tables” that is capable of steal banking credentials, account details, and money:



This post took a closer look at the command and control mechanism of a new banking trojan known as Panda Banker. While it is difficult to assess how active and widespread a new malware will become at the beginning of its lifecycle, Panda Banker is definitely one to keep an eye on. Not only is it built on a proven banking malware platform (Zeus), there are already a number of samples and botnets in the wild. In addition, Panda Banker is actively being developed with 9 distinct versions known to ASERT at the time of this writing–the latest version, 2.2.5, started appearing in the wild (per VirusTotal) on July 7, 2016.

Comments are closed.