Introduction
In this two-part blog series, we explore the evolution of SmokeLoader, a malware downloader that has been active since 2011. In Part 1, we explored early versions of SmokeLoader, from its initial rudimentary framework to its adoption of a modular architecture and introduction of encryption and obfuscation.
This blog provides an overview of SmokeLoader’s development from 2015 to 2022, where the malware continued to update its algorithms and improve anti-analysis techniques.
2015-2017: Protocol Renaissance
Versions 2015 and 2017 of SmokeLoader signify major releases in the evolution of the malware. One of the major changes was to the communication with the command-and-control (C2) server. In version 2015, the network protocol was updated with fields that became the basis for all subsequent versions, where each field was separated by a ‘#
’ delimiter.
The following table shows these fields, which have remained unchanged in all versions of SmokeLoader.
Field |
Description |
---|---|
|
Magic bytes (version number). |
|
Unique victim ID. |
|
Affiliate ID. |
|
Victim’s Windows version. |
|
Victim’s Windows OS architecture. |
|
User privileges. |
|
Command ID. |
|
Command argument (the meaning depends on the specific command). |
|
Command result (the meaning depends on the specific command). |
|
Additional data sent with the packet. |
Table 1: SmokeLoader C2 fields present in all versions from 2015-2022.
Introduction of a binary protocol
Beginning in 2017, all SmokeLoader versions started using a binary protocol with the same fields as version 2015. SmokeLoader version 2017 also updated the network communication to use two different static RC4 keys to encrypt the requests and decrypt the responses. Each RC4 key is 4 bytes and hardcoded in the main module of the malware. This modification provides an additional advantage – the data encrypted by SmokeLoader cannot be decrypted from a network capture unless the encryption keys are known.
The binary protocol has remained largely unchanged since 2017, except for the addition of the computer name field, which was added in version 2020. The figure below shows an example packet using SmokeLoader’s binary request format for versions 2020 through 2022.
Figure 1: Example SmokeLoader 2020-2022 network data (with RC4 encryption removed).
SmokeLoader versions 2017-2022 implement the following command IDs:
Command |
Value |
Description |
---|---|---|
|
|
Notifies the server that the infected system is online and awaiting tasking. |
|
|
Notifies the server that the infected system is ready to execute a personal task, or an update and awaiting more information. May also be used to report the result of an uninstall command. |
|
|
This command is used to confirm that an update or a task was executed correctly and the |
|
|
Exfiltrates data produced by the stealer plugin. The |
|
|
This command is used to notify the server about the presence of a specific process running on the victim’s computer. The |
|
|
Reports the status of the |
|
|
Submits the formgrabber plugin data to the server, where |
|
|
Submits passwords harvested from the sniffer plugin where |
|
|
Sends files from the infected system discovered by the fsearch plugin where the |
|
|
Confirms the successful execution of a DDoS attack (against the targets configured in the plugins’ rules). If |
|
|
Submits the results of the keylogger plugin where |
|
|
This command is used to request the TeamViewer package from the server. |
|
|
Once a TeamViewer instance is run, this command sends the ID and password of the TeamViewer session to the server. They are separated by the substring |
|
|
Requests a cryptocurrency miner executable from the server. In newer SmokeLoader versions, this command value is empty (undefined). *This command was introduced in version 2017 of SmokeLoader and removed in version 2020. |
|
|
This command is used by the bot to submit the email grabber plugin results to the server where *This command was introduced in version 2017 of SmokeLoader. |
Table 2: Message types implemented by SmokeLoader.
The primary message type is the CMD_ONLINE
, which serves as a knock or keep-alive. The response from the server may be one of the following values:
Byte |
Command |
Description |
---|---|---|
|
|
Personal task |
|
|
Uninstall |
|
|
Update |
Table 3: Commands implemented by SmokeLoader.
For all other scenarios, the server will provide the number of available tasks, as well as (optional) plugins and their respective configurations. The figure below shows an example task, with plugins (and their configurations).
Figure 2: Example SmokeLoader task with plugins and configuration.
Once SmokeLoader receives a response to the CMD_ONLINE
request, it will send a sequence of CMD_GETTASK
requests for tasks, updates, and confirm the result of previous commands.
SmokeLoader will request all available tasks from the server, and in response, the server will provide each task individually. These tasks may include either the content of a file to be executed or the location (URL) from which the file can be downloaded.
In May 2024, Operation Endgame with technical assistance provided by Zscaler ThreatLabz, leveraged SmokeLoader’s built-in uninstall (0x72) command to remotely disinfect tens of thousands of infections.
Algorithm changes
One notable characteristic of SmokeLoader is that the algorithms used vary across versions, likely as an attempt to break existing detections. For example, in SmokeLoader version 2014, the bot ID was generated from the computer name and volume information using a custom algorithm that combined CRC32 and XOR. However, version 2015 and newer have since replaced the hash algorithm to generate the bot ID with MD5.
The encryption of C2 servers was also altered in version 2017, which employs an XOR-based algorithm wherein each byte of the unencrypted C2 URL is encrypted into two bytes. The decryption algorithm utilizes these two bytes, along with a hardcoded 1-byte key, to retrieve the original byte.
The Python implementation of the algorithm is shown below:
def smoke2017_c2_xor_decrypt(enc, key):
out = b''
i = 0
while True:
out += (((enc[i] ^ key[0]) -
(enc[i+1] ^ key[0])) & 0xff).to_bytes(1, 'little')
i += 2
if i > len(enc) / 2:
break
return out
SmokeLoader version 2017 (and newer) also reintroduced string encryption, but replaced the algorithm with RC4.
Also beginning with version 2017, the malware adopted a different hash function to locate the addresses of the required APIs.
The Python code provided below represents the implementation of the algorithm used to hash the API names in version 2017:
def calc_hash_smoke2017(name):
name = name.encode()
hash = 0
for i in range(0, len(name)):
hash ^= (name[i] << 8)
temp = ROL(hash, 8)
temp_bytes = list(struct.pack('<L', temp))
temp_bytes[0] = 0xff &(temp_bytes[1] ^ temp)
temp = struct.unpack('<L', bytes(temp_bytes))[0]
hash = 0xffffffff & (temp * 2)
return hash
2018: The Stager Revolution
The most significant changes introduced in SmokeLoader version 2018, and subsequent versions, revolve around the stager module. These changes enhanced obfuscation techniques and implemented anti-analysis measures.
Starting from version 2018, the stager module incorporates comprehensive anti-analysis features and includes code for injecting the main module, in addition to handling decryption and decompression. Furthermore, the stager module underwent significant improvements to the obfuscation techniques.
It is important to note that the stager module, starting in version 2017, no longer loads the main module within the current process context. Instead, the stager itself injects the main module into the address space of an explorer process. In version 2017, the stager creates a new instance of the explorer.exe
process, while versions 2018 and onward inject the main module into the default explorer process. The reason why version 2017 was designed to create a new explorer process is because the SmokeLoader main module code (for that version) is only 32-bit. Therefore, if the victim’s Windows operating system is 64-bit, SmokeLoader can still function by creating and injecting the main module into a 32-bit (WoW64) explorer process.
Code obfuscation
Starting from version 2018, several code obfuscation techniques were implemented in SmokeLoader that persist in the latest versions. These methods are largely targeted at confusing disassemblers and include the following:
- Code permutations
- Opaque predicates (JZ / JNZ)
- Stack-based obfuscation, which mixes (string) data and code
- Obfuscated jumps that use the Relative Virtual Address (RVA) of the destination address along with the precalculated image base
Encrypted functions
In previous versions of SmokeLoader, we observed simple encryption layers using 1-byte XOR keys and straightforward non-obfuscated loops in the stagers. However, starting from version 2018, there was a significant improvement to encryption functions.
In the stager of SmokeLoader version 2018 (and newer), a majority of the code is encrypted using nested XOR layers, enhancing the encryption and obfuscation techniques employed, as shown in the figure below.
Figure 3: SmokeLoader’s stager using nested XOR encryption layers.
Among the final blocks is code responsible for decrypting, decompressing, and injecting the main module. The figure below shows an example of a function that decrypts SmokeLoader’s stager code for versions 2018 through 2022.
Figure 4: Code decryption for SmokeLoader versions 2018-2022.
After executing a decrypted block of code, it is immediately re-encrypted to minimize the amount of unencrypted code in memory.
Anti-analysis tricks
Since 2018, a significant portion of the anti-analysis techniques that were previously executed in the main module have been shifted to the stager. As each new version is released, the malware incorporates an expanding array of evasion techniques and expands its target range to include a wider variety of security products.
Furthermore, the new and more intricate stager necessitates the loading and utilization of multiple APIs. To locate the required API addresses, it employs a hash-based search mechanism, utilizing the DJB2 algorithm.
Starting from version 2018, the stager incorporates checks for keyboard layouts. If it detects Ukrainian or Russian languages, the infection process is halted. Additionally, the stager examines the OsMajorVersion
field within the PEB structure and ceases execution for operating system versions below 6 (Windows Vista/Server 2008).
PROPagate injection
In SmokeLoader 2018, the stager employs the PROPagate code injection method to inject the main module within the context of the explorer process. This injection process utilizes native Windows APIs such as NtOpenProcess
, NtDuplicateObject
, NtCreateSection
, and NtMapViewOfSection
. Additionally, the SetPropA
and SetNotifyMessageA
APIs are utilized to execute the PROPagate attack.
It is worth mentioning that starting from version 2018, the stager carries both x86 and x64 variants of the main module. Depending on the current platform, it will inject the appropriate variant into the explorer process.
Algorithm changes
In the 2018 version of SmokeLoader, significant changes were made to the algorithms used for decrypting and decompressing the main module. For decryption, a straightforward XOR is employed with a 4-byte key, while decompression is accomplished using the RtlDecompressBuffer
function provided by Windows, utilizing the LZNT1 algorithm.
Furthermore, the encryption method for the C2s was altered in the 2018 version. It now utilizes an XOR-based algorithm with a 4-byte key.
The following code shows a Python implementation to decrypt the C2 URLs for SmokeLoader version 2018:
def smoke2018_c2_xor_decrypt(enc, key):
out = ''
for i in range(len(enc) - 4):
xor_key = struct.unpack('<L', key)[0]
v = struct.unpack('<L', enc[i:i + 4])[0]
for j in range(4):
v = v ^ xor_key
xor_key >>= 8
tmp = (chr(~v & 0xff))
out += tmp
return out.encode()
The algorithm for hashing the API names to locate their addresses was also modified, as shown below.
def calc_hash_smoke2018_2022(name):
name = name.encode()
hash = 0
for i in range(0, len(name)):
hash = 0xffffffff&((name[i] & 0xDF) +
ROL(hash ^ (name[i] & 0xDF), 8))
return hash
Version 2018 was also the first to use scheduled tasks for persistence. Every SmokeLoader version since then has continued to utilize scheduled tasks with only the task name changing between versions.
2019-2022: Contemporary History
In the most recent versions of SmokeLoader, there haven’t been significant changes in the structure and functionality of the malware itself. However, there have been modifications to the algorithms used and the anti-analysis techniques employed.
NTDLL hook evasion
SmokeLoader’s stager heavily relies on Windows native APIs to carry out its tasks. Instead of utilizing the default ntdll
library that is loaded when the process is created, SmokeLoader loads its own copy of ntdll
into memory. This approach ensures that if breakpoints are set on the exported functions of the library or if a monitoring tool attempts to perform hooks, they will be unable to trace SmokeLoader’s behavior as it operates with its own separate copy of ntdll
.
In version 2020 and 2022, SmokeLoader creates the copy of ntdll
in memory using functions like CreateFileMappingW
and MapViewOfFile
. In version 2019, the malware created a copy of the library on disk within the %TEMP%
folder and loaded it from there using LdrLoadDll
.
Detection of security products
SmokeLoader has continuously improved its ability to detect security products on a system in order to avoid infection if any are present. This detection capability has significantly increased in the latest versions, with notable enhancements in 2020 and 2022. The table below highlights these improvements.
Process/registry value |
2012 |
2014 |
2015 |
2017 |
2018 |
2019 |
2020 |
2022 |
---|---|---|---|---|---|---|---|---|
|
x |
x |
x |
x |
x |
x |
||
|
x |
|||||||
|
x |
x |
x |
|||||
|
x |
|||||||
|
x |
x |
x |
x |
x |
x |
||
|
x |
|||||||
|
x |
x |
||||||
|
x |
x |
x |
|||||
|
x |
|||||||
|
x |
x |
x |
x |
x |
x |
||
|
x |
|||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
x |
|||||
|
x |
x |
x |
|||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
x |
x |
x |
x |
||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
x |
||||||
|
x |
|||||||
|
x |
Table 4: The year SmokeLoader introduced detection for a particular security product.
Furthermore, in the latest versions of SmokeLoader, the malware relies on Windows native APIs, specifically NtQuerySystemInformation
and NtQueryInformationProcess
, to query the kernel flags SystemCodeIntegrityInformation
and ProcessDebugPort
.
Algorithm changes
In versions 2019 and 2020 of SmokeLoader, a different XOR-based algorithm is implemented to decrypt the list of C2 servers. The decryption key used is derived from the CRC32b value of the encrypted C2 address.
The following code provides a Python implementation of the decryption algorithm:
def smoke2019_2020_decrypt(enc, key):
dec = b''
for c in enc:
for i in key: c = c ^ i
dec += (c ^ 0xE4).to_bytes(1, 'little')
return dec
In version 2022, SmokeLoader implemented an RC4 algorithm to protect the C2 URLs. Instead, a DWORD
within the structure of the encrypted C2 contains the RC4 key, while another DWORD
holds the CRC32b value of the URL. This can be defined as the following C structure:
struct EncryptedC2 {
unsigned char C2_length;
unsigned int RC4_key;
unsigned char C2_encrypted_data[C2_LENGTH];
unsigned int C2_CRC32b;
}
In versions 2020 and 2022, SmokeLoader introduced the LZSA algorithm for decompressing the main module. This replaced the previous algorithm, LZNT1. Additionally, the PROPagate method used for injecting the main module into the explorer process was no longer utilized in these versions. Instead, SmokeLoader creates a shared section as it did in previous versions and creates a remote thread by invoking the native API RtlCreateUserThread
.
Conclusion
SmokeLoader is a sophisticated and intricate modular malware downloader that has been prevalent for many years. The developers of this malware family have consistently enhanced its capabilities by introducing new features and employing obfuscation techniques to impede analysis efforts. While the most recent known version of SmokeLoader dates back to 2022, it has been extensively deployed in numerous campaigns throughout recent years. However, it is important to note that the absence of newer versions does not rule out the possibility of future iterations emerging in the near future. Following Operation Endgame, there has been an overall decline in SmokeLoader activity. However, this lull will likely be short-lived as various threat actors who have leveraged SmokeLoader regroup.
Zscaler Coverage
In addition to sandbox detections, Zscaler’s multilayered cloud security platform detects indicators related to SmokeLoader at various levels with the following threat names:
Indicators Of Compromise (IOCs)
Version |
Sample SHA256 |
---|---|
2012 |
857fc7aafbbf0d4c850c1b1585a72420600bdabe269f343c0c817614aa6c94cd |
2014 |
e92d1c2c1e145c1d6c42dd402e75f46e5edfb2bab5539c4d103d345b5ac965a3 |
2016 |
18aa1b79bbeee6a731b897377233d54b1b2464eeb9a25dafc0debfc43af8c04f |
2017 |
32ba1f3b96cf77a08c041d4983d6afa7db8e1948d27d6a8dd55b7bb95e493189 |
2018 |
b6ec96043dba7722cac4ed24b6979fc71a758bdf18ca44353c19194c172bf621 |
2018 |
5727c2cd54b8408ca0f8e943cad61027a2c3d51da64f2f1224a6b9acc4820f8e |
2019 |
fc20b03299b8ae91e72e104ee4f18e40125b2b061f1509d1c5b3f9fac3104934 |
2019 |
d5efd66f54dce6b51870e40a458fa30de366a2982ab2f83dddff5cb3349f654d |
2020 |
070a94ee0cd9ac1b1ed467353f5731e09cab136315447c04f53bc52d4fe3f8cc |
2020 |
7377efde4e4e86650ab8495f57ab4a76d4f8efe31e2962305b8c42a6cee70454 |
2022 |
c78bc4fb8955940b3ac9b52cb16744a61f8bdaf673fd64fc106465241c56cc6c |
Source: Original Post