Modifying the Sleep Mask Kit

Note: The full code of the sleep mask kit will not be shown here due to it being only for licensed Cobalt Strike users. Snippets of code and line numbers may be shown or referenced. Pseudocode and diagrams will be used in place of the full code for explanation.

Overview

Cobalt Strike is a well known payload and its appearance in memory is well known to many defenders. To evade static signature detections, Cobalt Strike performs a "sleep mask" routine after every callback, obfuscating beacon in memory while it sleeps. The characteristics of this routine in its default state are also well known, so the Sleep Mask Kit may be used to customize this behaviour.

A rough overview of sleep masking is shown in the diagram below.

In Cobalt Strike, this is implemented by having Beacon call another function named "sleep_mask" in place of the Sleep() function in kernel32.dll, passing in a list of memory (and heap) addresses to obfuscate, as well as pointers to WinAPIs that it needs to call (since external functions cannot be called natively in the kit, according to the official documentation).

Beacon then uses the XOR key information and list of memory addresses to apply a layer of XOR over the memory regions, as seen from line 40 to 52 of the sleepmask.c file. A rough pseudocode of the XOR logic is show below.

#define xor-key_length 13
<-- snip -->
DWORD * index;
DWORD a, b;
/* walk our sections and mask them */
index = sections_we_want_to_mask;
while (TRUE) {
	a = *index; b = *(index + 1);
	index += 2;
	if (a == 0 && b == 0)
		break;
	while (a < b) {
		//XOR the bytes
		*(address_we_want_to_xor + a) ^= mask_chars[a % xor_key_length]
		//Move on to the next byte by incrementing the address (stored in a)
		a++;
	}
}

As we can see, the logic is relatively simple. It loops over the memory sections Beacon wants to mask and runs a XOR loop on each section with a user defined key length. This is where we can apply our first modification: the key size. The default is 13, but changing it to anything else and recompiling will break most static signatures on the sleep mask routine.

This logic is repeated on the list of heap records to mask them as well. (line 54 to 61)

sleep_mask then uses the pointer to kernel32!Sleep passed to it by Beacon to call the sleep function.

The XOR logic from before is then repeated to reverse the XOR operation and put the memory back into plaintext for Beacon to use during its callback, and sleep_mask returns to Beacon.

Understanding this logic, another modification we can apply is on the XOR routine. A simple change would be to make it XOR every 2nd byte or skip a byte every x bytes. The logic for that is simple, and is left to the reader to implement. Do take note that the change must be applied to both the XOR encrypt and XOR decrypt code blocks above and below the Sleep() call to ensure the memory decrypts properly.

Have fun!

Last updated