Advanced Banload Analysis

From Colin Hardy
Jump to: navigation, search

TL;DR


Banking Trojan Downloaders (aka Banload) are malware downloaders commonly used to propagate Brazilian / Portuguese banking trojans. Analysts often refer to Banload as the actual malware, and this is likely because the banking trojans themselves are fairly generic but the term actually refers to the initial downloader portion of the malware as opposed to the actual trojan. The interesting part from a malware analysts view is actually inspecting this initial downloader and the challenge most face is actually getting the malware to execute in a virtualised environment. The malware is simply very analysis-aware. Here I share some techniques and code that I use in order to analyse a common Banload variant in order to extract the malicious C2 information for the purpose of protecting a network from the trojan it tries to download.

Source Code


The code discussed in this write-up is now hosted on my GitHub and see below for a super-quick walkthrough and example usage on YouTube.

Video


Watch this write-up in action and see below for a detailed walkthrough.

Delivery Method


Oftentimes Banload is delivered in the form of a zip file which contains a .jar. This could be a direct attachment to a malicious email campaign, but rather more commonly observed in the wild is the use of a shortened-URL contained within the body of the email which redirects to the malicious download. The jar file itself is packed full of anti-analysis features which usually mean it will fail to execute in most environments that are outside of a native Windows machine, with a Brazilian / Portuguese profile and with commonly installed software. Being written in Java one can inspect the raw source code quite easily, if the malware was able to execute you could extract the code from memory, but easier than that would be to parse the file using a decompiler such as JD-GUI.

Sample Analysis


Here I'll be analysing MD5: c81a371dba56832c931602e2a27804df which is a Jar file uploaded to VT in October 2016. This is a commonly used Banload variant and we can explore the raw source code using JD GUI. I prefer to copy and paste the output of JD GUI into Sublime for easier reading.

The first thing to note is the name of the class. This is very important as it's actually an 8-byte DES key (yes DES!) that is used to encrypt strings within the body of the code as part of its obfuscation technique. In this case the class name is wYPLHFSS so for ease of analysis save your file as wYPLHFSS.java.

A quick look atop the code, things look messy, off-putting and largely unreadable, unless you're this guy.

WYPLHFSS-2.jpg

Note as well, the name of the class is also stored as a variable in the private static string declarations in view. The variable name is 1642 characters in length, this is demonstrative of the obfuscation in play here, using lengthy variable names is a very easy way to put off analysts from delving just that little deeper. Fear not, things get more interesting.

Let's analyse the file for the most common strings, this will actually yield the most commonly called function and this function is the one we're very much interested in:

$ tr -c '[:alnum:]' '[\n*]' < wYPLHFSS.java | sort | uniq -c | sort -nr | head  -5

This should yield the top 5 results with this rather lengthy string in second place with 109 hits:

rsuuxxzwwQQEETTYYIIOLLJJHHFFDDAAXCCCBNN
...[truncated]...
QQWWRRTTUOOPPZCBBNaab

This variable name is actually the name of a function which takes one string argument. For ease of reference, we'll rename this function to cryptoFunc to make it easier to read and refer to. I recommend using Replace All to update this function name. The string argument is passed through a DES-based decryption routine and the function returns its value in plaintext to build various commands for onward execution.

An example of a string being passed as an argument (there are many throughout the code!) is on Line 155

String str = System.getenv(cryptoFunc("e865110db97a329f1d8b7f039027c40a34719b206a9703be"));

You'll notice that cryptoFunc depends on another function on Line 53 whose name begins QEETYYIIOOLLJJHFFFDDAAXXCCBBBNN. For ease, we'll rename this function to stringFunc.

  public static String stringFunc(int Nu)
  {
    byte[] newByte = { 100, 84, 45, 68, 56, 83, 47, 67, 66, 67, 47, 80, 75, 67, 83, 53, 80, 97, 100, 105, 110, 103, 85, 70, 69, 103, 105, 105, 106, 109, 109, 111, 111, 112, 112, 114, 114, 115, 115, 117, 117, 120, 120, 122, 122, 119 };
    return new String(newByte, Nu, 1);
  }

The new byte array can be converted into an ASCII string which reads:

dT-D8S/CBC/PKCS5PadingUFEgiijmmoopprrssuuxxzzw

This therefore gives clues as to the mode of encryption operation and the trailing letters indicate this character array is used as an alphabet. Actually it's used to build the parameters for the crypto routine, so for example passing the string DES as an argument on Line 98:

SecretKeyFactory UOOPKKHH..[truncated]..WEETTUUqqsttvzz = SecretKeyFactory.getInstance(stringFunc(3) + stringFunc(24) + stringFunc(5));

Where stringFunc(3), stringFunc(24) and stringFunc(5) translate to the string "DES" using the above alphabet.

If we extract the whole cryptoFunc and rename the function and it's variables, we end up with something a lot more readable:

  public static String cryptoFunc(String strInput, String key)
  throws Exception {
    byte[] newByte = byteFunc(strInput);
    Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    DESKeySpec desKey = new DESKeySpec(key.getBytes("UTF-8"));
    SecretKeyFactory secretKeyFact = SecretKeyFactory.getInstance("DES");
    SecretKey secretKey = secretKeyFact.generateSecret(desKey);
    IvParameterSpec ivParameter = new IvParameterSpec(key.getBytes("UTF-8"));
    cipher.init(2, secretKey, ivParameter);
    byte[] tmpByte = cipher.doFinal(newByte);
    return new String(tmpByte);
  }

So now we can extract this and any other function dependencies into our own code and develop a parser to examine any obfuscated strings on the fly.

Decrypting Strings


So, our aim then is to pull out all of the strings that are passed to the crypto function and evaluate their output to see what the bad guys are hiding from us. The pseudo-code for what we want to achieve is:

1. Parse the file and search for the DES key
2. Re-parse the file and search for any strings between quotes
3. Pass these strings into the cryptoFunc
4. Print the output to the console.

So you simply need to save the output from JD-GUI into a text file and pass the filename as an argument for the parser to examine:

Compile using:

$ javac decipher.java

Run using:

$ java decipher file.name

The code is as follows:

import java.io.*;
import java.util.regex.*;
import javax.crypto.*;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;

public class decipher {

  /*
    returns the byte output of the passed input string
  */
  private static byte[] byteFunc(String strInput) {
    byte[] myByte = new byte[strInput.length() / 2];
    for (int i = 0; i < myByte.length; i++) {
        String myStr = strInput.substring(2 * i, 2 * i + 2);
        int myInt = Integer.parseInt(myStr, 16);
        myByte[i] = ((byte) myInt);
    }
    return myByte;
  }

  /*
    return the decrypted output of the passed input string.
    this uses the 8-char key provided by via CLA.
  */
  public static String cryptoFunc(String strInput, String key)
  throws Exception {
    byte[] newByte = byteFunc(strInput);
    Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    DESKeySpec desKey = new DESKeySpec(key.getBytes("UTF-8"));
    SecretKeyFactory secretKeyFact = SecretKeyFactory.getInstance("DES");
    SecretKey secretKey = secretKeyFact.generateSecret(desKey);
    IvParameterSpec ivParameter = new IvParameterSpec(key.getBytes("UTF-8"));
    cipher.init(2, secretKey, ivParameter);
    byte[] tmpByte = cipher.doFinal(newByte);
    return new String(tmpByte);
  }

  /*
    parse the input file to extract the DES key.
  */
  public static String fileParse(File inFile, Pattern p) 
  throws FileNotFoundException {
    FileInputStream inputStream = new FileInputStream(inFile);
    BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
    String strLine;
    String match = "";

    try {
        while ((strLine = br.readLine()) != null) {
            Matcher m = p.matcher(strLine);
            while (m.find()) {
                match = m.group(1);
            }
        }
        br.close();
    } catch (Exception ex) {
        ex.printStackTrace(System.out);
    } 
    return match;
  }

  /*
    supply the patterns to use in the fileParse method to extract the key
  */
  public static String getKey(File inFile) 
  throws FileNotFoundException {
    String tmp = "";
    Pattern p = Pattern.compile("DESKeySpec\\((.*)\\.");
    String keyVar = fileParse(inFile, p);
    tmp = Pattern.quote(keyVar);
    Pattern q = Pattern.compile(tmp + " *?= *?\"(.*)\";");
    String key = fileParse(inFile, q);
    System.out.println(key);
    return key;
  }

  /*
    main
    searches the input file for strings between quotes.
    with each match, passes the output to the cryptofunc in order to decrypt
    using the 8-char key provided. 
    prints output to stdout.
  */
  public static void main(String[] args)
  throws Exception {
      // ensure correct usage
      File inFile = null; 
      if (args.length == 1) {
          try {
              inFile = new File(args[0]);
              if (!inFile.exists()) {
                  System.err.println("Error: the specified file " + inFile + " cannot be found.");
                  System.exit(1);
              }
          } catch (Exception ex) {
              ex.printStackTrace(System.out);
          }
      } else {
          System.err.println("Usage: java decipher <filename>");
          System.exit(1);
      }

      // get the key
      String key = getKey(inFile);
      
      /*
        search the input file for strings between quotes.
        pass each match to the decrypt function, assuming it meets simple DES-based requirements.
      */
      FileInputStream inputStream = new FileInputStream(inFile);
      BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
      String strLine;
      try {
        while ((strLine = br.readLine()) != null) {
          Pattern pat = Pattern.compile("\"([^\"]*)\"");
          Matcher mat = pat.matcher(strLine);
          while (mat.find()) {
            if ( (mat.group(1).length() % 8) != 0 || mat.group(1).length() < 9) {
              // skip over lines that don't meet the criteria. 
            } else {
              String output = cryptoFunc(mat.group(1), key);
              System.out.println(output);
            }
          }
        } 
        br.close();
      } catch (Exception ex) {
        ex.printStackTrace(System.out);
      } 

    }
  }

Show me the Money!


Running the above code using the malicious jar file as input will yield all the strings that are passed to the crypto function. This actually includes the malicious C2 information which the malware calls out to in order to download the actual trojan binaries:

ProgramFiles(X86)
ProgramFiles(X86)
\\GbPlugin\\
ProgramFiles
\\GbPlugin\\
.gpc
ProgramFiles(X86)
ProgramFiles(X86)
\\AppBrad\\
AppBrad|
ProgramFiles
\\AppBrad\\
AppBrad|
ProgramFiles(X86)
ProgramFiles(X86)
\\scpbrad\\
Brad|
ProgramFiles
\\scpbrad\\
Brad|
ABCDEFGHIJKLMNOPQRSTUVYWXZ1234567890qwertyuioplkjhgfdsazxcvbnm
ProgramFiles(X86)
br
brasil
pt
os.name
123
.vbs
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set colDrives = objFSO.Drives
Set objDrive = colDrives.item("C")
Wscript.Echo objDrive.SerialNumber
cscript //NoLogo
NO_DISK_ID
@echo off
RUNDLL32
 A0D876D1C0C3C7C7C9C8C6C4C7C6C6CD
Set WshShell = WScript.CreateObject("WScript.Shell")
Set lnk = WshShell.CreateShortcut("
")
lnk.TargetPath = "
"
lnk.HotKey = "ALT+CTRL+F"
lnk.WindowStyle = "1"
lnk.WorkingDirectory = "
"
lnk.Save
Set lnk = Nothing
.LNK
real
.vbs
cscript //NoLogo
Startup
real
.vbs
Set WshShell = WScript.CreateObject("WScript.Shell")
wscript.echo WshShell.SpecialFolders("
")
Set WSHShell = Nothing
cscript //NoLogo
POST
User-Agent
Mozilla/5.0
POST Response Code ::
POST request not worked
2B53A7EE127B81F609080B0A0C729AE2196FAEEE137B858CE31B65A7D92050=
&
43B5C73C4FB0C5CA3F494CA3D5374DB2D6296E82888BE1295EA6EB03090B61=
&
8EE1295D90F2157B9FD02357B6C83052B3D93D4EA1D4296BACD2256F80F70A=
&
0277888BE126699BFE758689FF077D8788FF09087B83848DF51F55AAEB2D52=
http://
/pdf/8CF2040D0F7697EC2C6CAD.php
#1:
#2:
#3:
shutdown -r -t 30 -c  Atualizando...
dos:hidden
real
.vbs
Dim oFSO
Set oFSO = CreateObject("Scripting.FileSystemObject")
oFSO.CreateFolder "
"
cscript //NoLogo
APPDATA
/Sun/Java/Deployment/Sun.jar
3
191.252.0.36
http
APPDATA
/
/
/
/Sun/Java/Deployment/
/
.mp3
.xml
.wmv
.bat
/Sun/Java/Deployment/Sun.jar
/pdf/pp64.pdf
/pdf/ss64.pdf
/pdf/jjgf.pdf
/pdf/pp32.pdf
/pdf/ss32.pdf
/pdf/jjgf.pdf

Some very interesting strings that are decrypted show the extent of the anti-analysis techniques being used. Firstly the malware looks for the presence of numerous pieces of Portuguese specific banking software G-Buster (referenced by .gpc in the above) and also scpbrad. Additionally, the malware also looks for the specific language settings of the machine, referenced by the strings br brasil and pt.

The key result though is the C2 details. This gives malware analysts the treasure they need to actually go and download the additional binaries for further examination. You can see here then the malware reaches out to the IP address 191.252.0.36 and POSTs content to

191.252.0.36/pdf/8CF2040D0F7697EC2C6CAD.php

The additional binaries are downloaded from the following paths:

/pdf/pp64.pdf
/pdf/ss64.pdf
/pdf/jjgf.pdf
/pdf/pp32.pdf
/pdf/ss32.pdf

The binaries are obviously not PDFs, the banload will rename these to DLLs and jjgf.pdf is actually a PE32 that is downloaded and executed by the initial script.

Summary


Hopefully you'll agree Banload is fun. I LOVE it when bad guys try to hide their code, it makes it all the more rewarding when you can decrypt their code on-the-fly and extract and takedown their malicious infrastructure. I hope you can use my decrypter to help parse DES-based encrypted banload samples, but don't stop there! There are other banload variants that use similar, but differing techniques however the same principles can be applied to extracting the decryption routines and parsing the encrypted strings. :)