First of all, I’m pretty proud to serve the first forensic blogpost @ cyber.wtf.
This blog post is about the detection of Command and Control (C2) traffic and how attackers use frameworks to maintain remote access through common protocols like HTTP/S, DNS, or SMB.
The experiment we conducted involved installing these malicious tools within a virtual environment and utilizing Suricata and Wireshark to analyze the traffic in detail.
The main question behind this was essentially:
“Does Suricata detect C2 traffic (out of the box)?”
- Introduction
- Open Source NIDS
- Virtual Lab
- Diving Down into the C2 Network Traffic with Wireshark
- Conclusion
C2 Basics
First things first, what is ‘Command and Control’ (Traffic)? Command and Control, or C2, is a type of remote access solution, but not the kind of software installed by local IT staff. Rather, it is a method cybercriminals utilize as an alternative way to gain or maintain a foothold, move laterally through the network, or interact with compromised hosts.
There are many common open-source C2 frameworks seen in the wild, such as Havoc or Sliver, which are available on GitHub. On the commercial side, you may come across Cobalt Strike by Forta or Brute Ratel by Dark Vortex. Cobalt Strike, which is supposed to be used in penetration testing scenarios, is often abused by cybercriminals in ransomware incidents.
Beacons are not typically executed by clicking on a malicious email attachment; rather, this usually occurs after the initial compromise of a single system. The main components are the implant or beacon, which runs on the compromised host, and the C2 server, which has the capability to control the compromised host. To achieve this goal, the beacon and the C2 server communicate within a typical client-server model. The system initiating the session is the compromised host. The main reason behind this is that outgoing internet traffic is not filtered or monitored as strictly as incoming traffic.
(Ab)used C2 Protocols
To make it even more complicated for blue teams, the traffic between the beacon and the C2 server is hidden within typical network protocols that are prevalent in a corporate network. Abused protocols include:
- HTTP (Hypertext Transfer Protocol)
- HTTPS (Hypertext Transfer Protocol Secure)
- DNS (Domain Name System)
- (m)TLS (Transport Layer Security)
- VPN (Virtual Private Network)
- SMB (Server Message Block)
Unfortunately, this is not the end of the story. Many more protocols can be exploited to tunnel C2 traffic. The main features the protocol must meet are:
- A bi-directional protocol, or alternatively, a P2P protocol
- A native Windows API should be available to handle the traffic
- The necessary permissions to use the aforementioned Windows API should be as minimal as possible
For example, ICMP matches all these requirements and can therefore also be exploited. As previously mentioned, most of the abused protocols appear regularly in network traffic, making them less suspicious when logged as outgoing traffic. One exception is SMB, which is unusual and should generally be blocked. DNS might also raise concerns for a firewall admin if it appears in an unusual manner. For instance, if there is an internal DNS resolving all internal requests but one client is using a public DNS, this could raise suspicion. However, ICMP, HTTP/S, or VPN/TLS traffic would typically not raise any concerns if logged in the outgoing firewall policy.
This sets the stage for the next section, where the open-source NIDS/NIPS Suricata is explained in detail, so we may detect C2 traffic even without a NGFW in our company network.
There are many tools you might encounter when searching for “IDS” (Intrusion Detection System) on popular search engines. IDS is a general term that also encompasses HIDS (Host Intrusion Detection Systems). However, in this case, we are specifically looking at NIDS (Network Intrusion Detection Systems) or NIPS (Network Intrusion Prevention Systems).
Although it is not entirely accurate, we will use the term NIDS to refer to both NIDS and NIPS collectively. Since we will not be utilizing any features that intercept traffic, using the term NIDS will suffice for our purposes.
If you’re interested in a more detailed explanation of what defines a NIDS, including its main components, I recommend an excellent book published in 2005: Foundations of Security Analysis and Design III.
Snort vs. Suricata
Snort is the oldest NIDS, dating back to before the 2000s and maintained by Cisco since 2013. It offers two main rulesets: the “Community Ruleset,” which, as you might expect, is free, and an extended “Subscriber Ruleset,” which requires a paid subscription.
Suricata had its first release in 2009 and is maintained by the “Open Information Security Foundation,” financially supported by Trellix (formerly known as FireEye) and several other commercial NGFW (Next Generation Firewall), NIDS providers that integrate Suricata into their products. Suricata also has two main rulesets: the free “ET Open” and the paid “ET Pro” subscription.
Even though Snort and Suricata are both NIDS/NIPS with nearly identical functionality, there are some key differences upon closer examination. A few years ago, if you had asked someone working with NIDS, they might have said, “rely on Suricata if you expect high traffic volumes.” This was because Snort lacked multi-threading or multi-core support, a feature that Suricata has had since its inception. However, this is no longer the case, as Snort 3 now includes multi-threading capabilities.
I came across an interesting paper from 2023, A comparative analysis of Snort 3 and Suricata, which benchmarked Snort and Suricata under high traffic loads.
TL;DR: CPU and memory usage were nearly the same, but Snort showed slightly higher packet loss.
As a result, Suricata exhibited a marginally better false-negative rate compared to Snort.
Here are some specific areas where the two systems differ and merit closer evaluation:
- Protocol Support: Suricata supports more protocols than Snort.
- Log Output Format: Both Snort (starting with version 3) and Suricata support (EVE) JSON as a log output format.
- Snort 2, by contrast, uses Unified2, a proprietary format that is now deprecated.
- Ruleset Updates: Updating rulesets in Suricata is more straightforward.
- In Snort, you may need to write your own scripts for automation or use third-party tools like “PulledPork.”
- As a result, implementing CTI (Cyber Threat Intelligence) is slightly less cumbersome with Suricata.
- Alert Management: Neither system includes built-in alert management.
- Anomaly Detection: Neither system supports statistics-based anomaly detection out of the box.
Personally, I find Suricata to be more modern and interoperable, so I focused my further testing exclusively on Suricata.
Overview of ET Open
In this section, we will provide a quick overview of “ET Open,” the free ruleset included with Suricata. First of all, ET Open is updated regularly, so any numbers mentioned below may not apply in the future.
ET Open contains around 50,000 rules, though a small number of them are commented out and will not be loaded, effectively making them absent.
Unfortunately, ET Open lacks structure, so analyzing it often requires extensive grepping.
Approximately 8,300 rules include terms such as classtype:command-and-control
, Command and Control
, CnC
, c2 communication
, or beacon
.
Of these, about 1,700 are commented out, leaving roughly 6,500 active rules intended to detect C2 activity.
The protocols these rules cover, in descending order, are: HTTP, DNS, TLS, TCP, UDP, SMTP, ICMP, FTP, SMB, TCP-Stream, and IP (General).
One thing we noticed is that the DNS rules are very specific, matching on known malicious domains. However, we did not find any rules that take a more general approach to detecting suspicious parameters within the DNS protocol. The same pattern holds true for the TLS rules: half of them (around 700 in total) match on known malicious names in the TLS Server Name Indicator (SNI), while the other half target the Subject field within TLS certificates. These rules are effective for catching low-hanging fruit but are unlikely to remain effective in the long term, as malicious domains (and their corresponding certificates) change frequently. Furthermore, with TLS 1.3, it is no longer possible to inspect the certificate (e.g., SNI or Subject) because this data is now encrypted. Looking ahead, rules structured this way will have limited effectiveness in the future.
In conclusion, we have Suricata as our NIDS and a collection of rules designed to detect C2 activity. Let’s put them to the test in the next section.
Before we can start, we need machines, a lot of virtual machines. We installed the following systems:
- Victim (Windows): Runs the C2 beacons
- Victim-Pivot (Windows): Runs the SMB beacon
- Attacker (Debian): C2 Server
- Suricata (Ubuntu): Router and Suricata
- dnscat2 (Ubuntu): C2 Server for DNS
All in all, the virtual network is configured as shown in the following figure:
You may wonder why there is a Victim-Pivot, running the SMB beacon, sitting in the same network as the Attacker. This is due to the specific usage of the SMB beacon, which does not communicate directly with the C2 server. Instead, in Sliver’s implementation, it serves as an indirect method of communication by relaying through another beacon to a secondary system. This process is illustrated in the following figure:
To keep things simple, the Victim-Pivot is located in the same network, ensuring that Suricata remains positioned between the C2 server and the SMB traffic.
A few additional modifications:
- Disable Windows Security Features:
- Disable Windows Defender RTP to allow the beacons to execute without interference.
- Set LimitBlankPasswordUse to 0 to allow SMB NULL Sessions.
- Initial Suricata Configuration (suricata.yaml):
- Set HOME_NET for the NIC at LAN1.
- Include ET Open (suricata.rules) in the rule-files section.
- Initial Configuration of the Suricata Host:
- Configure iptables firewall rules.
- Configure iptables NAT rules.
- Prerequisites for C2 via DNS:
- Purchase a domain.
- Set Name Server (NS) entries for the domain, pointing to the dnscat2 C2 server.
- Set TTL to 5 minutes.
- Verify that DNS packets are reaching the C2 server.
Now, we can start with the tools to generate the C2 traffic, for which we chose the following tools:
Havoc and Sliver are C2 frameworks that we will use to generate C2 traffic. Both of them essentially perform the same function but exploit different protocols. This way, we avoid putting all our eggs in one basket. For this reason, we also tested a smaller GitHub project (dnscat2), which is not a full-fledged C2 framework but rather an attack tool that allows us to control the beacon via DNS.
The following protocols were abused:
- HTTP/S (Sliver)
- (m)TLS (Sliver)
- WireGuard VPN (Sliver)
- DNS (dnscat2)
- HTTP and SMB (Havoc)
Generating Beacons
Sliver
Sliver is an interactive, console-like application with a beautified output, making it appear similar to a GUI.
Generating beacons with Sliver is straightforward and can be summarized as:
generate beacon --[protocol] [c2-ip]:[port] --save [path]
For example:
generate beacon --http 172.16.0.100:80 --save /home/http.exe
This command generates an HTTP beacon (by default for Windows) that contacts the C2 server at 172.16.0.100:80.
The process worked by default for all the beacons we used.
Some default parameters for the beacon are preconfigured, such as:
- A timeout: the duration (default 60 seconds) the beacon waits before connecting to the C2 server.
- A jitter: a random variation in the connection time, designed to avoid statistical detection.
After generating the beacon, the listener simply needs to be started by specifying the protocol, such as http
for the HTTP listener.
Once the beacon starts, it will appear in the interactive console, allowing interaction.
Havoc
Havoc features a GUI reminiscent of the Cobalt Strike console, with similar base functionalities. While Havoc lacks post-exploitation features, the initial look and feel are nearly identical. The steps to generate a beacon are straightforward:
- Start a listener for the appropriate protocol (see figure Havoc HTTP listener).
- Generate a payload (see figure Havoc HTTP payload generation).
- Execute the beacon on the target host.
Once the beacon starts, it will appear in the GUI console, allowing interaction:
dnscat2
dnscat2, like Sliver, is a console-like application. Its configuration differs slightly: the beacon executable needs to be built with Visual Studio and then executed via cmd using the C2 domain as a parameter:
Once the beacon starts, it will appear in the GUI console, allowing interaction.
There are many ways, depending on the tool and the (ab)used C2 protocol, to customize the traffic. However, we did not make any such customizations. As you will see in the next section, the traffic already looks quite sophisticated, which, I admit, surprised me as well.
F**k around
So now, we have several beacons running. What do we do next? Discover the network, escalate privileges, exfiltrate data, encrypt all the data, extortion. This is exactly what a cybercriminal would do in the same situation.
As a blue teamer, you may think about how to generate the most diverse traffic possible. Summarizing my approach, we performed the following actions, which may result in noticeable changes in the traffic sent or received:
- Beacon session initiation
- No interaction (beacon idle traffic)
- Executing commands with varying response sizes, such as
pwd
orpslist
- Uploading dummy ZIP archives to the victim machine with different file sizes
- Beacon session termination
These same interactions were carried out for all beacons, and the corresponding network traffic was captured. This traffic will be analyzed to identify distinctive characteristics suitable for use in Suricata detection rules. For a deeper dive, we’ll explore this in the section Diving Down into the C2 Network Traffic with Wireshark.
Results
There were no detections for all the mentioned beacons, except for the following:
- HTTP (Sliver): SURICATA Applayer Mismatch protocol both directions
- HTTP (Havoc): ET MALWARE Havoc Framework Header in HTTP Response
In the first case, this is not a real detection but rather an alert indicating that Suricata’s protocol detection has identified a mismatch in both directions. This could be an indicator that something is wrong with the traffic. However, in a real-world scenario, this alert would likely not be forwarded or analyzed further.
The second detection is more concerning, as it explicitly names the framework being used. Let’s take a closer look at the Suricata rule that triggered this detection, specifically the Havoc detection:
alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Havoc Framework Header in HTTP Response";
flow:established,to_client;
http.header_names; to_lowercase; content:"x-havoc|0d 0a|"; fast_pattern;
reference:url,github.com/HavocFramework/Havoc/blob/main/Teamserver/profiles/havoc.yaotl;
reference:url,twitter.com/MichalKoczwara/status/1652986620658761729;
classtype:trojan-activity; sid:2045270; rev:2;
metadata:attack_target Client_Endpoint, created_at 2023_05_01, deployment Perimeter, deployment SSLDecrypt, confidence High, signature_severity Major, updated_at 2024_04_26;)
I made some edits to the rule for better readability.
The rules as shown will not work if you simply copy and paste it.
For reference, you’ll find the Suricata rules mentioned below in our public GitHub repository (Suricata-C2).
A rule consists of the following three elements:
- Action: What should be done if the signature matches?
In this case, it’salert
, but you could also usedrop
,reject
, and others. - Header:
Defines the protocol, IP addresses (or placeholders), ports, and direction.
Here, it’shttp $EXTERNAL_NET any -> $HOME_NET any
, so any HTTP traffic no matter on which port coming from extern going to the LAN. - Options:
This is essentially the signature itself (what exactly you’re looking for) along with optional metadata like references or a classtype to provide additional context.
Let’s dive deeper into the signature details:
flow:established,to_client;
The flow keyword in Suricata identifies packets belonging to the same connection (e.g., same source, destination, protocol, source port, and destination port). For TCP, this refers to the TCP tunnel itself. For UDP, the tuple defines the flow. Here, the flow is established, meaning there’s already an active TCP tunnel, and the directionto_client
specifies traffic heading to the client.http.header_names; to_lowercase; content:"x-havoc|0d 0a|"; fast_pattern;
This line uses the HTTP-specific keywordhttp.header_names
, which inspects HTTP headers. Protocol-specific keywords are available and allow inspection of specific fields within the protocol, much like analyzing protocol fields in Wireshark. The rule checks whether the header containsx-havoc
, followed by a carriage return and line feed (|0d 0a|
), which marks the end of the header.- Reference, Classtype, Metadata:
These elements provide additional context or information about the detection. Most of this is self-explanatory, offering details such as related links and the severity of the alert.
For more detailed information on Suricata rules and keywords, you can refer to the documentation: 8.1 Rules Format - Suricata 7.0.7 documentation
While experimenting, the executables were shared via a shared folder in VMware Fusion.
In the meantime, my local antivirus (on the hypervisor) reacted:
“You had my curiosity. But now you have my attention.”
In this section, we will analyze the sniffed network traffic. The goal is not to reverse-engineer the encoding but rather to identify distinctive characteristics suitable for use in Suricata detection rules. All detection rules presented below have been modified for readability, so please do not simply copy and paste them.
HTTP (Sliver)
The C2 traffic itself, in its default configuration, is already pretty stealthy.
There are HTTP GET
and POST
requests, as you would expect while browsing a website.
Furthermore, the URI endpoints change with each request, but within a limited set.
Over time, you will see the same URIs recurring, but with changing parameters.
Additionally, the HTTP header contains a cookie that, as we tested, changes with each new (beacon) session—again, within a limited set.
Common cookies include AWSALBCORS=[...]
, which is typically used by AWS (Amazon Application Load Balancer).
I also noticed other common cookies such as SSID
or APISID
(both related to Google Ads optimization).
If you’re interested, check out the source code of the HTTP beacon generation (server/configs/http-c2.go). You’ll see that most of it is fairly generic. Therefore, rules matching on these generic (and often common) parameters would likely result in false positive detections.
Fortunately, HTTP isn’t encrypted, so we can delve into the transferred “HTTP” data:
As we can see, the data is encoded in several ways, which are described in more detail in this blog post from Immersivelabs. From my perspective, reverse-engineering the transferred data is a completely separate topic, and we won’t explore it in this post. Instead, we ask ourselves whether there are characteristics suitable for detection rules.
Interestingly, for some reason, English words are transferred every time a larger amount of data is sent to the C2 server. Without diving too deeply into Suricata and LUA scripting (an advanced method of defining detection logic within Suricata), I couldn’t find a way to implement detections for most of the used encodings.
Did you notice something? If not, look again at the HTTP encoding sample 3. For some reason, Sliver encodes data into English words at times. From my observations, this happens when larger amounts of data are transferred from the beacon to the C2 server. Digging into the source code, we can find a wordlist (EnglishDictionary).
My Suricata rule to detect this looks like this:
alert http any any -> any 80 (msg:"C2-Sliver: Suspicous Keywords detected in HTTP POST";
http.method; content:"POST";
http.uri; content:"php"; nocase;
file.data; pcre:"
/\x49\x43\x54\x45\x52\x49\x43\x41\x4c|
\x53\x54\x41\x52\x46\x49\x53\x48|
\x47\x55\x45\x53\x54\x49\x4e\x47|
\x4f\x56\x45\x52\x4f\x52\x47\x41\x4e\x49\x5a\x45|
\x43\x4f\x4e\x46\x45\x52\x4d\x45\x4e\x54\x53/";
classtype:trojan-activity; sid:1000400; rev:1;)
You may notice that we only used five random words (ICTERICAL, STARFISH, GUESTING, OVERORGANIZE, and CONFERMENTS) that were observed previously and converted to hex. To gain more confidence, you could implement a LUA script to check for the complete dictionary mentioned above. In my case, the detection rule was triggered within just a few interactions with the beacons.
HTTP (Havoc)
Havoc’s HTTP C2 traffic, in its default configuration, is far less sophisticated compared to Sliver. Take a look for yourself:
Notable characteristics:
- Only HTTP POST
- Request URI:
/
- Content-Type:
*/*
As mentioned, these are default values in Havoc, but you may use them to catch low-hanging fruit:
alert http any any -> any 80 (msg:"C2-Havoc: Suspicious HTTP Default Parameters";
http.method; content:"POST";
http.uri; content:"/"; urilen:1;
http.content_type; content:"*/*";
threshold:type both, track by_src, count 10, seconds 60;
sid:1000404; rev:1;)
HTTP POST (only) traffic may occur when using an API, but it’s not something you would typically expect from normal user browsing activity. To catch HTTP POST-only connections, we need to use the flowbits function in Suricata. Matching for HTTP POST without considering the flow’s context will, of course, result in false positives. Here’s how you can structure it:
- (1) Set a flag if there is an established flow containing an HTTP POST request.
- (2) Set a flag if there is an established flow containing an HTTP GET request, and unset the first flag.
- (3) Check if flag (1) is set and flag (2) is not set, and apply a threshold to reduce sensitivity to false positives.
(1.)
alert http any any -> any 80 (msg:"HTTP POST flow detected";
flow:to_server,established;
http.method; content:"POST";
flowbits:set,http_post; flowbits:noalert;
sid:1000401; rev:1;)
(2.)
alert http any any -> any 80 (msg:"HTTP GET request in flow detected";
flow:to_server,established;
http.method; content:"GET";
flowbits:set,non_http_post; flowbits:unset,http_post; flowbits:noalert;
sid:1000402; rev:1;)
(3.)
alert ip any any -> any any (msg:"Flow with only HTTP POST requests";
flow:to_server,established;
flowbits:isset,http_post; flowbits:isnotset,non_http_post;
threshold:type both, track by_src, count 10, seconds 60;
sid:1000403; rev:2;)
Summarizing, this combination of flowbits triggers, if 10 POST requests have been seen within 60 seconds without any GET requests in the same connection.
HTTPS / mTLS
HTTPS and TLS are (sadly) pretty boring to inspect. One can observe the TCP tunnel; after that, the TLS tunnel is established (Client Hello, Server Hello, and so on), and then TLS-encrypted data. Sliver uses TLS 1.3, so you can’t even inspect the provided certificate to find suspicious names or parameters.
Theoretically, HTTPS should have been intercepted and analyzed, but this would blow my timeframe. I guess we will take a closer look at this specifically in the future.
With mTLS, there is no interception on the network level because both the client and server are validating their certificates. This could be another way of disrupting the attacker, because the connection would fail if you try to inspect the traffic, and the connection between the beacon and C2 server isn’t working. There are other possibilities if you’re controlling the client and/or C2 server, but at this point, I haven’t evaluated them, so I won’t describe them in more detail.
So, to (possibly) detect this kind of C2 traffic without interception, there are only a few options:
- Anomaly detection (with Suricata and third-party tools)
- Generic domain (DNS) or IP reputation checks
- Cyber Threat Intelligence (CTI)
WireGuard VPN
Sliver’s WireGuard (VPN) implementation was very strange. At first, we suspected HTTPS or mTLS-like traffic but were surprised. Wireshark detected this traffic as DNS, with malformed queries:
Either this is a UDP tunnel embedding the WireGuard tunnel, or it’s a point-to-point DNS tunnel. Wireshark detects it as (malformed) DNS, but specific DNS fields are (more or less) meaningfully set. But that could also just be pure coincidence.
I don’t know where to start, but the sniffed packets had nothing to do with DNS, and any security appliance should sound the alarm (very loudly).
- Direction (beacon -> C2 || C2 -> beacon) doesn’t have any impact, it’s always a query.
There should be no DNS querys to specific hosts on the internet, neither should they sent “queries” back into the infrastructure. Preferably, it should be only your own DNS server as target for DNS queries or well known public DNS servers. Query ID
isn’t unique.
I observed0x0100
,0x0200
, or0x0400
. DNS query IDs are more or less unique (of course, they will reappear or be reused, but not within seconds), and ideally, there is only one query and one response for the same ID.- High numbers for “Question Count” (
QDCOUNT
) and “Answer Count” (ANCOUNT
).
These counters represent the number of requested hostnames or IP addresses and their corresponding answers. Normally (depending on the DNS implementation), you’d expect either “one” or a very small number when multiple queries are combined. As seen in C2 via WireGuard, the numbers are ridiculously high (15332
), which I doubt you’d see in legitimate DNS queries. The same applies to “Answer Count.”ANCOUNT
does not have to matchQDCOUNT
; depending on whether NXDOMAIN is returned or multiple entries exist for the same hostname (as with MX records), discrepancies are common. However, the number here is unbelievably high too (1928
). - Empty Query Name (
QNAME
).
As you may notice, the query name states<Root>
, which I believe is a misinterpretation of the set bit. Actually, it’s00
, which you would not see in normal DNS queries, as there should always be a name set. Furthermore, theQNAME
field’s first bit represents the length of the actual name. - Empty Query Type (
QTYPE
).
There is no emptyQTYPE
, and0
is simply not in use. You would expect1
for an A record,12
for a PTR, and so on. - Non-
IN
Query Class.
Much likeQTYPE
, there are a few reserved values, but anything other thanIN
for the DNS Query Class is suspicious.
Finally, we have a bunch of suspicious values to filter for.
Sounds good, right?
Spoiler: No.
As we mentioned, Wireshark flags these DNS queries as malformed, so will Suricata even detect them as DNS?
Rules to match this specific traffic must be written as UDP rules, which makes it a bit complicated.
If the traffic is not recognized as DNS, you can’t use the protocol-specific keywords.
Instead, you must count the bytes within the packet to look for specific values.
This doesn’t sound too bad, but keep in mind that some fields have variable lengths, making it nearly impossible to write rules based on many of the distinctive characteristics we found for Sliver’s WireGuard.
However, some earlier fields in the UDP payload are not variable and are always set properly. For example, a rule might look like this:
alert udp any any -> any 53 (msg:"Sliver: Possible DNS Tunnel detected, re-use of suspicious dns.id 0400";
content:"|04 00|"; offset:0; depth:2;
threshold:type both, track by_src, count 5, seconds 60;
classtype:trojan-activity; sid:10000027; rev:1;)
As you can see, within the UDP packet’s payload, starting at offset 0
(the beginning of the payload) and extending to a depth of two bytes, we check for 04 00
, the suspicious DNS Query ID.
To avoid mismatches in any UDP packet, we also use a threshold.
As seen in C2 via WireGuard 04 00
appears most often.
SMB
The SMB traffic did not show anything suspicious; the traffic appears as you would expect.
But do you know how the communication is implemented?
It’s pretty simple: over named pipes and interprocess communication (IPC
).
In my opinion, this is one of the noisiest ways to perform C2 for several reasons.
First, there should be no illogical SMB traffic inside your network (e.g., due to firewall blocks), especially via IPC
.
Second, named pipes in general are heavily inspected by EDR solutions.
As more of a showcase than a practical detection, we wrote the following rule to detect when IPC
is used:
alert smb any any -> any 445 (msg:"SMB Tree Connect Request to IPC$ detected";
smb.named_pipe; content:"IPC"; nocase;
sid:10000040; rev:1;)
You can also implement a more general detection rule that checks for suspicious names used in named pipes, such as psexec
(the default name for psexec).
For reference, you may consult this GitHub list, which includes more suspicious names.
DNS
DNS is one of the most essential network protocols, responsible for resolving names to addresses. Ever heard of major internet outages? Most of the time, it’s either a cable damage or DNS issues.
There are different types of DNS resource records (RR
), for example:
A
(maps a domain name to an IPv4 address)MX
(points to a domain’s mail server address)
As a quick recap, this is how DNS queries work:
- The client sends a DNS query to its resolver (typically specified in the network interface settings, often the same as the default gateway).
- The resolver checks if the queried name is in its cache.
- If not, the resolver sends a query for the authoritative name server (NS) of the queried domain to the NS root server responsible for the queried top-level domain (like
.de
for Germany). - The NS root server responds to the resolver.
- The resolver queries the authoritative NS for the original query and sends the answer back to the client.
DNS became one of the most fascinating protocols I’ve explored, especially when abused for C2. In simple terms, the communication is hidden within the DNS requests and responses.
I’m not entirely sure if the query (5724015bf0ece4379bcf010016e5210323.see-two.xyz: type TXT, class IN
) is also part of the communication or if it depends on the implementation of the tool or framework used.
My guess is that the query changes every time so the DNS server (8.8.4.4
) cannot deliver cached entries.
For every query, the authoritative NS is contacted by the DNS server (in this case, the C2 server).
At the very least, the DNS answer is abused to share data (b12c015bf0274b170a46c3ffff52cc7b96
).
DNS is not a protocol designed to share large amounts of data. In fact, it’s pretty inefficient because a high volume of “queries” and “answers” is required. As shown in the following plot, roughly seven to eight requests occur every second during interaction with the beacon:
For future reference, here’s the statistics overview of the same pcap:
Summarizing Key Indicators of Suspicious Behavior
There are several indicators pointing to suspicious activity. It’s hard to determine if it’s C2 or data exfiltration, but from my perspective, such anomalies (even if they fit within protocol parameters) should be monitored:
- High count of DNS queries.
In a timeframe of about 600 seconds, 1,300 queries were sent—an average of 2 queries per second. However, as shown in the C2 via DNS - Query plot, there are bursts for some minutes, resulting in up to 8 requests per second, which is highly unusual for a single client. - Unusual
RR
derivation.
Looking at the “query type” section in C2 via DNS - Statistics, you’ll notice the presence of onlyCNAME
,MX
andTXT
records. Additionally, the quantities of theseRR
s are nearly identical. Normally, one would expectA
orAAAA
records to dominate, with only a few otherRR
s. - Strange and changing subdomains.
As mentioned earlier, the subdomain may be part of the communication channel, changing with every request. While this prevents caching (and isn’t inherently suspicious), the names are not human-readable and appear to be scrambled hex text. - Suspicious answer data.
The DNS responses seem to contain scrambled hex text, deviating from typical DNS behavior. - Suspicious answer data length.
While normal DNS responses vary in length, the observed answers frequently approach the maximum allowed length of 255 characters (239
was the maximum observed).
Unfortunately, not all indicators can be directly translated into Suricata rules. Some require additional correlation logic, either through LUA scripting or third-party tools. However, for the following scenarios, we created Suricata rules:
- (1) High volume of DNS queries per host
This rule detects DNS queries (dns.opcode:0
), combined with a threshold for a single source. - (2) Long DNS query length (suspiciously long subdomains)
This rule is similar to the first but also checks if the query length exceeds 25 characters. Based on real-world data, you may need to fine-tune this rule to avoid false positives. (For context, the smallest query we observed was 46 characters, so there’s some flexibility here.) - (3) Suspicious answer data length
Unlike the other two rules, this rule’s threshold observes data per destination. In my lab, responses exceeding 100 bytes triggered no false positives. However, if the answer includes multipleMX
records, this limit can be easily exceeded. Thresholding and fine-tuning are likely required for practical use.
(1)
alert dns any any -> any 53 (msg:"Multiple Huge amount of DNS-Queries possibel C2-Channel or exfiltration detected";
dns.opcode:0;
threshold:type both, track by_src, count 30, seconds 60;
sid:1000020; rev:1;)
(2)
alert dns any any -> any 53 (msg:"Multiple DNS Query Length exceeds 25 characters, possible C2-Channel or exfiltration detected";
dns.opcode:0; dns.query; bsize:>25;
threshold:type both, track by_src, count 10, seconds 60;
sid:1000021; rev:1;)
(3)
alert dns any 53 -> any any (msg:"Multiple DNS Answer to single Host exceeds 100 Byte, possible C2-Channel detected";
dsize:>=100;
threshold:type both, track by_dst, count 10, seconds 60;
sid:1000022; rev:1;)
So, diving back up, let’s summarize what we found in the lab.
What did we want to show with this blog post?
- We want to show that Wireshark doesn’t byte (yes, that’s intentionally written like that).
- We want to demonstrate how C2 communication is implemented across different protocols and where it might appear.
What do we learn from this?
ET Open did not detect much of the intentionally generated C2 traffic. This may depend on the tool or C2 framework used, so these results may vary. You may notice that we pointed out many suspicious characteristics within the sniffed traffic, but not all of them can be directly used to create Suricata rules. Either they would result in a large number of false positives (which we definitely want to avoid), or you would need to rely on LUA scripting or third-party tools to handle the necessary correlations.
Encrypted traffic also poses a significant challenge since we can’t look inside it. To address this, you would either need to intercept it (if feasible) or implement (statistical-based) anomaly detection. Alternatively, you could verify elements like JA3S hashes against CTI.
Let’s not forget that tools like Suricata are not solutions in themselves; they are tools to solve problems. (Shoutout to Landi, from whom I borrowed this proverb.) Personally, I believe there should be a ruleset for detecting unusual anomalies like the ones I highlighted in the DNS section. Staying on the topic of DNS, I noticed that all detection rules in ET Open focused on explicit domain names. Not a single rule examined packet sizes or similar characteristics.
Additionally, we think detection rules should be fed from diverse sources:
- A solid ruleset that is “always there” and regularly updated,
- Your own research, as we conducted in the lab,
- Community or threat research blog posts that can be leveraged to detect malicious behavior.
If you’ve made it all the way here, you have my respect, because this blog post turned out to be a bit longer than initially planned - who could have guessed?
Again, you’ll find the Suricata rules we created on GitHub, which is a repository within the G DATA Advanced Analytics organization.
Kind regards, Andreas