Randomised C2 traffic: Can it be detected?

Is it possible to detect a C2 agent’s randomised traffic based on network traffic data alone? We tested it and are sharing our results here in our knowledge blog.

Prior testing work with C2 agent

In 2023, Trovent Security conducted a test together with Timo Sablowski from carmasec to detect C2 communication from a purely IMAP-based command and control (C2) agent. At the time, the test focused on detecting the deterministic behaviour of the C2 agent in network traffic.

Conclusion at that time: Certain indicators (session duration and frequency) can be recognised, but these are not reliable detection criteria in themselves. Our conclusion from the point of view of the attack detection system: Reliable detection can only be achieved by combining detected indicators with contextual knowledge about the IT infrastructure.

More challenging test scenario

To make things even more difficult for the Blue Team, i.e. for detection, compared to the original test, Timo developed a new agent to avoid predictable behaviour altogether.

Timo therefore integrated randomisation at two crucial points. Both the packet size and the dispatch times were modified accordingly. A random value between 10 and 2000 was used for the packet size, while the transmission time was additionally masked by a randomised jitter.

This article is about whether and how such a C2 agent can theoretically (and practically) be detected purely on the basis of network traffic data. Spoiler: It is more difficult than expected!

Effective detection despite randomisation?

The detection of C2 agents from network traffic data alone is in some ways in a class of its own. Especially since it is only a small part of a relatively long attack process (in the context of the killchain) that can potentially be detected.

In a typical attack scenario, an attacker would first have to compromise a company’s IT infrastructure as a basic prerequisite in order to be able to deploy a C2 agent. Normally, this process would already trigger a large number of indicators (IOCs) that could be detected.

In our constructed test scenario, however, we do not have the possibility to detect the indicators of such a compromise, but only carry out analyses based on the network traffic (i.e. after the assumed installation of the C2 agent).

Even more challenges

An additional difficulty: Due to its randomisation, the C2 agent does not exhibit the typical beaconing behavior we would normally look for.

To solve this problem, we were inspired by methods for detecting balance sheet fraud. Benford’s law describes a regularity in the distribution of the leading digits of numbers in empirical data sets. Based on this regularity, auditors can analyze data sets and conclude whether certain distributions indicate fraud, while other distributions are “natural”.

We also wanted to use this approach for our C2 detection scenario and conducted the following test.

Packet size

For the randomised C2 agent, the packet size is determined randomly. The easiest way to do this would be to use the random.random() function in Python. Since the values generated by this function are between 0 and 1, a possible C2 agent could generate its packet sizes as follows if they are to be between 1 and 1000:

packet_size = 1 + round(random.random() * 999) # [1, 1000]

The resulting distribution is a uniform distribution for the digits 1 to 9, as can be seen in the figure below (blue bars).

Timo has now told us that the packet sizes generated by the C2 agent for analyzing network traffic are between 10 and 2000, provided that no large amounts of data are transferred. A possible implementation for this would be:

packet_size = 10 + round(random.random() * 1990) # [10, 2000]

Here we can see in the bar chart (green bars) that the majority of the numbers begin with a 1. This is obvious, as half of the generated numbers have the form 1xxx. If we subtract these 5000 (50% of 10,000 samples) from the 5584, we get the expected ~580 as in the case of the other leading digits. The uniform distribution character is therefore still evident and easily recognizable in the figure.

Suppose an attacker generates numbers between 10 and 6000:

packet_size = 10 + round(random.random() * 5990) # [10, 6000]

Now the distribution is slightly different again, but the equal distribution character is still evident (see above).

“Natural” distributions of packet sizes

Now that we know how random packet sizes (based on uniformly distributed random functions) behave, we need to look at packet sizes from “natural” distributions for comparison. This is the only way we have a chance to identify the randomised packets of C2 network traffic by their size in “natural” traffic.

To do this, we will look at two different examples:

CPU load

To show that naturally occurring numbers do not follow this type of distribution, we have constructed the following example:

packet_size = 1 + round(random.random() * psutils.cpu_percent()) # [1, CPU_PERCENT]

As soon as random values are based on a natural value, the patterns identified above in the randomisation can no longer be recognized. The digits 1 and 2 occur very frequently, while the other digits occur with decreasing frequency.

This is an indicator that randomised packet sizes can be distinguished from natural data traffic.

DNS traffic

Another example for comparison are the recordings of DNS traffic from Forward Lookups to A-Records in our test lab. The packet sizes here mainly start at 5 or 6, and the distribution – just like the CPU-based distribution – can therefore be distinguished from the distribution caused by the C2 agent.

The result: It is possible for us to really distinguish randomly generated packet sizes of the C2 agent from natural data traffic.

Is this feasible in practice?

In our test, we were able to determine that the randomly generated packet sizes deviate in their size distribution from natural data traffic and are therefore recognizable. But is this also feasible in practice?

Our tests have shown that in reality, this type of analysis is heavily dependent on whether the available packet sizes obtained from network traffic correspond to the random numbers. However, this requires a high degree of precision with regard to the actual packet sizes.

And this degree of precision depends not least on the protocol used, other compressions and the encryption used. UDP is the best protocol for our analysis, as the packet sizes are transmitted on a one-to-one basis.

However, the reality in our test scenario was somewhat different:

🛑 The C2 agent uses TCP, which leads to a fragmentation of the message length.

🛑 Network traffic data is monitored using NetFlow, which uses aggregation to calculate packet sizes.

🛑 Data traffic originating from the C2 agent is also AES-encrypted. Depending on the implementation and mode, this can also change the packet size.

We must therefore conclude that, for the reasons mentioned above, the detection of C2 traffic in data traffic with randomly generated packet sizes is not possible – at least not reliably.

Conclusions drawn from test scenario

For a simple C2 agent with randomised packet sizes that sends its random packet sizes directly over UDP, we are able to reliably detect them with Trovent MDR using only network traffic.

However, detecting a C2 agent that uses TCP and operates with randomised packet sizes using only network traffic data is very difficult – and unreliable at best.

For the above approach of detecting randomised packet sizes in natural traffic to work, we need very precise detection of packet sizes. For example, directly at the endpoint, but this would also lead to a very large, probably impractical log data volume.

Unfortunately, the data generated by NetFlow is not sufficient for this. In addition, measures that further distort the packet size (e.g. compression, encryption with padding) can further complicate the detection approach described in this article.

Implication for effective implementation of attack detection?

➡ The fact that such accurate packet size measurements are not available for attack detection emphasizes the need to be able to detect C2 attacks in the different phases of an attack.

➡ As we mentioned at the beginning, in a realistic attack scenario, an attacker would first have to successfully compromise the IT infrastructure in order to deploy and activate a C2 agent on a target system.

➡ Generally speaking: For a successful C2 attack, several steps in the so-called Killchain must be successfully completed. And in each of the Killchain phases, indicators (IOCs) and behavioral anomalies are generated that can be used as part of attack detection – ideally long before a sophisticated agent like Timo’s C2 agent starts sending data.

➡ An effective attack detection system must always assume that an attacker will succeed in making unnoticed progress in individual phases of an attack. For this reason, our Managed Detection & Response solution consistently relies on the right combination of rule-based and machine-learning-supported detection to maximize the probability of detection in various killchain phases.


Are you looking for an MDR solution? Or would you like to know how Trovent’s Context Engine works? Get in touch with us! We’re available to discuss your requirements and demo our solutions.