πŸ”’
❌
There are new articles available, click to refresh the page.
Before yesterdayNVISO Labs

Cobalt Strike: Using Known Private Keys To Decrypt Traffic – Part 2

27 October 2021 at 08:49

We decrypt Cobalt Strike traffic using one of 6 private keys we found.

In this blog post, we will analyze a Cobalt Strike infection by looking at a full packet capture that was taken during the infection. This analysis includes decryption of the C2 traffic.

If you haven’t already, we invite you to read part 1 first: Cobalt Strike: Using Known Private Keys To Decrypt Traffic – Part 1.

For this analysis, we are using capture file 2021-02-02-Hancitor-with-Ficker-Stealer-and-Cobalt-Strike-and-NetSupport-RAT.pcap.zip, this is one of the many malware traffic capture files that Brad Duncan shares on his web site Malware-Traffic-Analysis.net.

We start with a minimum of knowledge: the capture file contains encrypted HTTP traffic of a Cobalt Strike beacon communicating with its team server.

If you want to know more about Cobalt Strike and its components, we highly recommend the following blog post.

First step: we open the capture file with Wireshark, and look for downloads of a full beacon by stager shellcode.

Although beacons can come in many forms, we can identify 2 major categories:

  1. A small piece of shellcode (a couple of hundred bytes), aka the stager shellcode, that downloads the full beacon
  2. The full beacon: a PE file that can be reflectively loaded

In this first step, we search for signs of stager shellcode in the capture file: we do this with the following display filter: http.request.uri matches β€œ/….$”.

Figure 1: packet capture for Cobalt Strike traffic

We have one hit. The path used in the GET request to download the full beacon, consists of 4 characters that satisfy a condition: the byte-value of the sum of the character values (aka checksum 8) is a known constant. We can check this with the tool metatool.py like this:

Figure 2: using metatool.py

More info on this checksum process can be found here.
The output of the tool shows that this is a valid path to download a 32-bit full beacon (CS x86).
The download of the full beacon is captured too:

Figure 3: full beacon download

And we can extract this download:

Figure 4: export HTTP objects
Figure 5: selecting download EbHm for saving
Figure 6: saving selected download to disk

Once the full beacon has been saved to disk as EbHm.vir, it can be analyzed with tool 1768.py. 1768.py is a tool that can decode/decrypt Cobalt Strike beacons, and extract their configuration. Cobalt Strike beacons have many configuration options: all these options are stored in an encoded and embedded table.

Here is the output of the analysis:

Figure 7: extracting beacon configuration

Let’s take a closer look at some of the options.

First of all, option 0x0000 tells us that this is an HTTP beacon: it communicates over HTTP.
It does this by connecting to 192.254.79[.]71 (option 0x0008) on port 8080 (option 0x0002).
GET requests use path /ptj (option 0x0008), and POST requests use path /submit.php (option 0x000a)
And important for our analysis: there is a known private key (Has known private key) for the public key used by this beacon (option 0x0007).

Thus, armed with this information, we know that the beacon will send GET requests to the team server, to obtain instructions. If the team server has commands to be executed by the beacon, it will reply with encrypted data to the GET request. And when the beacon has to send back output from its commands to the team server, it will use a POST request with encrypted data.

If the team server has no commands for the beacon, it will send no encrypted data. This does not necessarily mean that the reply to a GET request contains no data: it is possible for the operator, through profiles, to masquerade the communication. For example, that the encrypted data is inside a GIF file. But that is not the case with this beacon. We know this, because there are no so-called malleable C2 instructions in this profile: option 0x000b is equal to 0x00000004 -> this means no operations should be performed on the data prior to decryption (we will explain this in more detail in a later blog post).

Let’s create a display filter to view this C2 traffic: http and ip.addr == 192.254.79[.]71

Figure 8: full beacon download and HTTP requests with encrypted Cobalt Strike traffic

This displays all HTTP traffic to and from the team server. Remark that we already took a look at the first 2 packets in this view (packets 6034 and 6703): that’s the download of the beacon itself, and that communication is not encrypted. Hence, we will filter these packets out with the following display filter:

http and ip.addr == 192.254.79.71 and frame.number > 6703

This gives us a list of GET requests with their reply. Remark that there’s a GET request every minute. That too is in the beacon configuration: 60.000 ms of sleep (option 0x0003) with 0% variation (aka jitter, option 0x0005).

Figure 9: HTTP requests with encrypted Cobalt Strike traffic

We will now follow the first HTTP stream:

Figure 10: following HTTP stream
Figure 11: first HTTP stream

This is a GET request for /ptj that receives a STATUS 200 reply with no data. This means that there are no commands from the team server for this beacon for now: the operator has not issued any commands at that point in the capture file.

Remark the Cookie header of the GET request. This looks like a BASE64 string: KN9zfIq31DBBdLtF4JUjmrhm0lRKkC/I/zAiJ+Xxjz787h9yh35cRjEnXJAwQcWP4chXobXT/E5YrZjgreeGTrORnj//A5iZw2TClEnt++gLMyMHwgjsnvg9czGx6Ekpz0L1uEfkVoo4MpQ0/kJk9myZagRrPrFWdE9U7BwCzlE=

That value is encrypted metadata that the beacon sends as a BASE64 string to the team server. This metadata is RSA encrypted with the public key inside the beacon configuration (option 0x0007), and the team server can decrypt this metadata because it has the private key. Remember that some private keys have been β€œleaked”, we discussed this in our first blog post in this series.

Our beacon analysis showed that this beacon uses a public key with a known private key. This means we can use tool cs-decrypt-metadata.py to decrypt the metadata (cookie) like this:

Figure 12: decrypting beacon metadata

We can see here the decrypted metadata. Very important to us, is the raw key: caeab4f452fe41182d504aa24966fbd0. We will use this key to decrypt traffic (the AES adn HMAC keys are derived from this raw key).

More metadata that we can find here is: the computername, the username, …

We will now follow the HTTP stream with packets 9379 and 9383: this is the first command send by the operator (team server) to the beacon:

Figure 13: HTTP stream with encrypted command

Here we can see that the reply contains 48 bytes of data (Content-length). That data is encrypted:

Figure 14: hexadecimal view of HTTP stream with encrypted command

Encrypted data like this, can be decrypted with tool cs-parse-http-traffic.py. Since the data is encrypted, we need to provide the raw key (option -r caeab4f452fe41182d504aa24966fbd0) and as the packet capture contains other traffic than pure Cobalt Strike C2 traffic, it is best to provide a display filter (option -Y http and ip.addr == 192.254.79.71 and frame.number > 6703) so that the tool can ignore all HTTP traffic that is not C2 traffic.

This produces the following output:

Figure 15: decrypted commands and results

Now we can see that the encrypted data in packet 9383 is a sleep command, with a sleeptime of 100 ms and a jitter factor of 90%. This means that the operator instructed the beacon to beacon interactive.

Decrypted packet 9707 contains an unknown command (id 53), but when we look at packet 9723, we see a directory listing output: this is the output result of the unknown command 53 being send back to the team server (notice the POST url /submit.php). Thus it’s safe to assume that command 53 is a directory listing command.

There are many commands and results in this capture file that tool cs-parse-http-traffic.py can decrypt, too much to show here. But we invite you to reproduce the commands in this blog post, and review the output of the tool.

The last command in the capture file is a process listing command:

Figure 16: decrypted process listing command and result

Conclusion

Although the packet capture file we decrypted here was produced more than half a year ago by Brad Duncan by running a malicious Cobalt Strike beacon inside a sandbox, we can decrypt it today because the operators used a rogue Cobalt Strike package including a private key, that we recovered from VirusTotal.

Without this private key, we would not be able to decrypt the traffic.

The private key is not the only way to decrypt the traffic: if the AES key can be extracted from process memory, we can also decrypt traffic. We will cover this in an upcoming blog post.

About the authors
Didier Stevens is a malware expert working for NVISO. Didier is a SANS Internet Storm Center senior handler and Microsoft MVP, and has developed numerous popular tools to assist with malware analysis. You can find Didier onΒ TwitterΒ andΒ LinkedIn.

You can follow NVISO Labs on Twitter to stay up to date on all our future research and publications.

Cobalt Strike: Using Process Memory To Decrypt Traffic – Part 3

3 November 2021 at 19:18

We decrypt Cobalt Strike traffic with cryptographic keys extracted from process memory.

This series of blog posts describes different methods to decrypt Cobalt Strike traffic. In part 1 of this series, we revealed private encryption keys found in rogue Cobalt Strike packages. And in part 2, we decrypted Cobalt Strike traffic starting with a private RSA key. In this blog post, we will explain how to decrypt Cobalt Strike traffic if you don’t know the private RSA key but do have a process memory dump.

Cobalt Strike network traffic can be decrypted with the proper AES and HMAC keys. In part 2, we obtained these keys by decrypting the metadata with the private RSA key. Another way to obtain the AES and HMAC key, is to extract them from the process memory of an active beacon.

One method to produce a process memory dump of a running beacon, is to use Sysinternals’ tool procdump. A full process memory dump is not required, a dump of all writable process memory is sufficient.
Example of a command to produce a process dump of writable process memory: β€œprocdump.exe -mp 1234”, where -mp is the option to dump writable process memory and 1234 is the process ID of the running beacon. The process dump is stored inside a file with extension .dmp.

For Cobalt Strike version 3 beacons, the unencrypted metadata can often be found in memory by searching for byte sequence 0x0000BEEF. This sequence is the header of the unencrypted metadata. The earlier in the lifespan of a process the process dump is taken, the more likely it is to contain the unencrypted metadata.

Figure 1: binary editor view of metadata in process memory

Tool cs-extract-key.py can be used to find and decode this metadata, like this:

Figure 2: extracted and decoded metadata

The metadata contains the raw key: 16 random bytes. The AES and HMAC keys are derived from this raw key by calculating the SHA256 value of the raw key. The first half of the SHA256 value is the HMAC key, and the second half is the AES key.

These keys can then be used to decrypt the captured network traffic with tool cs-parse-http-traffic.py, like explained in Part 2.

Remark that tool cs-extract-key.py is likely to produce false positives: namely byte sequences that start with 0x0000BEEF, but are not actual metadata. This is the case for the example in figure 2: the first instance is indeed valid metadata, as it contains a recognizable machine name and username (look at Field: entries). And the AES and HMAC key extracted from that metadata, have also been found at other positions in process memory. But that is not the case for the second instance (no recognizable names, no AES and HMAC keys found at other locations). And thus that is a false positive that must be ignored.

For Cobalt Strike version 4 beacons, it is very rare that the unencrypted metadata can be recovered from process memory. For these beacons, another method can be followed. The AES and HMAC keys can be found in writable process memory, but there is no header that clearly identifies these keys. They are just 16-byte long sequences, without any distinguishable features. To extract these keys, the method consists of performing a kind of dictionary attack. All possible 16-byte long, non-null sequences found in process memory, will be used to try to decrypt a piece of encrypted C2 communication. If the decryption succeeds, a valid key has been found.

This method does require a process memory dump and encrypted data.
This encrypted data can be extracted using tool cs-parse-http-traffic.py like this: cs-parse-http-traffic.py -k unknown capture.pcapng

With an unknown key (-k unknown), the tool will extract the encrypted data from the capture file, like this:

Figure 3: extracting encrypted data from a capture file

Packet 103 is an HTTP response to a GET request (packet 97). The encrypted data of this response is 64 bytes long: d12c14aa698a6b85a8ed3c3c33774fe79acadd0e95fa88f45b66d8751682db734472b2c9c874ccc70afa426fb2f510654df7042aa7d2384229518f26d1e044bd

This is encrypted data, sent by the team server to the beacon: it contains tasks to be executed by the beacon (remark that in these examples, we look at encrypted traffic that has not been transformed, we will cover traffic transformed by malleable instructions in an upcoming blog post).

We can attempt to decrypt this data by providing tool cs-extract-key.py with the encrypted task (option -t) and the process memory dump: cs-extract-key.py -t d12c14aa698a6b85a8ed3c3c33774fe79acadd0e95fa88f45b66d8751682db734472b2c9c874ccc70afa426fb2f510654df7042aa7d2384229518f26d1e044bd rundll32.exe_211028_205047.dmp.

Figure 4: extracting AES and HMAC keys from process memory

The recovered AES and HMAC key can then be used to decrypt the traffic (-k HMACkey:AESkey):

Figure 5: decrypting traffic with HMAC and AES key provided via option -k

The decrypted tasks seen in figure 5, are β€œdata jitter”. Data jitter is a Cobalt Strike option, that sends random data to the beacon (random data that is ignored by the beacon). With the default Cobalt Strike beacon profile, no random data is sent, and data is not transformed using malleable instructions. This means that with such a beacon profile, no data is sent to the beacon as long as there are no tasks to be performed by the beacon: the Content-length of the HTTP reply is 0.

Since the absence of tasks results in no encrypted data being transmitted, it is quite easy to determine if a beacon received tasks or not, even when the traffic is encrypted. An absence of (encrypted) data means that no tasks were sent. To obfuscate this absence of commands (tasks), Cobalt Strike can be configured to exchange random data, making each packet unique. But in this particular case, that random data is useful to blue teamers: it permits us to recover the cryptographic keys from process memory. If no random data would be sent, nor actual tasks, we would never see encrypted data and thus we would not be able to identify the cryptographic keys inside process memory.

Data sent by the beacon to the team server contains the results of the tasks executed by the beacon. This data is sent with a POST request (default), and is known as a callback. This data too can be used to find decryption keys. In that case, the process is the same as shown above, but the option to use is -c (callback) in stead of -t (tasks). The reason the options are different, is that the way the data is encrypted by the team server is slightly different from the way the data is encrypted by the beacon, and the tool must be told which way to encrypt the data was used.

Some considerations regarding process memory dumps

For a process memory dump of maximum 10MB, the β€œdictionary” attack will take a couple of minutes.

Full process dumps can be used too, but the dictionary attack can take much longer because of the larger size of the dump. Tool cs-extract-key.py reads the process memory dump as a flat file, and thus a larger file means more processing to be done.

However, we are working on a tool that can parse the data structure of a dump file and extract / decode memory sections that are most likely to contain keys, thus speeding up the key recovery process.

Remark that beacons can be configured to encode their writable memory while they are not active (sleeping): in such cases, the AES and HMAC keys are encoded too, and can not be recovered using the methods described here. The dump parsing tool we are working on will handle this situation too.

Finally, if the method explained here for version 3 beacons does not work with your particular memory dump, try the method for version 4 beacons. This method works also for version 3 beacons.

Conclusion

Cryptographic keys are required to decrypt Cobalt Strike traffic. The best situation is to have the corresponding private RSA key. If that is not the case, HMAC and AES keys can be recovered using a process memory dump and capture file with encrypted traffic.

About the authors

Didier Stevens is a malware expert working for NVISO. Didier is a SANS Internet Storm Center senior handler and Microsoft MVP, and has developed numerous popular tools to assist with malware analysis. You can find Didier onΒ TwitterΒ andΒ LinkedIn.

You can follow NVISO Labs onΒ TwitterΒ to stay up to date on all our future research and publications.

Detecting DCSync and DCShadow Network Traffic

15 November 2021 at 08:30

This blog post on detecting Mimikatz’ DCSync and DCShadow network traffic, accompanies SANS webinar β€œDetecting DCSync and DCShadow Network Trafficβ€œ.

Intro

Mimikatz provides two commands to interact with a Windows Domain Controller and extract or alter data from the Active Directory database.

These two commands are dcsync and dcshadow.

The dcsync command can be used, on any Windows machine, to connect to a domain controller and read data from AD, like dumping all credentials. This is not an exploit or privilege escalation, the necessary credentials are required to be able to do this, for example a golden ticket.

The dcshadow command can be used, on any Windows machine, to connect to a domain controller and write data to AD, like changing a password or adding a user. This too is not an exploit or privilege escalation: proper domain admin credentials are necessary to achieve this.

Both commands rely on the active directory data replication protocol: Directory Replication Service (DRS). This is a protocol (MSRPC / DCE/RPC based) that domain controllers use to replicate their AD database changes between them. The Microsoft API for DRS is DRSUAPI.

Such traffic should only occur between domain controllers. When DRS traffic is detected between a DC and a non-DC (a user workstation for example), alarms should go of.

Alerting

An Intrusion Detection System can detect DRSUAPI traffic with proper rules.

Figure 1: IDS inspecting traffic between workstation and DC

The IDS needs to be positioned inside the network, at a location where traffic between domain controllers and non-domain controllers can be inspected.

DCE/RPC traffic is complex to parse properly. For example, remote procedure calls are done with an integer that identifies the procedure to call. The name of the function, represented as a string for example, is not used in the DCE/RPC protocol. Furthermore, function integers are only unique within an API: for example, function 0 is the DsBind function in the DRSUAPI function, but function 0 is also the DSAPrepareScript in the DSAOP interface.

A very abstract view of such traffic, can be represented like this:

Figure 2: abstraction of DCE/RPC traffic

If an IDS would just see or inspect packet B, it would not be able to determine which function is called. Sure, it is function 0, but for which API? Is it DsBind in the DRSUAPI API or is is DSAPrepareScript in the DSAOP interface? Or another one …

So, the IDS needs to keep track of the interfaces that are requested, and then it can correctly determine which functions are requested.

Alerting dcsync

Here is captured dcsync network traffic, visualized with Wireshark (dcerpc display filter):

Figure 3: DCSync network traffic

Frame 28 is our packet A: requesting the DRSUAPI interface

Frame 41 is our packet B: requesting function DsGetNCChanges

Notice that these packets do belong to the same TCP connection (stream 4).

Thus, a rule would be required, that triggers on two different packets. This is not possible in Snort/Suricata: simple rules inspect only one packet.

What is typically done in Suricata for such cases, is to make two rules: one for packet A and one for packet B. And an alert is only generated when rule B triggers after rule A triggers.

This can be done with a flowbit. A flowbit is literally a bit kept in memory by Suricata, that can be set or cleared.

These bits are linked to a flow. Simply put, a flow is a set of packets between the same client and server. It’s more generic than a connection.

Thus, what needs to be done to detect dcsync traffic using a flowbit, is to have two rules:

  1. Rule 1: detect packet of type A and set flowbit
  2. Rule 2: detect packet of type B and alert if flowbit is set

Suricata rules that implement such a detection, look like this:

alert tcp $WORKSTATIONS any -> $DCS any (
msg:"Mimikatz DRSUAPI"; 
flow:established,to_server; 
content:"|05 00 0b|"; depth:3; 
content:"|35 42 51 e3 06 4b d1 11 ab 04 00 c0 4f c2 dc d2|"; depth:100; 
flowbits:set,drsuapi; 
flowbits:noalert; 
reference:url,blog.didierstevens.com; classtype:policy-violation; sid:1000010; rev:1;)

alert tcp $WORKSTATIONS any -> $DCS any (
msg:"Mimikatz DRSUAPI DsGetNCChanges Request";
flow:established,to_server;
flowbits:isset,drsuapi; 
content:"|05 00 00|"; depth:3; 
content:"|03 00|"; offset:22; depth:2;
reference:url,blog.didierstevens.com; classtype:policy-violation; sid:1000011; rev:1;)

The first rule (Mimikatz DRSUAPI) is designed to identify a DCERPC Bind to the DRSUAPI API. The packet data has to start with 05 00 0B:

Figure 4: DCERPC packet header

5 is the major version of the protocol, 0 is the minor version, and 0B (11 decimal) is a Bind request.

A UUID is used to identify the DRSUAPI interface (this is not done with a string like DRSUAPI, but with a UUID that uniquely identifies the DRSUAPI interface):

Figure 5: DRSUAPI UUID

The UUID for DRSUAPI is

e3514235-4b06-11d1-ab04-00c04fc2dcd2

In network packet format, it is

35 42 51 e3 06 4b d1 11 ab 04 00 c0 4f c2 dc d2.

When both content clauses are true, the rule triggers. The action that is triggered, is setting a flowbit named drsuapi:

flowbits:set,drsuapi;

A second action, is to prevent the rule from generating an alert when setting this flowbit:

flowbits:noalert;

This explains the first rule.

The second rule (Mimikatz DRSUAPI DsGetNCChanges Request) is designed to detect packets with a DRSUAPI request for function DsGetNCChanges. The packet data has to start with 05 00 00:

Figure 6: DRSUAPI Request

5 is the major version of the protocol, 0 is the minor version, and 00 is an RPC request.

And further down in the packet data (position 22 to be precise) the number of the function is specified:

Figure 6: DRSUAPI DsGetNCChanges

Number 3 is DRSUAPI function DsGetNCChanges.

When flowbit drsuapi is set and both content clauses are true, the rule triggers.

flowbits:isset,drsuapi;

And an alert is generated.

Notice that the rule names contain the word Mimikatz, but these rules are not specific to Mimikatz: they will also trigger on regular replication traffic between DCs. The key to use these rules properly, is to make them inspect network traffic between domain controllers and non-domain controllers. Replication traffic should only occur between DCs.

Alerting dcshadow

Mimikatz dcshadow command also generates DRSUAPI network traffic, and the rules defined for dcsync also trigger on dcshadow traffic.

One lab in SANS training SEC599, Defeating Advanced Adversaries – Purple Team Tactics & Kill Chain Defenses, covers dcsync and its network traffic detection. If you take this training, you can also try out dcshadow in this lab.

The dcshadow command requires two instances of Mimikatz to run. First, one running as system to setup the RPC server:

Figure 7: first instance of Mimikatz for dcshadow (screenshot a)
Figure 8: first instance of Mimikatz for dcshadow (screenshot b)

And a second one running as domain admin to start the replication:

Figure 9: second instance of Mimikatz for dcshadow

This push instruction starts the replication:

Figure 10: second instance of Mimikatz for dcshadow (screenshot b)

dcshadow network traffic looks like this in Wireshark (dcerpc display filter):

Figure 11: dcshadow network traffic

Notice the DRSUAPI bind requests and the DsGetNCChanges requests -> these will trigger the dcsync rules.

DRSUAPI_REPLICA_ADD is also an interesting function to detect: it adds a replication source. The integer that identifies this function is 5.

Figure 12: DRSUAPI_REPLICA_ADD

A rule to detect this function can be created based on the rule to detect DsGetNCChanges.

What needs to be changed:

  1. The opnum: 03 00 -> 05 00
  2. The rule number, sid:1000011 -> sid:1000014 (for example)
  3. And the rule message (preferably): β€œMimikatz DRSUAPI DsGetNCChanges Request” -> β€œMimikatz DRSUAPI DRSUAPI_REPLICA_ADD Request”
alert tcp $WORKSTATIONS any -> $DCS any (
msg:"Mimikatz Mimikatz DRSUAPI DRSUAPI_REPLICA_ADD Request";
flow:established,to_server;
flowbits:isset,drsuapi; 
content:"|05 00 00|"; depth:3; 
content:"|05 00|"; offset:22; depth:2;
reference:url,blog.didierstevens.com; classtype:policy-violation; sid:1000014; rev:1;)

More generic rules

It is also possible to change the flowbit setting rule (rule for packet A), to generate alerts. This is done by removing the following clause:

flowbits:noalert;

Alerts are generated whenever the DRSUAPI interface is bound to, regardless of which function is called.

And a generic rule for a DRSUAPI function call can also be created, by removing the following clause from the DsGetNCChanges rule (for example):

content:”|03 00|”; offset:22; depth:2;

Byte order and DCEPRC

DCERPC is a flexible protocol, that allows different byte orders. A byte order, is the order in which bytes are transmitted over the network. When dealing with integers that are encoded using more than one byte, for example, different orders are possible.

The opnum detected in the dcsync rule, is 3. This integer is encoded with 2 bytes: a most significant byte (00) and a least significant byte (03).

When the byte order is little-endian, the least significant byte (03) is transmitted first, followed by the most significant byte (00). This is what is present in the captured network traffic.

But when the byte order is big-endian, the most significant byte (00) is transmitted first, followed by the least significant byte (03).

And thus, the rules would not trigger for big-endian byte-order.

The byte order is specified by the client in the data representation bytes of the DCERPC packet data:

Figure 13: DCERPC data representation

If the first nibble of the first byte of the data representation is one, the byte order is little-endian.

Big-endian is encoded with nibble value zero.

We have developed rules that check the byte-order, and match the opnum value accordingly:

alert tcp $WORKSTATIONS any -> $DCS any (msg:"Mimikatz DRSUAPI DsGetNCChanges Request"; flow:established,to_server; flowbits:isset,drsuapi; content:"|05 00 00|"; depth:3; byte_test:1,>=,0x10,4; byte_test:1,<=,0x11,4; content:"|03 00|"; offset:22; depth:2; reference:url,blog.didierstevens.com; classtype:policy-violation; sid:1000012; rev:1;)
alert tcp $WORKSTATIONS any -> $DCS any (msg:"Mimikatz DRSUAPI DsGetNCChanges Request"; flow:established,to_server; flowbits:isset,drsuapi; content:"|05 00 00|"; depth:3; byte_test:1,>=,0x00,4; byte_test:1,<=,0x01,4; content:"|00 03|"; offset:22; depth:2; reference:url,blog.didierstevens.com; classtype:policy-violation; sid:1000013; rev:1;)

alert tcp $WORKSTATIONS any -> $DCS any (msg:"Mimikatz DRSUAPI DRSUAPI_REPLICA_ADD Request"; flow:established,to_server; flowbits:isset,drsuapi; content:"|05 00 00|"; depth:3; byte_test:1,>=,0x10,4; byte_test:1,<=,0x11,4; content:"|05 00|"; offset:22; depth:2; reference:url,blog.didierstevens.com; classtype:policy-violation; sid:1000015; rev:1;)
alert tcp $WORKSTATIONS any -> $DCS any (msg:"Mimikatz DRSUAPI DRSUAPI_REPLICA_ADD Request"; flow:established,to_server; flowbits:isset,drsuapi; content:"|05 00 00|"; depth:3; byte_test:1,>=,0x00,4; byte_test:1,<=,0x01,4; content:"|00 05|"; offset:22; depth:2; reference:url,blog.didierstevens.com; classtype:policy-violation; sid:1000016; rev:1;)

Notice that Suricata and Snort can also be configured to enable the dcerpc preprocessor. This allows for the creation of rules that don’t have to take implementation details into account, like byte-order:

alert dcerpc $WORKSTATIONS any -> $DCS any (msg:"Mimikatz DRSUAPI DsGetNCChanges Request"; flow:established,to_server; dce_iface:e3514235-4b06-11d1-ab04-00c04fc2dcd2; dce_opnum:3; reference:url,blog.didierstevens.com; classtype:policy-violation; sid:1000017; rev:1;)

But such rules can have a significantly higher performance impact, because of the extra processing performed by the dcerpc preprocessor.

Conclusion

In this blog post we show how to detect Active Directory replication network traffic. Such traffic is normal between domain controllers, but it should not be detected between a non-domain controller (like a workstation or a member server) and a domain controller. The presence of unexpected DRS traffic, is a strong indication of an ongoing Active Directory attack, like Mimikatz’ DCSync or DCShadow.

The rules we start with operate at a low network layer level (TCP data), but we show how to develop rules at a higher level, that are more versatile and require less attention to implementation details.

Finally, the rules presented in this blog post are alerting rules for a detection system. But they can easily be modified into blocking rules for a prevention system, by replacing the alert action by a drop or reject action.

All the rules presented here can also be found on our IDS rules Github repository.

About the authors

Didier Stevens is a malware expert working for NVISO. Didier is a SANS Internet Storm Center senior handler and Microsoft MVP, and has developed numerous popular tools to assist with malware analysis. You can find Didier onΒ TwitterΒ andΒ LinkedIn.

You can follow NVISO Labs onΒ TwitterΒ to stay up to date on all our future research and publications.

  • There are no more articles
❌