QakBOT v5 Deep Malware Analysis
Meet Qakbot
QakBot, also recognized as QBot, QuackBot, and Pinkslipbot, has been operational for years, initially as a financial malware targeting governments and businesses for financial fraud by pilfering user credentials and keystrokes.
Over time, it has evolved into a malware dropper, spreading sensitive information to other network systems. The threat group has updated its code-base to support 64-bit versions of Windows, enhanced encryption algorithms, and added further obfuscation techniques. With the release of Qakbot version 5.0, the string encryption algorithm underwent a significant change. While strings are still encrypted using a simple XOR key, the key is no longer hard-coded in the data section. Instead, it is encrypted with AES.
Technical in Points
- Qakbot uses API hashing to hide its imports. It uses CRC32 hashing, along with another layer of XORing with a hard-coded key. It’s parsing the loaded DLLs in memory and getting its export tables. As a result, Qakbot can resolve imported APIs and build its IAT.
- Qakbot comes with encrypted strings inside the .data section, These strings are encrypted using a XOR key and that key is encrypted using AES algorithm.
- Environment Detection: Qakbot includes checks to detect if it is running in a virtual machine or sandbox environment, commonly used tools for malware analysis. If such conditions are detected, Qakbot may change its behavior or terminate itself to avoid detection.
- Configuration Extraction: Qakbot comes with AES encrypted configuration.This configuration contains details related to the malicious campaign and the C2 which the malware will communicate with for further commands.
- C2 Communication: After extracting its C2, Qakbot establishes a connection with its C2 servers to receive commands for downloading, executing additional modules, updating configuration values, and exfiltrating gathered information from the infected system.
- Qakbot gathers comprehensive information about the compromised host to send to its C2 server and create a unique victim fingerprint. This includes OS version, domain trusts, computer name, username, screen resolution, system time, system uptime, and bot uptime. It mainly relies on Windows Management Instrumentation(WMI) to collect details such as hardware ID, installed languages, and installed programs.
Sample Basic Information
SHA-256 | af6a9b7e7aefeb903c76417ed2b8399b73657440ad5f8b48a25cfe5e97ff868f |
---|---|
File type | Win DLL |
Target Machine | x64 |
Creation Time | 2024-01-29 13:43:37 UTC |
First Seen In The Wild | 2024-02-07 10:12:50 UTC |
Anti Analysis
API Resolution
QakBot uses Windows API Hashing (Dynamic API Resolution) to evade signature-based anti-malware scanners and make static analysis harder.
We can see based on algorithm constants that Qakbot uses the CRC32 hash algorithm, also there is another layer of XORing, and here are the steps in some detail :
The DllName is decrypted by XORing with a hard-coded key 0xA235CB91. After decryption, a handle to the DLL is obtained. This handle is then passed to a function that iterates over the DLL’s exported functions. A function resolves the addresses of the exports by iterating over the export table of the module, hashing the name of each export using CRC32, and comparing the result with a hard-coded CRC32 hash to determine if it has found the correct address.
With knowledge of the algorithm name and XOR key, we can use the awesome hashdb
plugin from OALabs that performs string hash lookup against a remote database.
Once the HashDB plugin decrypts all API names, we create structures to store the API lists from each DLL. This simplifies our workflow and make our life easier while analysis .
Defeating encrypted Strings
Qakbot strings are obfuscated, making the analysis more difficult, so the next step is to decrypt them.
Decryption routine
This version decrypts the strings with an XOR key just like the earlier versions but this XOR key is encrypted using the AES algorithm.
It first Calculates a SHA256 hash for aes_key_ref and uses the calculated hash as the AES Key then decrypts the enc_xor_key blob using AES in CBC mode to have the dec_xor_key.
The final step is to use the dec_xor_key to decrypt the string array.
Writing a decryption script
We now can write an IDAPython script to decrypt the strings and add comments to the code, making analysis easier here are some notes before the script :
- The first 16 bytes of the enc_xor_key are used as the AES IV.
- There are two encrypted string tables used.
- There are two decryption functions with 4 wraps.
- The wrap function decrypts the string array and selects the string based on an index [the only argument].
Figure(8): Index pattern used in script
#--------------- imports --------------------#
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import idautils
#------------- helper ------------------------#
def hex_to_int(x):
if type(x) == int :
return x
return (int(x[:-1], 16))
def search_by_index(table , ind):
return(table[ind:].split('\x00')[0])
#------------- IDA py ------------------------#
def read_data_ida(address,size):
data = idc.get_bytes(address, size)
return data
def set_comment(address, text):
idc.set_cmt(address, text,0)
#------------ Decryption ---------------------#
def calculate_sha256(input_data):
sha256_hash = hashlib.sha256()
sha256_hash.update(input_data)
hash_hex = sha256_hash.digest()
return hash_hex
def aes_decrypt(ciphertext, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)
unpadded_plaintext = unpad(plaintext, AES.block_size)
return unpadded_plaintext
def xor_decrypt(data,key):
dec_data = ''
for i in range(len(data)):
dec_data += chr(data[i] ^ key[i % len(key)])
return dec_data
def full_dec(enc_str , enc_xor_key , aes_key_init):
aes_key = calculate_sha256(aes_key_init)
dec_xor_key = aes_decrypt(enc_xor_key[16:],aes_key,enc_xor_key[:16])
dec_str = xor_decrypt(enc_str,dec_xor_key)
return dec_str
#----------- Decrypt enc str tbl 1 -------------#
enc_str_1 , enc_xor_key_1 , aes_key_init_1 = read_data_ida(0x1800297A0 , 0x1836) , read_data_ida(0x18002AFE0,0xA0) , read_data_ida(0x180029700,0x9F) #read our data .
tbl_1 = full_dec(enc_str_1,enc_xor_key_1,aes_key_init_1)
#----------- Decrypt enc str tbl 2 -------------#
enc_str_2 , enc_xor_key_2 , aes_key_init_2 = read_data_ida(0x1800282A0 , 0x5AD) , read_data_ida(0x1800281C0,0xD0) , read_data_ida(0x180028150,0x63) #read our data .
tbl_2 = full_dec(enc_str_2,enc_xor_key_2,aes_key_init_2)
#--> pattern used: mov ecx , immediate_val
def do_magic(table,references):
for ref in references:
prev_instruction_address = idc.prev_head(ref)
if (idc.print_insn_mnem(prev_instruction_address) == 'mov' and idc.print_operand(prev_instruction_address,0) == 'ecx' and idc.get_operand_type(prev_instruction_address,1) == 5):
ind = print_operand(prev_instruction_address,1)
set_comment(ref,search_by_index(table,hex_to_int(ind)))
else :
prev_instruction_address = idc.prev_head(prev_instruction_address)
if (idc.print_insn_mnem(prev_instruction_address) == 'mov' and idc.print_operand(prev_instruction_address,0) == 'ecx' and idc.get_operand_type(prev_instruction_address,1) == 5):
ind = print_operand(prev_instruction_address,1)
set_comment(ref,search_by_index(table,hex_to_int(ind)))
else:
prev_instruction_address = idc.prev_head(prev_instruction_address)
if (idc.print_insn_mnem(prev_instruction_address) == 'mov' and idc.print_operand(prev_instruction_address,0) == 'ecx' and idc.get_operand_type(prev_instruction_address,1) == 5):
ind = print_operand(prev_instruction_address,1)
set_comment(ref,search_by_index(table,hex_to_int(ind)))
else:
print('not working' ,hex(ref))
reference_1 = list(idautils.CodeRefsTo(idc.get_name_ea_simple("wrap_mw_decrpyion_fun_1"), 0)) #codeRefs-to need "ea" as arguemt .
reference_1 = reference_1 + list(idautils.CodeRefsTo(idc.get_name_ea_simple('wrap_2_mw_decrpyion_fun_1') , 0))
reference_2 = list(idautils.CodeRefsTo(idc.get_name_ea_simple('wrap_2_mw_decrpyion_fun_2'), 0))
reference_2 = reference_2 + list(idautils.CodeRefsTo(idc.get_name_ea_simple('wrap_mw_decrpyion_fun_2'), 0))
def main():
do_magic(tbl_1,reference_1)
do_magic(tbl_2,reference_2)
if __name__ == '__main__':
main()
you can get the full decrypted strings list from here
Emulation Check
Qakbot uses the GetFileAttributesW function to check for a folder "C:\INTERNAL__empty." If this directory exists, it suggests that the environment might be used for analysis, such as Microsoft Defender emulation or sandbox, and then the process will be terminated.
Checking Processes
Qakbot loops through running processes on the system and compares their executable names against well-known static and dynamic malware analysis tools.
full processes list
Expand to see more
wireshark.exe
filemon.exe
procmon.exe
idaq64.exe
tcpview.exe
frida-winjector-helper-32.exefrida-winjector-helper-64.exe
tcpdump.exe
windump.exe
ethereal.exe
ettercap.exe
rtsniff.exe
packetcapture.exe
capturenet.exe
qak_proxy
dumpcap.exe
CFF Explorer.exe
not_rundll32.exe
ProcessHacker.exe
loaddll32.exe
PETools.exe
ImportREC.exe
LordPE.exe
SysInspector.exe
proc_analyzer.exe
sysAnalyzer.exe
sniff_hit.exe
joeboxcontrol.exe
joeboxserver.exe
ResourceHacker.exe
x64dbg.exe
Fiddler.exe
sniff_hit.exe
sysAnalyzer.exe
BehaviorDumper.exe
processdumperx64.exe
anti-virus.EXE
sysinfoX64.exe
sctoolswrapper.exe
sysinfoX64.exe
FakeExplorer.exe
apimonitor-x86.exe
idaq.exe
dumper64.exe
user_imitator.exe
Velociraptor.exe
Anti VM
Qakbot exploits Windows Management Instrumentation (WMI), a system management technology used to administer remote systems and provide comprehensive data about the operating system, hardware, and installed software and applications on a computer.
- It uses WMI queries to gather system information, including details about virtualization. It queries classes such as Win32_ComputerSystem, Win32_Bios, Win32_DiskDrive, or Win32_PhysicalMemory, then check for patterns indicative of virtualized environments. These patterns include known manufacturer or model strings associated with virtualization platforms.
Below are the classes and their corresponding checked values :
Class | Checked Values |
---|---|
Win32_ComputerSystem | MS_VM_CERT, VMware, Virtual Machine |
Win32_Bios | VRTUAL, VMware, VMW, Xen |
Win32_DiskDrive | VMware, PROD_VIRTUAL_DISK, VIRTUAL-DISK, XENSRC, 20202020 |
Win32_PhysicalMemory | VMware, VMW, QEMU |
Win32_PnPEntity | QEMU, VMware Pointing, VMware Accelerated, VMware SCSI,.. |
Qakbot also searches for ‘vmnat’, a process initiated by VMware upon startup. ‘vmnat’ manages communication in the Network Address Translation (NAT) set up with the guest machine .
Qakbot’s C2 Functionality
Malware needs to connect to C2 servers to execute remote commands, update its code, and exfiltrate stolen data. Before doing so, it needs to extract its C2 from an encrypted configuration.
Configuration Extraction
Qakbot, in this version, contains an embedded AES encrypted configuration within its .data section.
The AES decryption method used is the same as the one we’ve seen for decrypting strings. The key will be SHA-256 hashed before attempting the decryption, the first 16 bytes of the encrypted string used as IV. Then use the final key to decrypt the rest encrypted data.
With the same method and key, Qakbot will decrypt its C2 list .
With this information, we can reuse our string decryption script with some edits to have the configuration . notice that :
- The first 32 bytes in the decrypted data represent the SHA-256 validation, a cryptographic process used for data integrity verification. These bytes serve as a hash value that allows systems to confirm the authenticity and integrity of the data being processed.
We can see the output of the script (configuration).
C2 communication
QakBot mainly uses HTTP for C2 communication. The malware communicates with its C2 servers through encrypted AES payloads and then encodes the result in Base64.
Gather system INFO
Part of 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. This approach allows it to collect various details about the system, including hardware, software, and configuration data. By using these methods together, QakBot obtains a comprehensive overview of the target computer’s setup and specifications.
VMI Queries Used
Qakbot builds a WMI query by concatenating strings to form It then executes these queries to retrieve critical data and obtain a comprehensive overview of the system’s configuration and installed security measures.
Here are the WMI classes targeted and the information they retrieve:
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 |
Windows command line
Qakbot creates anonymous pipes to execute various built-in command-line tools processes, enabling it to retrieve information about the compromised system’s environment effectively.
Here is the list of commands that can be used to gather information about the system:
Windows 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. |
Additionally, it will use Windows API calls to get different system details like computer name, screen size, AD domain info, user name, processor details, whether it’s a 32-bit or 64-bit Windows, and the operating system version, along with its respective full paths.
Collect AntiViruses Information
Qakbot checks for specific antivirus programs like Kaspersky, Avast, Norton, etc to see if any antivirus software is active on the system. It does this by scanning running programs and looking for related processes from these vendors.
This list shows which antivirus vendors are associated with each process :
processes | Related Vendor |
---|---|
ccSvcHst.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 |
AvastSvc.exe;aswEngSrv.exe;aswToolsSvc.exe;afwServ.exe;aswidsagent.exe;AvastUI.exe | Avast |
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 |
CSFalconService.exe;CSFalconContainer.exe | CrowdStrike Falcon |
Executing C2 Commands
After establishing communication, the C2 server will send commands to be executed. These commands are represented as integer values or indexes.
Process Hollowing
QakBot selects a system process for process hollowing based on the machine’s architecture (32-bit or 64-bit) and the installed antivirus software.
This list includes the following system processes:
Expand to see more
%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
It first calls the CreateProcessW()
API with the CREATE_SUSPENDED flag to start a new process, making it suspended at the beginning.
Then it allocates virtual memory in a target process, writes data into the allocated region, and then modifies the memory protection to allow execution.
Next, it retrieves the context of the thread to modify it to set the instruction pointer (EIP/RIP register) to point to the entry point of the injected code.
It finally calls the API ResumeThread()
to resume the new processes.
Persistence
QakBot sets itself to run on system reboot through a registry entry or Scheduled Task.
Conclusion
Qakbot is an advanced malware with regular updates and powerful anti-analysis actions, ensuring it remains a persistent threat with a wide range of capabilities and techniques.
YARA Rule
rule detect_Qakbot_v5
{
meta:
description = "just a rule for Qakbot v5"
author = "Mohamed Ezzat (@ZW01f)"
hash1 = "af6a9b7e7aefeb903c76417ed2b8399b73657440ad5f8b48a25cfe5e97ff868f"
hash2 = "59559e97962e40a15adb2237c4d01cfead03623aff1725616caeaa5a8d273a35"
strings:
$s1 = "\\u%04X\\u%04X" ascii wide
$s2 = "%u;%u;%u" ascii wide
$s3 = "CfGetPlatformInfo" ascii wide
$p1 = {45 33 C0 E8 ?? ?? ?? ?? 35 91 CB 35 A2 41 3B C7}
$p2 = { 0F B6 01 48 FF C1 44 33 C0 41 8B C0 41 C1 E8 04 83 E0 0F 44 33 04 82 41 8B C0 41 C1 E8 04 83 E0 0F 44 33 04 82 49 83 E9 01 75 ?? 41 F7 D041 8B C0 C3}
condition:
uint16(0) == 0x5A4D and all of ($p*) and (2 of ($s*)) and filesize < 500KB
}
Python Automated Configuration Extraction
This python script is used to extract the configuration of the Qakbot malware :
- Open the binary file.
- Get the .data section.
- Extract the the key and the encrypted configuration data .
- SHA-256 hash the extracted key to get the final key.
- Use the key to decrypt the configurations.
- Parse the decrypted configurations to extract useful information.
#--------------- imports --------------------#
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import socket
from datetime import datetime
import pytz
#------------- helper ------------------------#
def extract_data(filename): #finds the content of the ".data" section. .
import pefile
pe = pefile.PE(filename)
for section in pe.sections:
if '.data' in section.Name.decode(encoding='utf-8').rstrip('x00'):
return (section.get_data(section.VirtualAddress, section.SizeOfRawData))
def tohex(data):
import binascii
if type(data) == str:
return binascii.hexlify(data.encode('utf-8'))
else:
return binascii.hexlify(data)
def get_ip(ip_binary):
# Convert the binary network format to a human-readable string format
ip_str = socket.inet_ntoa(ip_binary)
return ip_str
#------------ Decryption ---------------------#
def calculate_sha256(input_data):
sha256_hash = hashlib.sha256()
sha256_hash.update(input_data)
hash_hex = sha256_hash.digest()
return hash_hex
def aes_decrypt(ciphertext, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)
unpadded_plaintext = unpad(plaintext, AES.block_size)
return unpadded_plaintext
def full_dec(enc_str , aes_key_init):
aes_key = calculate_sha256(aes_key_init)
dec_str = aes_decrypt(enc_str[17:],aes_key,enc_str[1:17])
return dec_str
def parse_camp(input_str):
lines = input_str.strip().split(b'\r\n')
parsed_data = {}
for line in lines:
key, value = line.split(b'=')
parsed_data[key] = value
timestamp = int(parsed_data[b'3'])
dt_obj = pytz.utc.localize(datetime.utcfromtimestamp(timestamp))
print(f"Botnet ID : {parsed_data[b'10']}'")
print(f"b'40' : {parsed_data[b'40']}'")
print(f"Campaign Timestamp : {dt_obj}")
def parse_c2(dec_ips):
i = 0
splitted_data = [dec_ips[i:i+7] for i in range(1, len(dec_ips), 8)]
for data in splitted_data:
ip = get_ip(data[:4])
port = int(tohex(data[4:6]),16)
print('IP[{0}] = {1}:{2}'.format(i,ip,port))
i = i + 1
def main():
file_name = input("enter the file path: ")
# The config data begins at these offsets inside the .data section
enc_ips_rva = 0x852 ; size_rva = 0x850 ; enc_config_rva = 0x1022
data_section = extract_data(file_name) #read data section
size = ord(data_section[size_rva:size_rva+1])
enc_config_ips = data_section[enc_ips_rva:enc_ips_rva+size]
enc_config = data_section[enc_config_rva:enc_config_rva+size]
init_key = b'T2X!wWMVH1UkMHD7SBdbgfgXrNBd(5dmRNbBI9'
aes_key = calculate_sha256(init_key)
campaign_info = full_dec(enc_config,init_key)
dec_c2 = full_dec(enc_config_ips,init_key)
print('##------------------- Campaign Info -------------------##')
print('sha256 :',tohex(campaign_info[:32]))
print('#--------------------------------------#')
parse_camp(campaign_info[32:])
print('##------------------- Qakbot c2 -------------------##')
print('sha256 :',tohex(dec_c2[:32]))
print('#--------------------------------------#')
parse_c2(dec_c2[32:])
if __name__ == '__main__':
main()