Detect Shulfar Malware Encrypted TCP C&C Traffic Using PacketSmith Yara-X Detection Module
We empower businesses with cutting-edge software and expert services to navigate the complexities of today's cyber landscape. Secure your network with cutting-edge software and services that ensure your safety and peace of mind!
Introduction
Splunk published a blog post about a variant of the Gh0stRat malware family used in a new campaign delivered alongside the CloverPlus adware. The blog post is titled "Not Just Annoying Ads: Adware Bundles Delivering Gh0st RAT", dated April 17, 2026. Although Splunk detects the RAT as a variant of the Gh0stRat family, Netomize refutes linking this variant to the Gh0stRat family due to major differences in code structure, C&C communication protocols, and functionality. Instead, we refer to it as "Shulfar," the reverse of the DLL Export name "RAFlush".
The malware utilizes two communication channels: one via the HTTP protocol and the other through a custom TCP packet payload. Notably, the custom TCP protocol employs a straightforward encryption algorithm, using XOR and addition with a one-byte key, to encrypt the TCP packet's payload.
The malware is a 32-bit DLL written in C. I've chosen it to showcase the "yara" detection module in PacketSmith, highlighting its ability to detect encrypted traffic without relying on a specific key.
The variant with the following information (in the table shown below) matches the "Gh0stRat" variant referenced by the Splunk Threat Research Team. The sample is available on VT.
| Attribute | Value |
|---|---|
| MD5 | 20e7a8b973ac2b43c95ddb77308266c9 |
| SHA-1 | e46e6ea272ae628d15bfb7b71ff40e3950fd2e85 |
| SHA-256 | ec6ef50587a847d4a655e9bfc5c1aee4078005c0774a3e6fa23949cc4d8fbad3 |
| File Size | 158856 bytes |
| File Type | Win32 DLL |
Table 1 - Malware Basic Properties
Please refer to our previous blog post, "Detect SnappyClient C&C Traffic Using PacketSmith + Yara-X Detection Module", for more information about how to use the PacketSmith "yara" detection module.
The malware is packed. This blog post primarily focuses on the custom TCP packet payload.
Shulfar communicates with the C&C server 107.163.56.251 over TCP/6658. It sends a packet similar to the following:
Figure 1 - Shulfar Encrypted Checkin Packet (TCP payload)
I've made the pcap available for download via Netomize's official repo (RFiles), Shulfar packet capture (4,678 bytes).
The packet includes system-specific information and some hardcoded values. Before being sent to the C&C server, it is encrypted with a fixed one-byte key, specifically 0x64. The decrypted packet corresponds to:
Figure 2 - Shulfar Decrypted Checkin Packet (TCP payload)
The structure of the packet in Figures 1 and 2 is as follows:
| Offset | Length | Description |
|---|---|---|
| 0x00 | 0x40 | Processor name, and if unsuccessful, set it to "Find CPU Error". |
| 0x40 | 0x20 | Total physical memory in MB (format, "%u MB"). |
| 0x60 | 0x20 | OS version (format, "Win %s SP%d"). |
| 0x80 | 0x20 | Unique constant identifier ("10151338"). |
| 0xA0 | 0x80 | If the file "C:\qylxnhy\lang.ini" exists and includes the substring "http://" but not "search", retrieve the file's buffer (with a length of 253 bytes). Otherwise, return the hardcoded C&C server address "http://107.163.56.250:18963/main.php". |
| 0x120 | 0x04 | System default UI language ID (for example, 0x0409 en-US). |
| 0x124 | 0x04 | Fixed value 0xffffffff (-1). |
Table 1 - TCP Packet Structure
Note - 1: The packet has a fixed length of 296 bytes. If the data at offset 0xA0 is read from the "lang.ini" file and exceeds 128 bytes, the file's content will overflow into the rest of the packet's buffer.
The following high-level pseudocode snippet illustrates how the packet is constructed:
// function rva 0x10005753 (for the dumped DLL with fixed IAT)
int __cdecl collect_sys_info_checkin_pkt(packet_cinfo *pkt_data)
{
int ram_size;
LANGID SystemDefaultUILanguage;
BYTE Data[260];
CHAR server_addr_from_file[253];
CHAR SubKey[48];
_MEMORYSTATUSEX Buffer;
DWORD Type;
DWORD cbData;
HKEY phkResult;
qmemcpy(SubKey, aHardwareDescri, sizeof(SubKey));
if ( RegOpenKeyExA(HKEY_LOCAL_MACHINE, SubKey, 0, KEY_ALL_ACCESS, &phkResult) )
{
j_strcpy(pkt_data, aFindCpuError);
}
else
{
Type = 4;
cbData = 200;
reg_query_value(phkResult, aProcessornames, 0, &Type, Data, &cbData);
reg_close_key(phkResult);
j_strcpy(pkt_data, Data);
}
// copy processor name
copy_buffer_skip_leading_wspaces(pkt_data);
get_os_version_info(pkt_data->os_ver_info);
Buffer.dwLength = 64;
GlobalMemoryStatusEx(&Buffer);
ram_size = convert_to_mb(Buffer.ullTotalPhys, 20u);
wvsprintfA_0(pkt_data->physical_mem, aUMb, (ram_size + 1));
j_strcpy(pkt_data->identifier, a10151338); // "10151338"
SystemDefaultUILanguage = GetSystemDefaultUILanguage();
server_addr_from_file[0] = 0;
pkt_data->lang_id = SystemDefaultUILanguage;
memset(&server_addr_from_file[1], 0, 252u);
pkt_data->const_delimiter = 0xFFFFFFFF;
if ( check_lang_ini_file(server_addr_from_file, 256u) )
{
return j_strcpy(pkt_data->server_addr, server_addr_from_file);
}
else
{
// "http://107.163.56.250:18963/main.php"
return wvsprintfA_0(pkt_data->server_addr, aS, aHttp1071635625);
}
}
And the encryption algorithm pseudocode is:
int __cdecl encrypt_traffic(int data, int dsize, uint8_t key)
{
int result;
int i;
char edata;
result = key / 254;
for ( i = 0; i < dsize; *result = edata )
{
result = i + data;
edata = key % 254 + 1 + (*(i + data) ^ (key % 254 + 1));
++i;
}
return result;
}
The initial key provided to the encrypt_traffic function is 0x63, and the final derived key is 0x64. This means the key has to be between 1 and 254. Therefore, to decrypt the traffic, you subtract 0x64 from every byte and XOR it with the same key (0x64).
The server response is neither encrypted nor compressed; the malware anticipates receiving a fixed-size packet of 188 bytes (0xbc) for control commands from the server.
Detection Logic
With the packet structure documented and the encryption algorithm understood, we can use Yara-X operators to simulate the decryption process on the packet's payload, searching for fixed bytes at specific offsets and ranges. We look for " MB\x00" and "Win " among these fixed bytes. Additionally, we verify that the packet's payload size is set to 296. While we could include more atomic indicators in the detection logic, it's unnecessary. The two fixed content matches we check for at specific offsets and within a specific range are sufficient to prevent any potential false positives in their encrypted format.
We could derive a rule similar to the following:
rule shulfar_malware_encrypted_tcp_packet
{
meta:
description = "Detecting Shulfar malware encrypted checkin TCP packet"
filter = "Frames (frames:)"
sha1 = "e46e6ea272ae628d15bfb7b71ff40e3950fd2e85"
reference = "https://www.splunk.com/en_us/blog/security/detecting-ghost-rat-cloverplus-adware-loader-analysis.html"
author = "Netomize"
date = "04/24/2026"
condition:
tcp.is_set
and
math.in_range(port.src, 1024, 65535) // ephemeral ports
and
tcp.data.size == 296
and
for any key in (0..255) :
(
for any pos in (tcp.data.offset + 64..tcp.data.offset + 96) :
(
(
// check for ' ' (0x20)
(((uint8(pos) - ((key % 254) + 1)) & 0xff) ^ ((key % 254) + 1)) == 0x20
and
// check for 'M' (0x4D)
(((uint8(pos + 1) - ((key % 254) + 1)) & 0xff) ^ ((key % 254) + 1)) == 0x4d
and
// check for 'B' (0x42)
(((uint8(pos + 2) - ((key % 254) + 1)) & 0xff) ^ ((key % 254) + 1)) == 0x42
and
// check for null byte (0x00)
(((uint8(pos + 3) - ((key % 254) + 1)) & 0xff) ^ ((key % 254) + 1)) == 0x00
)
)
and
for any pos in (tcp.data.offset + 96..tcp.data.offset + 128) :
(
(
// check for 'W' (0x57)
(((uint8(pos) - ((key % 254) + 1)) & 0xff) ^ ((key % 254) + 1)) == 0x57
and
// check for 'i' (0x69)
(((uint8(pos + 1) - ((key % 254) + 1)) & 0xff) ^ ((key % 254) + 1)) == 0x69
and
// check for 'n' (0x6e)
(((uint8(pos + 2) - ((key % 254) + 1)) & 0xff) ^ ((key % 254) + 1)) == 0x6e
and
// check for ' ' byte (0x20)
(((uint8(pos + 3) - ((key % 254) + 1)) & 0xff) ^ ((key % 254) + 1)) == 0x20
)
)
)
}
The detection rule ensures the ephemeral source port range and packet data size are enforced. To verify the fixed content matches " MB\x00" and "Win ", regardless of any specific encryption key, we iterate over all possible keys and establish two loops to check each content match at their respective offsets in the packet.
Conclusion
This article demonstrates the capabilities of the PacketSmith "yara" detection module in identifying encrypted traffic without relying on a specific key by simulating the decryption algorithm for all possible keys. Additionally, we have detailed the construction of Shulfar's custom C&C traffic over the TCP channel.
Author: Mohamad Mokbel
First release: April 24, 2026

