Sleep Mask Kit IOCs

YARA rule included!

In the original version of this blogpost, I incorrectly attributed detection of Cobalt Strike's Sleep Mask feature to a userland hook applied to the kernel32!Sleep function. However, upon further inspection during a conversation with dmchell, I realised that the hook that I saw in PE-Sieve with the sleep mask enabled was not due to the sleep mask feature, but rather due to the Malleable C2 profile I was using at the time, which used module stomping for execution. The module stomping caused the loaded DLL to show up as a hook in PE-Sieve. As a result, I have done further research and reuploaded this blogpost with the updated content.

Certain AV/EDR products were still detecting my beacons in memory, specifically attributing them as Cobalt Strike beacons. An example is shown below:

While I was not surprised at my beacons being flagged through the sleep mask feature (there are other means of detection, after all) I was surprised that they were able to attribute it to Cobalt Strike specifically, via a memory scan. The Sleep Mask Kit was recompiled with a modified key size, no beacon commands were executed prior to the memory scan and the beacon never called back during the scan. Due to the nature of sleep masking as a technique, successful attribution of Cobalt Strike in memory means that something Cobalt Strike specific was left in memory, and not encrypted. To understand how that could be possible, I went back to the Sleep Mask Kit.

As the Sleep Mask Kit is a part of the licensed Cobalt Strike product, the full source code will not be shown here. A small snippet is shown to understand how it works.

As referenced in my other post, the way sleep masking is done in Cobalt Strike is a list of sections containing beacon memory artifacts are passed as an argument to the sleep masking function, which loops over them, applying a XOR key to them. At this point I had heard from a few people that not every section belonging to Beacon was passed in. Based on this, I had a theory that the AV/EDR had a static signature for a section of Beacon memory that was not encrypted on sleep.

To test this, I popped a beacon into x64dbg and process hacker, and checked a few things.

First, I set my beacon's sleep time to 0, to make it as easy as possible to observe what memory was being encrypted and decrypted on sleep from the debugger.

Now, I located the beacon memory allocation by checking the thread call stack for the caller of the SleepEx function.

With the caller address, we can find the base of the memory allocation this belongs to (which contains beacon stuff) in the memory tab.

While discussing the viability of evasion using the Sleep Mask Kit, someone mentioned that there were bytes related to the sleep mask function that were not encrypted on sleep. Seeing as this allocation stayed with the RX permission during beacon sleep (which should set beacon memory allocs to RW), I can assume that this is the sleep_mask function in the Sleep Mask Kit, which cannot encrypt or RW itself on sleep, as it needs to execute to wake beacon. This would allow the detection and attribution of Cobalt Strike even during sleep, as the sleep masking function is unable to obfuscate itself, and as a result is vulnerable to static signatures.

To observe this, I viewed that memory region in x64dbg, and sure enough, the bytes were not XORed on sleep.

I crafted a simple YARA rule to search for the set of strings at the start of this memory region that I observed were not XORed on sleep, and stayed constant between beacons.


rule CobaltStrike_sleepmask {
	meta:
		description = "Static bytes in Cobalt Strike 4.5 sleep mask function that are not obfuscated"
		author = "CodeX"
		date = "2022-07-04"
	strings:
		$sleep_mask = {48 8B C4 48 89 58 08 48 89 68 10 48 89 70 18 48 89 78 20 45 33 DB 45 33 D2 33 FF 33 F6 48 8B E9 BB 03 00 00 00 85 D2 0F 84 81 00 00 00 0F B6 45 }
	condition:
		$sleep_mask
}

I spawned a few beacons on my testing host with different configurations, and ran the YARA rule against all processes to test for:

  • Detection of different beacon types and configurations

    • TCP beacon

    • SMB beacon

    • Various modifications to the sleep mask kit

  • False positive rate

The results are shown below:

Get-Process | ForEach-Object {./yara64.exe cs_sleepmask.yar $_.ID 2>$null}

After comparing the detected PIDs, the rule was able to accurately identify every Cobalt Strike process, with the only "false positive" being the x64dbg process (PID 1356) (which was debugging the beacon). While I have not done extensive testing with this rule yet, a few other friends have tested this rule and no false positives have occurred yet.

This was my first attempt at locating specific IOCs so far while learning malware dev, and it was pretty fun. I might do another post soon trying to evade this, though in theory any custom sleep hook should evade this static signature.

Special thanks to:

  • dmchell - for helping me figure out my previous detection theory was incorrect

  • Anyone in the discord who helped (advice, rule testing)

Last updated