Qakbot malware analysis
A detailed write-up for QakBot malware
QakBot in nutshell
QakBot, also known as QBot, QuackBot and Pinkslipbot, is a banking Trojan that has existed for over a decade. It was found in the wild in 2007 and since then it has been continually maintained and developed. Its main purpose is to steal banking credentials (e.g., logins, passwords, etc.)
Infection chain
QakBot is known to infect its victims mainly via spam campaigns. In some cases, the emails were delivered with Microsoft Office documents (Word, Excel) or password-protected archives with the documents attached. The documents contained macros and victims were prompted to open the attachments with claims that they contained important information (e.g., an invoice). In some cases, the emails contained links to web pages distributing malicious documents.
QakBot infection chain
First look
| SHA-1 | 93b1ab0a9e70a546c4b89dcb20a158dfc90b1421 |
| SHA-256 | 73e4969db4253f9aeb2cbc7462376fb7e26cc4bb5bd23b82e2af0eaaf5ae66a8 |
| File type | Win32 DLL |
| Creation Time | 1992-06-19 22:22:17 UTC |
| First Submission | 2021-12-24 13:06:18 UTC |
Stage-1
The malware imports WriteProcessMemory it seems to inject code in remote process.
After running , here we go , now we have explorer.exe process in suspend state with PE file injected into it , now we dumped the mapped second stage of QakBot.
Process hacker memory view with injected DLL in explorer.exe
After dumping the mapped version I’am going to unmap it with PE-unmapper.
Stage-2
Basic information
| SHA-1 | c5dca02ef2029b0144dc54bdce4889ed16c18126 |
| SHA-256 | 8E2D95CD9F2ED6D40CEB42D92BC9D623524103CF966B6190A8F096CA32AF50B6 |
| File type | Win32 Dll |
| Creation Time | 2021-12-09 09:22:27 |
Anti Analysis
Encrypted string tables
QakBot encoded its strings in two string tables wrapped by 4 functions , every table has its data and key blobs which handled by the encryption routine.
The 4 wrapper functions are used heavily in this sample so this script will handle the wrapper function and do the decryption routine then comment with the string with offset passed to wrapper function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import idaapi
import idautils
import idc
import binascii
import pefile
DATA_FILE = r"PATH"
DEFAULT_STR_SIZE = 0x850
DEFAULT_KEY_SIZE = 0x5A
def decrypter(data_string, data_key):
if not data_key:
raise ValueError("XOR key is empty")
decoded = ""
for i in range(len(data_string)):
decoded += chr(data_string[i] ^ data_key[i % len(data_key)])
return decoded
def string_decrypter_search(enc_data, key_data, offset):
out = []
for i in range(len(enc_data)):
out.append(enc_data[i] ^ key_data[i % len(key_data)])
result = bytes(out)[offset:].decode("latin", errors="ignore").split("\x00")[0]
return offset, result
def extract_data(filename):
pe = pefile.PE(filename)
for sec in pe.sections:
name = sec.Name.decode(errors="ignore").rstrip("\x00")
if name == ".data":
return sec.get_data()
raise RuntimeError(".data section not found")
def calc_offsets(seg_start, va):
return va - seg_start
def string_decrypter(enc_va, key_va, str_offset):
data_seg_start = None
for s in idautils.Segments():
if idc.get_segm_name(s) == ".data":
data_seg_start = idc.get_segm_start(s)
break
if data_seg_start is None:
raise RuntimeError(".data segment not found in IDA")
enc_off = calc_offsets(data_seg_start, enc_va)
key_off = calc_offsets(data_seg_start, key_va)
data = extract_data(DATA_FILE)
if enc_off < 0 or key_off < 0:
raise RuntimeError("Invalid offsets")
enc_blob = data[enc_off:]
if b"\x00\x00" in enc_blob:
enc_size = enc_blob.index(b"\x00\x00")
else:
enc_size = DEFAULT_STR_SIZE
enc_data = data[enc_off:enc_off + enc_size]
key_blob = data[key_off:]
if b"\x00\x00" in key_blob:
key_size = key_blob.index(b"\x00\x00")
else:
key_size = DEFAULT_KEY_SIZE
key_data = data[key_off:key_off + key_size]
if not enc_data or not key_data:
raise RuntimeError("Failed to extract encrypted data or key")
_, result = string_decrypter_search(enc_data, key_data, str_offset)
return f"string[{str_offset}]: {result}"
def comment_string_offset(enc_addr, key_addr, func_name):
func_ea = idc.get_name_ea_simple(func_name)
if func_ea == idc.BADADDR:
print(f"[!] Function {func_name} not found")
return
# Try to decompile function once
cfunc = None
if idaapi.init_hexrays_plugin():
try:
cfunc = idaapi.decompile(func_ea)
except Exception:
pass
for call_ea in idautils.CodeRefsTo(func_ea, 0):
prev_ea = idc.prev_head(call_ea)
args = []
for _ in range(5):
mnem = idc.print_insn_mnem(prev_ea)
if mnem == "push" and idc.get_operand_type(prev_ea, 0) == idc.o_imm:
args.insert(0, idc.get_operand_value(prev_ea, 0))
elif mnem == "mov" and idc.get_operand_type(prev_ea, 1) == idc.o_imm:
args.append(idc.get_operand_value(prev_ea, 1))
prev_ea = idc.prev_head(prev_ea)
for arg in args:
try:
decrypted = string_decrypter(enc_addr, key_addr, arg)
comment_text = decrypted.split(": ", 1)[1]
idc.set_cmt(call_ea, comment_text, 0)
if cfunc:
class CallVisitor(idaapi.ctree_visitor_t):
def __init__(self):
idaapi.ctree_visitor_t.__init__(self, idaapi.CV_FAST)
self.done = False
def visit_expr(self, expr):
if expr.op == idaapi.cot_call:
if expr.cexpr.op == idaapi.cot_obj and expr.cexpr.obj_ea == call_ea:
idaapi.set_citem_cmt(cfunc, expr, comment_text, 0)
self.done = True
return 0
v = CallVisitor()
v.apply_to(cfunc.body, None)
except Exception:
pass
print(string_decrypter(0x1001D5A8, 0x1001E3F8, 1486)) #string_table_1
print(string_decrypter(0x1001D0B0, 0x1001D050, 708)) #string_table_2
comment_string_offset(0x1001D5A8, 0x1001E3F8, "mw_w_decode_string_table_0")
comment_string_offset(0x1001D5A8, 0x1001E3F8, "mw_w_decode_string_table_1")
comment_string_offset(0x1001D0B0, 0x1001D050, "mw_w_decode_string_table_2")
comment_string_offset(0x1001D0B0, 0x1001D050, "mw_w_decode_string_table_3")
print("\n[+] Done")
Here we go the script did its magic and we have commented strings!
The result of the decryption script
Emulation check
QakBot uses the GetFileAttributesW function to check for a folder "C:\INTERNAL__empty" if directory exists then environment might be used for analysis, such as Microsoft Defender emulation or sandbox, and then the process will be terminated.
API hashing
QakBot used CRC32 hashing algorithm to hash all its IAT then XORED with value 0x218FE95B to obfuscate all its imports QakBot hash API function
Then used API resolving function to rebuild its IAT , Once using HashDB plugin we will have all resolved QakBot IAT. QakBot API resolving function
Checking process
QakBot searches for process to detect malware analysis machines.
Show process
tcpdump.exe
windump.exe
ethereal.exe
wireshark.exe
ettercap.exe
rtsniff.exe
packetcapture.exe
capturenet.exe
srvpost.exe
frida-winjector-helper-32.exe
frida-winjector-helper-64.exe
Anti VM
It uses WMI queries to gather system information, including details about virtualization. It queries classes such as Win32_ComputerSystem, Win32_Bios, Win32_DiskDrive , Win32_processorand Win32_PhysicalMemory then check for virtualized environments. QakBot getting processor information using WMI
Persistence
QakBot maintains persistence on the system through Scheduled Tasks or registry key SOFTWARE\Microsoft\Windows\CurrentVersion\Run to run after reboot. QakBot persistence function 1
Also it has other persistence function that create the same scheduled task and create process with regsvr.exe. QakBot persistence function 2
Code injection
QakBot uses process hollowing technique to inject itself in 32 or 64 bit process in the system This list contains the target system processes for injection
Show processes
%SystemRoot%\SysWOW64\AtBroker.exe
%SystemRoot%\System32\AtBroker.exe
%SystemRoot%\SysWOW64\xwizard.exe
%SystemRoot%\System32\xwizard.exe
%SystemRoot%\SysWOW64\explorer.exe
%SystemRoot%\explorer.exe
%SystemRoot%\SysWOW64\wermgr.exe
%SystemRoot%\System32\wermgr.exe
%SystemRoot%\SysWOW64\OneDriveSetup.exe
%SystemRoot%\System32\OneDriveSetup.exe
%SystemRoot%\SysWOW64\msra.exe
%SystemRoot%\System32\msra.exe
%SystemRoot%\SysWOW64\mobsync.exe
%SystemRoot%\System32\mobsync.exe
QakBot process hollowing function
Evasion
QakBot is concerned in manipulating the key SOFTWARE\Microsoft\Microsoft Antimalware\Exclusions\Paths to eventually include itself as an authorized anti-malware tool and stay out the radar. QakBot Evasion function
Windows command line
QakBot creates anonymous pipes to execute built-in command-line tools processes to retrieve information about the compromised system’s environment. Qakbot CMD tools function
Here is the list of commands used by QakBot to gather information about the system
| Command | Output |
|---|---|
| ipconfig /all | Displays detailed configuration information about all network interfaces. |
| whoami /all | Displays user, group, and privileges information for the current user |
| nltest /domain_trusts /all_trusts | Lists all domain trusts established with the current domain. |
| qwinsta | Lists information about all Remote Desktop sessions on the local system. |
| nslookup -querytype=ALL -timeout=12 _ldap._tcp.dc._msdcs.%s | Performs a DNS lookup for LDAP service records for the specified domain controller. |
| net share | Lists information about shared resources on the local system. |
| net localgroup | Lists information about local groups on the local system. |
| netstat -nao | Lists active network connections and associated processes |
| net view | Lists information about shared resources on remote systems. |
| route print | Displays the IP routing table for the local system. |
| arp -a | Displays the ARP cache, which contains mappings of IP addresses to MAC addresses. |
Gathering information
QakBOT communication with its command and control is sending information about the computer. QakBot gathers computer information using a combination of Windows API calls, shell commands, and Windows Management Instrumentation (WMI) commands. There is other functions that used other classes to get information and here the classes used by QakBot.
| Class | Properties | Result |
|---|---|---|
| Win32_OperatingSystem | Caption | OS Info [name and version] |
| AntiVirusProduct | * | Information about antivirus products installed on a system |
| Win32_Processor | * | Information about the processor |
| Win32_ComputerSystem | * | Information about the computer system, including its hardware configuration, such as the manufacturer, model, system type, number of processors, memory |
| Win32_Bios | * | Details about a computer’s BIOS, like its version, manufacturer, and release date |
| Win32_DiskDrive | * | Information about the disk drives installed on a computer, including their model, manufacturer, interface type, capacity |
| Win32_PhysicalMemory | * | Details about the physical memory modules in use, including their capacity, speed, manufacturer |
| Win32_Product | Caption, Description, Vendor, Version, InstallDate, InstallSource, PackageName | Information about installed software, including its name, description, vendor, version, installation date, installation source, and package name |
| Win32_PnPEntity | Caption, Description, DeviceID, Manufacturer, Name, PNPDeviceID, Service, Status | Details about Plug and Play devices, such as their name, description, device ID, manufacturer, name, PnP device ID, service, and status |
Information about AntiViruses
QakBot checks for specific antivirus programs like Kaspersky, Avast, Norton, etc to see if any antivirus software is active on the system. Here the list of AntiViruses products checked by QakBot.
| Processes | Vendor |
|---|---|
| AvastSvc.exe;aswEngSrv.exe;aswToolsSvc.exe;afwServ.exe;aswidsagent.exe;AvastUI.exe | Avast |
| CSFalconService.exe;CSFalconContainer.exe | CrowdStrike Falcon |
| cSvcHst.exe;NortonSecurity.exe;nsWscSvc.exe | Norton Security |
| avgcsrvx.exe;avgsvcx.exe;avgcsrva.exe | AVG Antivirus |
| MsMpEng.exe | Microsoft Defender Antivirus |
| avp.exe;kavtray.exe | Kaspersky Antivirus |
| coreServiceShell.exe;PccNTMon.exe;NTRTScan.exe | Trend Micro Antivirus |
| fshoster32.exe | F-Secure Antivirus |
| fmon.exe | FortiClient Antivirus |
| egui.exe;ekrn.exe | ESET |
| bdagent.exe;vsserv.exe;vsservppl.exe | Bitdefender |
| Sophos UI.exe;SophosUI.exe;SAVAdminService.exe;SavService.exe | Sophos |
| WRSA.exe | Webroot SecureAnywhere |
| vkise.exe;isesrv.exe;cmdagent.exe | Kaspersky |
| ByteFence.exe | ByteFence |
| MBAMService.exe;mbamgui.exe | Malwarebytes |
| mcshield.exe | McAfee |
| dwengine.exe;dwarkdaemon.exe;dwwatcher.exe | Datawatch |
| SentinelServiceHost.exe;SentinelStaticEngine.exe;SentinelAgent.exe | SentinelOne |
| SonicWallClientProtectionService.exe;SWDash.exe | SonicWall |
| CynetEPS.exe;CynetMS.exe;CynetConsole.exe | Cynet |
Configuration Extraction
QakBot encrypts its C&C IPs using RC4 and SHA-1 encryption scheme through hasing a key with SHA-1 then derived key for RC4
mw_w_resource_crypto function
mw_w_crypt_resource function
mw_ww_rc4 function
mw_w_rc4 function Based on functions above the malware decrypts the resource
3715 with RC4 derived key form hashing SHA-1 to key \System32\WindowsPowerShell\v1.0\powershell.exe to extract the IPs of C2.
IOCs
This script that I used to extrcat IPs from resource 3715
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import binascii
import pefile
import ipaddress
from Crypto.Cipher import ARC4
from Crypto.Hash import SHA1
KEY = b'\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
MALWARE_PATH = r"PATH"
OUTPUT_FILE = "extracted_config.txt"
sha1_key = SHA1.new(data=KEY).digest()
def extract_resource(filename, res_id):
pe = pefile.PE(filename)
extracted_data = b""
if not hasattr(pe, "DIRECTORY_ENTRY_RESOURCE"):
return extracted_data
for resource_type in pe.DIRECTORY_ENTRY_RESOURCE.entries:
if not hasattr(resource_type, "directory"):
continue
for resource_id in resource_type.directory.entries:
if hasattr(resource_id, "name"):
if str(resource_id.name) == str(res_id):
data_entry = resource_id.directory.entries[0].data
offset = data_entry.struct.OffsetToData
size = data_entry.struct.Size
extracted_data = pe.get_memory_mapped_image()[
offset : offset + size
]
return extracted_data
return extracted_data
def rc4_decrypt(key_data, data):
cipher = ARC4.new(key_data)
return cipher.decrypt(data)
def main():
with open(OUTPUT_FILE, "w", encoding="utf-8", errors="ignore") as out:
out.write("[+] Malware Configuration Extraction\n")
out.write("=" * 50 + "\n\n")
resource_5812 = extract_resource(MALWARE_PATH, 5812)
if not resource_5812:
out.write("[-] Failed to extract resource 5812\n")
return
decrypted_5812 = rc4_decrypt(sha1_key, resource_5812)[20:]
out.write("[+] BOTNET & CAMPAIGN INFORMATION\n")
out.write("-" * 40 + "\n")
out.write(decrypted_5812.decode("latin1", errors="ignore") + "\n\n")
resource_3719 = extract_resource(MALWARE_PATH, 3719)
if not resource_3719:
out.write("[-] Failed to extract resource 3719\n")
return
decrypted_3719 = rc4_decrypt(sha1_key, resource_3719)
c2_blob = decrypted_3719[21:]
out.write("[+] C2 SERVER LIST\n")
out.write("-" * 40 + "\n")
k = 0
index = 0
while k + 6 <= len(c2_blob):
ip_raw = c2_blob[k : k + 4]
port_raw = c2_blob[k + 4 : k + 6]
ip_addr = ipaddress.IPv4Address(ip_raw)
port = int(binascii.hexlify(port_raw), 16)
out.write(f"[{index}] {ip_addr}:{port}\n")
k += 7
index += 1
out.write("\n[+] Extraction completed successfully\n")
if __name__ == "__main__":
main()
Here is the result of our configuration extractor
[+] Malware Configuration Extraction
==================================================
[+] BOTNET & CAMPAIGN INFORMATION
----------------------------------------
10=obama150
3=1640256791
[+] C2 SERVER LIST
----------------------------------------
[0] 96.21.251.127:2222
[1] 70.51.134.181:2222
[2] 69.14.172.24:443
[3] 186.64.87.213:443
[4] 94.62.161.77:995
[5] 103.139.242.30:990
[6] 114.79.148.170:443
[7] 217.164.247.241:2222
[8] 178.153.86.181:443
[9] 136.232.34.70:443
[10] 37.210.226.125:61202
[11] 173.21.10.71:2222
[12] 31.219.154.176:32101
[13] 140.82.49.12:443
[14] 32.221.229.7:443
[15] 24.152.219.253:995
[16] 106.51.48.170:50001
[17] 114.38.161.124:995
[18] 96.37.113.36:993
[19] 190.39.205.165:443
[20] 45.9.20.200:2211
[21] 105.198.236.99:995
[22] 70.163.1.219:443
[23] 103.139.242.30:995
[24] 24.95.61.62:443
[25] 136.143.11.232:443
[26] 31.215.215.152:1194
[27] 103.143.8.71:6881
[28] 102.65.38.67:443
[29] 31.215.70.105:443
[30] 86.97.9.221:443
[31] 83.110.91.18:2222
[32] 63.153.187.104:443
[33] 74.15.2.252:2222
[34] 217.165.123.47:61200
[35] 41.228.22.180:443
[36] 24.53.49.240:443
[37] 149.135.101.20:443
[38] 94.200.181.154:995
[39] 67.209.195.198:443
[40] 209.210.95.228:32100
[41] 96.80.109.57:995
[42] 80.14.196.176:2222
[43] 38.70.253.226:2222
[44] 24.222.20.254:443
[45] 103.142.10.177:443
[46] 217.128.93.27:2222
[47] 103.157.122.130:21
[48] 24.178.196.158:2222
[49] 182.191.92.203:995
[50] 76.169.147.192:32103
[51] 78.180.66.163:995
[52] 89.41.8.168:443
[53] 190.73.3.148:2222
[54] 79.173.195.234:443
[55] 120.150.218.241:995
[56] 182.56.56.249:443
[57] 121.175.104.13:32100
[58] 76.25.142.196:443
[59] 79.167.192.206:995
[60] 59.6.7.83:61200
[61] 71.74.12.34:443
[62] 83.110.98.231:995
[63] 89.137.52.44:443
[64] 114.143.92.41:61202
[65] 67.165.206.193:993
[66] 94.60.254.81:443
[67] 23.233.146.92:443
[68] 73.151.236.31:443
[69] 96.80.109.57:995
[70] 187.162.59.232:995
[71] 72.252.201.34:995
[72] 50.237.134.22:995
[73] 201.172.31.95:80
[74] 100.1.119.41:443
[75] 40.134.247.125:995
[76] 109.12.111.14:443
[77] 89.101.97.139:443
[78] 24.55.112.61:443
[79] 93.48.80.198:995
[80] 75.188.35.168:443
[81] 83.199.144.45:2222
[82] 92.154.9.41:50002
[83] 111.125.245.116:995
[84] 39.49.105.128:995
[85] 82.152.39.39:443
[86] 105.106.30.144:443
[87] 31.35.28.29:443
[88] 103.139.242.30:22
[89] 218.101.110.3:995
[90] 182.176.180.73:443
[91] 121.175.104.13:443
[92] 65.100.174.110:8443
[93] 79.160.207.214:443
[94] 70.224.68.92:443
[95] 173.25.166.81:443
[96] 176.205.152.44:443
[97] 108.4.67.252:443
[98] 189.174.46.65:995
[99] 187.189.86.168:443
[100] 176.24.150.197:443
[101] 86.98.52.117:443
[102] 200.54.14.34:80
[103] 103.139.242.30:443
[104] 103.139.242.30:465
[105] 103.139.242.30:993
[106] 103.139.242.30:993
[107] 78.101.89.174:2222
[108] 78.101.89.174:443
[109] 73.5.119.219:443
[110] 74.5.148.57:443
[111] 68.186.192.69:443
[112] 50.33.112.74:995
[113] 70.93.80.154:995
[114] 75.169.58.229:32100
[115] 63.143.92.99:995
[116] 217.39.100.89:443
[117] 46.9.77.245:995
[118] 173.71.147.134:995
[119] 75.110.250.187:443
[120] 194.36.28.238:443
[121] 65.100.174.110:443
[122] 65.100.174.110:443
[123] 82.78.212.133:443
[124] 83.110.107.123:443
[125] 59.88.168.108:443
[126] 65.128.74.102:443
[127] 68.204.7.158:443
[128] 78.101.82.198:995
[129] 80.6.192.58:443
[130] 41.97.234.150:995
[131] 114.79.145.28:443
[132] 188.54.96.91:443
[133] 50.238.6.36:443
[134] 83.110.107.123:443
[135] 217.165.11.65:61200
[136] 103.143.8.71:995
[137] 2.178.67.97:61202
[138] 86.198.237.51:2222
[139] 88.253.171.236:995
[140] 187.172.146.123:443
[141] 92.167.4.71:2222
[142] 189.30.244.252:995
[143] 194.36.28.26:443
[144] 84.199.230.66:443
[145] 14.96.67.177:443
[146] 50.238.6.36:443
[147] 182.56.57.23:995
[148] 87.70.118.51:443
[149] 93.48.58.123:2222
[+] Extraction completed successfully
Conclusion
QakBot is a known Trojan-Banker whose techniques may vary from binary to binary (older and newer versions). It has been active for over a decade and doesn’t look like going away anytime soon. The malware is continuously receiving updates and the threat actors keep adding new capabilities and updating its modules in order to steal information and maximize revenue.

