
- Elastic Security Labs is releasing a QBOT malware analysis report from a recent campaign
- This report covers the execution chain from initial infection to communication with its command and control containing details about in depth features such as its injection mechanism and dynamic persistence mechanism.
- From this research we produced a YARA rule, configuration-extractor, and indicators of compromises (IOCs)
As part of our mission to build knowledge about the most common malware families targeting institutions and individuals, the Elastic Malware and Reverse Engineering team (MARE) completed the analysis of the core component of the banking trojan QBOT/QAKBOT V4 from a previously reported campaign.
QBOT — also known as QAKBOT — is a modular Trojan active since 2007 used to download and run binaries on a target machine. This document describes the in-depth reverse engineering of the QBOT V4 core components. It covers the execution flow of the binary from launch to communication with its command and control (C2).
QBOT is a multistage, multiprocess binary that has capabilities for evading detection, escalating privileges, configuring persistence, and communicating with C2 through a set of IP addresses. The C2 can update QBOT, upload new IP addresses, upload and run fileless binaries, and execute shell commands.
As a result of this analysis, MARE has produced a new yara rule based on the core component of QBOT as well as a static configuration extractor able to extract and decrypt its strings, its configuration, and its C2 IP address list.
For information on the QBOT configuration extractor and malware analysis, check out our blog posts detailing this:

The sample is executed with the regsvr32.exe binary, which in turn will call QBOT’s DllRegisterServer export:

After execution, QBOT checks if it’s running under the Windows Defender sandbox by checking the existence of a specific subdirectory titled: C:\\INTERNAL\\__empty, if this folder exists, the malware terminates itself:

The malware will then enumerate running processes to detect any antivirus (AV) products on the machine. The image below contains a list of AV vendors QBOT reacts to:

AV detection will not prevent QBOT from running. However, it will change its behavior in later stages. In order to generate a seed for its pseudorandom number generator (PRNG), QBOT generates a fingerprint of the computer by using the following expression:
fingerprint = CRC32(computerName + CVolumeSerialNumber + AccountName)
If the “C:” volume doesn’t exist the expression below is used instead:
fingerprint = CRC32(computerName + AccountName)
Finally, QBOT will choose a set of targets to inject into depending on the AVs previously detected and the machine architecture:
AV detected & architecture | Targets |
BitDefender | Kaspersky | Sophos | TrendMicro | & x86 | %SystemRoot%\\SysWOW64\\mobsync.exe %SystemRoot%\\SysWOW64\\explorer.exe |
BitDefender | Kaspersky | Sophos | TrendMicro & x64 | %SystemRoot%\\System32\\mobsync.exe %SystemRoot%\\explorer.exe %ProgramFiles%\\Internet Explorer\\iexplore.exe |
Avast | AVG | Windows Defender & x86 | %SystemRoot%\\SysWOW64\\OneDriveSetup.exe %SystemRoot%\\SysWOW64\\msra.exe %ProgramFiles(x86)%\\Internet Explorer\\iexplore.exe |
Avast | AVG | Windows Defender & x64 | %SystemRoot%\\System32\\OneDriveSetup.exe %SystemRoot%\\System32\\msra.exe |
x86 | '%SystemRoot%\\explorer.exe %SystemRoot%\\System32\\msra.exe %SystemRoot%\\System32\\OneDriveSetup.exe |
x64 | %SystemRoot%\\SysWOW64\\explorer.exe %SystemRoot%\\SysWOW64\\msra.exe %SystemRoot%\\System32\\OneDriveSetup.exe |
QBOT will try to inject itself iteratively, using its second stage as an entry point, into one of its targets– choosing the next target process if the injection fails. Below is an example of QBOT injecting into explorer.exe.


QBOT begins its second stage by saving the content of its binary in memory and then corrupting the file on disk:

The malware then loads its configuration from one of its resource sections:

QBOT also has the capability to load its configuration from a .cfg file if available in the process root directory:

After loading its configuration, QBOT proceeds to install itself on the machine– initially by writing its internal configuration to the registry:

Shortly after, QBOT creates a persistence subdirectory with a randomly-generated name under the %APPDATA%\Microsoft directory. This folder is used to drop the in-memory QBOT binary for persistence across reboot:

At this point, the folder will be empty because the malware will only drop the binary if a shutdown/reboot event is detected. This “contingency” binary will be deleted after reboot.
QBOT will attempt the same install process for all users and try to either execute the malware within the user session if it exists, or create a value under the CurrentVersion\Run registry key for the targeted user to launch the malware at the next login. Our analysis didn’t manage to reproduce this behavior on an updated Windows 10 machine. The only artifact observed is the randomly generated persistence folder created under the user %APPDATA%\Microsoft directory:

QBOT finishes its second stage by restoring the content of its corrupted binary and registering a task via Schtask to launch a QBOT service under the NT AUTHORITY\SYSTEM account.
The first stage has a special execution path where it registers a service handler if the process is running under the SYSTEM account. The QBOT service then executes stages 2 and 3 as normal, corrupting the binary yet again and executing commands on behalf of other QBOT processes via messages received through a randomly generated named pipe:


QBOT begins its third stage by registering a window and console event handler to monitor suspend/resume and shutdown/reboot events. Monitoring these events enables the malware to install persistence dynamically by dropping a copy of the QBOT binary in the persistence folder and creating a value under the CurrentVersion\Run registry key:

At reboot, QBOT will take care of deleting any persistence artifacts.
The malware will proceed to creating a watchdog thread to monitor running processes against a hardcoded list of binaries every second. If any process matches, a registry value is set that will then change QBOT behavior to use randomly generated IP addresses instead of the real one, thus never reaching its command and control:
frida-winjector-helper-32.exe frida-winjector-helper-64.exe Tcpdump.exe windump.exe ethereal.exe wireshark.exe ettercap.exe rtsniff.exe packetcapture.exe capturenet.exe qak_proxy | dumpcap.exe CFF Explorer.exe not_rundll32.exe ProcessHacker.exe tcpview.exe filemon.exe procmon.exe idaq64.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 |
QBOT will then load its domains from one of its .rsrc files and from the registry as every domain update received from its C2 will be part of its configuration written to the registry. See Extracted Network Infrastructure in Appendix A.
Finally, the malware starts communicating with C2 via HTTP and TLS. The underlying protocol uses a JSON object encapsulated within an enciphered message which is then base64-encoded:

Below an example of a HTTP POST request sent by QBOT to its C2:
Accept: application/x-shockwave-flash, image/gif, image/jpeg, image/pjpeg, */*
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Host: 181.118.183.98
Content-Length: 77
Cache-Control: no-cache
qxlbjrbj=NnySaFAKLt+YgjH3UET8U6AUwT9Lg51z6zC+ufeAjt4amZAXkIyDup74MImUA4do4Q==
Through this communication channel, QBOT receives commands from C2 — see Appendix B (Command Handlers). Aside from management commands (update, configuration knobs), our sample only handles binary execution-related commands, but we know that the malware is modular and can be built with additional features like a VNC server, a reverse shell server, proxy support (to be part of the domains list), and numerous other capabilities are feasible.
QBOT uses an implementation of Mersenne Twister Random Number Generator (MTRNG) to generate random values:

The MTRNG engine is then used by various functions to generate different types of data, for example for generating registry key values and persistence folders. As QBOT needs to reproduce values, it will almost always use the computer fingerprint and a “salt” specific to the value it wants to generate:


As this sample has two string banks, it has four GetString' functions currying the string bank and the decryption key parameters: One C string function and one wide string function for each string bank. Wide string functions use the same string banks, but convert the data to utf-16.


See Appendix C (String Deciphering Implementation).


The malware resolves the library name through its GetString function and then resolves the hash table with a classic library’s exports via manual parsing, comparing each export to the expected hash. In this sample, the hashing comparison algorithm use this formula:
CRC32(exportName) XOR 0x218fe95b == hash
The malware is embedded with different resources, the common ones are the configuration and the domains list. Resources are encrypted the same way: The decryption key may be either embedded within the data blob or provided. Once the resource is decrypted, an embedded hash is used to check data validity.

See Appendix D (Resource Deciphering Implementation).



Each second, QBOT parses running processes looking for one matching the hardcoded exception list. If any is found, a “fuse” value is set in the registry and the watchdog stops. If this fuse value is set, QBOT will not stop execution– but at the third stage, the malware will use randomly generated IP and won't be able to contact C2.


To inject its second stage into one of a hardcoded target, QBOT uses a classic CreateProcess, WriteProcessMemory, ResumeProcess DLL injection technique. The malware will create a process, allocate and write the QBOT binary within the process memory, write a copy of its engine, and patch the entry point to jump to a special function. This function performs a light initialization of QBOT and its engine within the new process environment, alerts the main process of its success, and then execute the second stage.




Part of the QBOT installation process is installing itself within others users’ accounts. To do so, the malware enumerates each user with an account on the machine (local and domain), then dumps its configuration under the user’s Software\Microsoft registry key, creates a persistence folder under the users’ %APPDATA%\Microsoft folder, and finally tries to either launch QBOT under the user session if the session exist, or else creates a run key to launch the malware when the user will log in.



QBOT registers a console event to handle shutdown/reboot events as well.


QBOT has a mechanism to verify the signature of every message received from its command and control. The verification mechanism is based on a public key embedded in the sample. This public key could be used to identify the campaign the sample belongs to, but this mechanism may not always be present.


The public key comes from a hardcoded XOR-encrypted data blob.



One especially interesting procedure listed installed antivirus via WMI:



QBOT has a system to keep track of processes injected with binaries received from its command and control in order to manage them as the malware receives subsequent commands. It also has a way to serialize and save those binaries on disk in case it has to stop execution and recover execution when restarted.
To do this bookkeeping, QBOT maintains two global structures — a list of all binaries received from its command and control, and a list of running injected processes:


The QBOT malware family is highly active and still part of the threat landscape in 2022 due to its features and its powerful modular system. While initially characterized as an information stealer in 2007, this family has been leveraged as a delivery mechanism for additional malware and post-compromise activity.
Elastic Security provides out-of-the-box prevention capabilities against this threat. Existing Elastic Security users can access these capabilities within the product. If you’re new to Elastic Security, take a look at our Quick Start guides (bite-sized training videos to get you started quickly) or our free fundamentals training courses. You can always get started with a free 14-day trial of Elastic Cloud.
MITRE ATT&CK is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community.
Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.
- Tactic: Privilege Escalation
- Tactic: Defense Evasion
- Tactic: Discovery
- Tactic: Command and Control
Techniques and Sub techniques represent how an adversary achieves a tactical goal by performing an action.
- Technique: Process Injection (T1055)
- Technique: Modify Registry (T1112)
- Technique: Obfuscated Files or Information (T1027)
- Technique: Obfuscated Files or Information: Indicator Removal from Tools (T1027.005)
- Technique: System Binary Proxy Execution: Regsvr32 (T1218.010)
Technique: Application Window Discovery (T1010) - Technique: File and Directory Discovery (T1083)
- Technique: System Information Discovery (T1082)
- Technique: System Location Discovery (T1614)
- Technique: Software Discovery: Security Software Discovery (T1518.001)
- Technique: System Owner/User Discovery (T1033)
- Technique: Application Layer Protocol: Web Protocols (T1071.001)
1.161.71.109:443 1.161.71.109:995 100.1.108.246:443 101.50.103.193:995 102.182.232.3:995 103.107.113.120:443 103.139.243.207:990 103.246.242.202:443 103.87.95.133:2222 103.88.226.30:443 105.226.83.196:995 108.60.213.141:443 109.12.111.14:443 109.228.220.196:443 113.11.89.165:995 117.248.109.38:21 120.150.218.241:995 120.61.2.95:443 121.74.167.191:995 125.168.47.127:2222 138.204.24.70:443 140.82.49.12:443 140.82.63.183:443 140.82.63.183:995 143.0.34.185:443 144.202.2.175:443 144.202.2.175:995 144.202.3.39:443 144.202.3.39:995 148.64.96.100:443 149.28.238.199:443 149.28.238.199:995 172.114.160.81:995 172.115.177.204:2222 173.174.216.62:443 173.21.10.71:2222 174.69.215.101:443 175.145.235.37:443 176.205.119.81:2078 176.67.56.94:443 176.88.238.122:995 179.158.105.44:443 180.129.102.214:995 180.183.128.80:2222 181.118.183.98:443 181.208.248.227:443 181.62.0.59:443 182.191.92.203:995 182.253.189.74:2222 185.69.144.209:443 | 186.105.121.166:443 187.102.135.142:2222 187.207.48.194:61202 187.250.114.15:443 187.251.132.144:22 190.252.242.69:443 190.73.3.148:2222 191.17.223.93:32101 191.34.199.129:443 191.99.191.28:443 196.233.79.3:80 197.167.62.14:993 197.205.127.234:443 197.89.108.252:443 2.50.137.197:443 201.145.189.252:443 201.211.64.196:2222 202.134.152.2:2222 203.122.46.130:443 208.107.221.224:443 209.197.176.40:995 217.128.122.65:2222 217.164.210.192:443 217.165.147.83:993 24.178.196.158:2222 24.43.99.75:443 31.35.28.29:443 31.48.166.122:2078 32.221.224.140:995 37.186.54.254:995 37.34.253.233:443 38.70.253.226:2222 39.41.158.185:995 39.44.144.159:995 39.52.75.201:995 39.57.76.82:995 40.134.246.185:995 41.228.22.180:443 41.230.62.211:993 41.38.167.179:995 41.84.237.10:995 42.235.146.7:2222 45.241.232.25:995 45.46.53.140:2222 45.63.1.12:443 45.63.1.12:995 45.76.167.26:443 45.76.167.26:995 45.9.20.200:443 46.107.48.202:443 | 47.156.191.217:443 47.180.172.159:443 47.180.172.159:50010 47.23.89.62:993 47.23.89.62:995 5.32.41.45:443 5.95.58.211:2087 66.98.42.102:443 67.209.195.198:443 68.204.7.158:443 70.46.220.114:443 70.51.138.126:2222 71.13.93.154:2222 71.74.12.34:443 72.12.115.90:22 72.252.201.34:995 72.76.94.99:443 73.151.236.31:443 73.67.152.98:2222 74.15.2.252:2222 75.113.214.234:2222 75.99.168.194:443 75.99.168.194:61201 76.169.147.192:32103 76.25.142.196:443 76.69.155.202:2222 76.70.9.169:2222 78.87.206.213:995 80.11.74.81:2222 81.215.196.174:443 82.152.39.39:443 83.110.75.97:2222 84.241.8.23:32103 85.246.82.244:443 86.97.11.43:443 86.98.208.214:2222 86.98.33.141:443 86.98.33.141:995 88.228.250.126:443 89.211.181.64:2222 90.120.65.153:2078 91.177.173.10:995 92.132.172.197:2222 93.48.80.198:995 94.36.195.250:2222 94.59.138.62:1194 94.59.138.62:2222 96.21.251.127:2222 96.29.208.97:443 96.37.113.36:993 |
Id | Handler |
---|---|
0x1 | MARE::rpc::handler::CommunicateWithC2 |
0x6 | MARE::rpc::handler::EnableGlobalRegistryConfigurationValuek0x14 |
0x7 | MARE::rpc::handler::DisableGlobalRegistryConfigurationValuek0x14 |
0xa | MARE::rpc::handler::KillProcess |
0xc | MARE::rpc::handler::SetBunchOfGlobalRegistryConfigurationValuesAndTriggerEvent1 |
0xd | MARE::rpc::handler::SetBunchOfGlobalRegistryConfigurationValuesAndTriggerEvent0 |
0xe | MARE::rpc::handler::DoEvasionMove |
0x12 | MARE::rpc::handler::NotImplemented |
0x13 | MARE::rpc::handler::UploadAndRunUpdatedQBOT0 |
0x14 | MARE::rpc::handler::Unk0 |
0x15 | MARE::rpc::handler::Unk1 |
0x19 | MARE::rpc::handler::UploadAndExecuteBinary |
0x1A | MARE::rpc::handler::UploadAndInjectDll0 |
0x1B | MARE::rpc::handler::DoInjectionFromDllToInjectByStr |
0x1C | MARE::rpc::handler::KillInjectedProcessAndDisableDllToInject |
0x1D | MARE::rpc::handler::Unk3 |
0x1E | MARE::rpc::handler::KillInjectedProcessAndDoInjectionAgainByStr |
0x1F | MARE::rpc::handler::FastInjectdll |
0x21 | MARE::rpc::handler::ExecuteShellCmd |
0x23 | MARE::rpc::handler::UploadAndInjectDll1 |
0x24 | MARE::rpc::handler::UploadAndRunUpdatedQBOT1 |
0x25 | MARE::rpc::handler::SetValueToGlobalRegistryConfiguration |
0x26 | MARE::rpc::handler::DeleteValueFromGlobalRegistryConfiguration |
0x27 | MARE::rpc::handler::ExecutePowershellCmd |
0x28 | MARE::rpc::handler::UploadAndRunDllWithRegsvr32 |
0x29 | MARE::rpc::handler::UploadAndRunDllWithRundll32 |
def decipher_strings(data: bytes, key: bytes) -> bytes:
result = dict()
current_index = 0
current_string = list()
for i in range(len(data)):
current_string.append(data[i] ^ key[i % len(key)])
if data[i] == key[i % len(key)]:
result[current_index] = bytes(current_string)
current_string = list()
current_index = i + 1
return result
Read more
from Crypto.Cipher import ARC4
from Crypto.Hash import SHA1
def decipher_data(data: bytes, key: bytes) -> tuple[bytes, bytes]:
data = ARC4.ARC4Cipher(SHA1.SHA1Hash(key).digest()).decrypt(data)
return data[20:], data[:20]
def verify_hash(data: bytes, expected_hash: bytes) -> bool:
return SHA1.SHA1Hash(data).digest() == expected_hash
def decipher_rsrc(rsrc: bytes, key: bytes) -> bytes:
deciphered_rsrc, expected_hash = decipher_data(rsrc[20:], rsrc[:20])
if not verify_hash(deciphered_rsrc, expected_hash):
deciphered_rsrc, expected_hash = decipher_data(rsrc, key)
if not verify_hash(deciphered_rsrc, expected_hash):
raise RuntimeError('Failed to decipher rsrc: Mismatching hashes.')
return deciphered_rsrc
Read more