The PhishLabs Blog

Dissecting the Qadars Banking Trojan

Posted by Raashid Bhat on Feb 22, '17

Qadars is a sophisticated and dangerous trojan used for crimeware-related activities including banking fraud and credential theft. Qadars targets users through exploit kits and is installed using Powershell Scripts. We have observed Qadars targeting multiple well-known banks in UK and Canada and is capable of stealing infected users' two-factor authentication codes and banking credentials through the deployment of webinjects. While not as well known or widespread as other Trojans, the operators have shown commitment to development of Qadars’ on-board evasion techniques and its advanced and adaptable privilege escalation module. This emphasis on persistence alongside the frequent shifts in both industry and geographic targeting indicate Qadars will remain a potent threat through 2017.

 List of webinject targets from a Qadars configuration file

In this technical blog post, we will analyze a Qadars binary file and provide code and a Yara rule to aid in the analysis and detection of this banking Trojan. First, we will examine Qadars’ methods of thwarting reverse engineering through the utilization of a dynamically resolved Import Address Table with obfuscated functions and strings. We then will detail the trojan’s behaviour and dynamically-generated command and control centers with which it communicates. The C2s are not utilized solely for the collection of stolen credentials. We have also observed them delivering a module to Qadars samples operating in a low privilege environment that employs social engineering to trick the user into allowing higher level access.

Import Address Table (IAT) and String Obfuscation

In its pure form, Qadars has built-in protection to make reverse engineering difficult, such as dynamic resolution of the Import Address Table (IAT) and obfuscation of the IAT functions and strings.  At the beginning of execution, it calls a subroutine responsible for resolving and concealing IAT entries.

It locates API entries using a well-known hashing method. For example, in the code depicted below, 9B102E2Dh corresponds to LoadLibraryA:

Resolving API Calls via Hashing Mechanism

Dynamic Link Libraries (DLLs) are loaded using LoadLibrary and API names are located by parsing the export address table as show in the trace file below:

Loading DLLs Using LoadLibrary Function

Furthermore, Qadars conceals an API function by XORing the address of an API call with a 4-byte XOR-Key.  Wherever there is a call to a particular API function, the original value is reverted back to its XOR-encoded value.

Decoding XOR-encoded API Call

In order to simplify the analysis, we can utilize one of two methods: create an IDA script to statically resolve the import addresses, or create an IDA script to rebuild the IAT. We will utilize the latter method.

Reconstructing Imports by Instruction Patching

In order to restore the imported function, we would need our instructions to specify CALL [APIPointer] instead of CALL <register>. However, patching an indirect call would not be allowed because the size of an indirect call is only 2 bytes, while the size of a referenced call is 6 bytes. We could accommodate these additional 4 bytes by NOP'ing the previous XOR operation which is used to retrieve the original value. In this manner, we could keep the offsets at their specified and original locations. The following code comparison (also known as diff) illustrates this concept:

Maintaining Memory Offsets by Inserting NOP Instructions

All resolved entries are stored in an array 748 bytes in size consisting of 187 total API calls.

Resolved API Function Calls

We will use the following script to XOR the API address array with the original global XOR key. This allows us to patch and relocate the instructions.

# Raashid Bhat
# (C) PhishLabs 2017
# IAT Patch Script Qadars Banking Trojan 

XORKey = 0x43B9A447 # 2017 v3
LoadLibException = 0x004196F0

ApiResolvRange = 0x00406150

ApiResolvRangeLen = 0x00409ACC - 0x00406150                 

from capstone import *
import struct
Debug = 1 

def ReadMem(addr, n):
    global Debug
    if Debug:
        return DbgRead(addr, n)
        return GetManyBytes(addr, n)
def WriteMem(addr, buff):
    global Debug
    if Debug:
        DbgWrite(addr, buff)
        for i in buff:
            PatchByte(addr, ord(i))
            addr = addr + 1

def PatchIndirectCall(MemAddr, Addrs, CallDst):
    Reg = ''
    md = Cs(CS_ARCH_X86, CS_MODE_32)
    for i in md.disasm(MemAddr, Addrs):
        print "0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str) 

        if i.mnemonic == 'xor' and Reg == '':
            print i.op_str[0:3]
            Reg = i.op_str[0:3]
        if i.mnemonic == 'call':
            if i.op_str == Reg: 

                print "0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str)
                print "Size = %d"  % (i.address - ( Addrs + 6))
                Inst = ReadMem(Addrs + 6, (i.address - ( Addrs + 6))) # read remaining instructions
                WriteMem( Addrs , '\x90' *  (i.address - ( Addrs) + 2)) # write NOPS
                WriteMem(Addrs, Inst)
                Inst = "\xff\x15" + struct.pack("<L", CallDst)
                WriteMem(i.address  - 6, Inst)

for i in range(0x004193DC, 0x004196F0, 4): 

    PatchDword(i, DbgDword(i)  ^ XORKey)  

    if i == LoadLibException:
x = XrefsTo(i)   

    for j in x:
        addr = j.frm
        print addr
        if addr > ApiResolvRange and addr < ApiResolvRange + ApiResolvRangeLen: # fail
            print "[] API Patch Subroutine Skipping... "
        print hex(j.frm)
        PatchIndirectCall(ReadMem(addr, 0x32), addr, i)

Script to Patch API Address Array

Patching Import Address Table

Upon opening this file in IDA, we are presented with an annotated Import Address Table:

Patched Import Address Table in IDA

Similarly, we can use an IDA script to deal with Qadars string obfuscation which is simply a XOR-based decoding algorithm in which each of the encoded strings has the following structure:

struct EncodedString
        DWORD len;
        char Encodedbuf[len]; // XOR encoded with a key

XORKEY = “4B57A7E012368BE9AA48” // found in sample 

    while ( v12 < v16 )
      *(_BYTE *)(v12++ + v13) ^= v15[v14];
      v14 = (v14 + 1) % v11;
    result = v13;

The code can be simply represented in Python as follows:

def DecodeString(Ea):
    XORBuff = "4B57A7E012368BE9AA48".decode("hex")  

    BuffLen = Dword(Ea)
    print "[] Buffer Len = %d " % BuffLen
    dst = ""

    for i in range(0, BuffLen):
        dst = dst + chr( (Byte(Ea + 4 + i) & 0xff) ^ ord(XORBuff[i % (10)]))
    print len(dst)
    j = 0
    for i in dst:
        PatchByte(Ea + j, ord(i))
        j = j + 1

We will use the following IDA Python script to help us with decoding all encoded strings present in Qadars:

# IDAPython String Decoder For Qadars
# Raashid Bhat
# (C) PhishLabs 2017 

import struct
procesed = []
def DecodeString(Ea):
    XORBuff = "4B57A7E012368BE9AA48".decode("hex") #xorkey   

    BuffLen = Dword(Ea)
    print "[] Buffer Len = %d " % BuffLen
    dst = ""

    for i in range(0, BuffLen):
        dst = dst + chr( (Byte(Ea + 4 + i) & 0xff) ^ ord(XORBuff[i % (10)]))
    print len(dst)
    j = 0
    for i in dst:
        PatchByte(Ea + j, ord(i))
        j = j + 1 

for i in CodeRefsTo(ScreenEA(),1):
    print hex(i)
    ea = PrevAddr(i)
    while "push    offset" not in GetDisasm(ea):
        ea = PrevAddr(ea)
    print GetDisasm(ea)[19:]
    if "asc_" in  GetDisasm(ea):
        addr = GetDisasm(ea)[19:].split(";")[0]
        addr = GetDisasm(ea)[19:]
    if int(addr, 16) in procesed:

    DecodeString(int(addr, 16))
    procesed.append(int(addr, 16)) 

for i in procesed:
   MakeStr(i, BADADDR)

Running this script on the sample decodes all strings and makes them visible in the Strings window.

Qadars Deobfuscated Strings.pngDeobfuscated Strings

Privilege Escalation / Social Engineering and Spoofing  Adobe Update

If Qadars is not presented with a specific set of privileges, it tries to contact and download a module from the command and control center. This module is then loaded in memory and an export, aptly named “Exploit” is invoked to complete the privilege escalation. Currently, a known vulnerability in how the Win32k.sys kernel-mode driver handles objects in memory is exploited for this purpose (CVE-2015-1701).

Qadar 'Exploit' Module.png
Decoding 'Exploit' Module

Debugging 'Exploit' 1.pngDebugging Symbols for 'Exploit' Module

Qadars 'Exploit' Module DLL.png

'Exploit' Module in DLL Exports

Qadars Privilege Escalation.pngElevated Permissions Following Invocation of 'Exploit' Module

If the  privilege escalation code does not work, Qadars attempts to socially engineer the victim with a fake Windows security update prompt. This executes code that allows Qadars to run with higher privileges using the “runas” verb:

Qadars Fake Windows Prompt.pngFake Windows Security Prompt

Upon execution of the malware, it loads a fake window with a progress bar masquerading as an Adobe Updater application to provide a sense of legitimacy.

Qadars Fake Adobe Update.pngFake Adobe Flash Update


Communication and DGA

Qadars locates the command and control center by generating a list of 200 domains using a combination of a time seed and some constants.  On February 1st, Qadars started using a new seed value 0xE1F1, replacing the previous seed, 0xE1F2.

Qadars Domain Generation 1.pngQadars Domain Generation 2.pngDomain Generation

Initially, two information packets are generated and concatenated. They consist of a chunk of information serialized in the following format: botid, version , operation type, etc. 

This information is packed together and fed to another subroutine which generates a MD5 hash of a 9-byte random string. This string will be used as an AES-128 encryption key which is then appended in the beginning of the encoded packet for command and control traffic decoding. 

Information is serialized in each entry in the following format:

struct InfoStructEntry
unsigned int len;
unsigned char Buffer[len];

The response is encrypted using AES-128 and the first 16 bytes consist of the MD5 hash of the command and control buffer. This hash is used for verification before processing.

Struct c2packet
BYTE MD5Hash[16];
BYTE []AESEncryptedBuffer;

After decryption, the base packet consists of metadata information which is used to determine the parameters and type of block to be processed. Multiple entries consist of either modules, updates, or a web inject file which is APLIB compressed.

Qadars Base Packet.png

Yara rule 

The following Yara rule can be used to identify this Qadars variant:

rule Qadars
        $dga_function = { 69 C9 93 B1 39 3E BE F1 E1 00 00 2B F1 81 E6 FF FF FF 7F B8 56 55 55 55 F7 EE 8B C2 C1 E8 1F 03 C2 8D 04 40 }


Topics: Threat Analysis, Threat Intelligence, Banking Trojan, Qadars


What's this all about?

The PhishLabs Blog is where we share our insights and thoughts on cybercrime and online fraud.

Upcoming Events