Skip to main content

Command Palette

Search for a command to run...

Detect Shulfar Malware Encrypted TCP C&C Traffic Using PacketSmith Yara-X Detection Module

Published
7 min read
N

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