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
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) else: return GetManyBytes(addr, n) def WriteMem(addr, buff): global Debug if Debug: DbgWrite(addr, buff) else: for i in buff: PatchByte(addr, ord(i)) addr = addr + 1 return 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%st%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%st%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 = "xffx15" + struct.pack("WriteMem(i.address - 6, Inst) return for i in range(0x004193DC, 0x004196F0, 4): PatchDword(i, DbgDword(i) ^ XORKey) if i == LoadLibException: continue x = XrefsTo(i) for j in x: addr = j.frm print addr if addr > ApiResolvRange and addr print "[] API Patch Subroutine Skipping... " continue 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 { *(_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] else: addr = GetDisasm(ea)[19:] if int(addr, 16) in procesed: continue 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.
Deobfuscated 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).
Decoding 'Exploit' Module
Debugging Symbols for 'Exploit' Module
'Exploit' Module in DLL Exports
Elevated 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:
Fake 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.
Fake 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.
Domain 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.
Yara rule
The following Yara rule can be used to identify this Qadars variant:
rule Qadars { strings: $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 } condition: $dga_function }