## **Overview**
Sleep Masks provide an added edge of memory evasion for beacons sitting in memory. Most of the time a beacon is sitting in memory, it's sitting idle. This leaves it vulnerable to many EDR products when sitting unencrypted, which exposes sensitive information such as beacon config files; this contains C2 URL's and other pertinent build information that can burn a campaign in minutes if discovered. Sleep masks provide the ability to unmask the beacon only when needed and sit in an encrypted state when not needed.
Finding an encrypted beacon can be nearly impossible when sitting dormant. At that point it relies entirely on finding the sleep mask, which will lead the way to finding the beacon.
The artifact used for analyzing comes from [[Live-Off-the-Land Memory Dumping]], which can be performed on any Windows System out of the box without the need for additional software.
### **Sleep Mask Build**
For background context, the original sleep mask was built with the Mutator Kit, available within Fortra's Arsenal Kit, for evasion purposes. In particular the configurations for Substition, Flattening, and Split Basic Blocks were checked within the build.
![[Pasted image 20260223195208.png]]
While this doesn't make finding the sleep mask any harder, it does add complexity if attempting to deobfuscate the mask to unencrypt the beacon when carved from memory.
### **Finding Suspicious Threads**
Loading up WinDbg, the .dmp can be opened for Analysis.
![[Pasted image 20260223201135.png]]
As soon as the dmp is loaded in, symbols will need to be loaded (Unless you have an offline copy, you will need temporary internet access for the symbols to be looked up against Microsoft's public symbol server.)
![[Pasted image 20260223202026.png]]
Commands to run to load symbols:
- .symfix
- .reload
After symbols have been fixed, threads can be examined looking for suspicious calling patterns. For sleep masks in particular, their only purpose is to mask and unmask the beacon. While sleep masks can be obfuscated as we will see, they remain a great hunting artifact because they remain unencrypted in memory. When looking for suspicious calling threads this will often times cause these to stand out.
Within WinDbg, we can grab a dump of all threads and their call stack with the command `~*k`
![[Pasted image 20260223203157.png]]
In this instance the dump generated ~70 threads and their respective call stack. A fair amount to sift through manually. Understanding the call stack for a sleep mask can help narrow down the number of suspicious threads that need to be investigated. A number of API's below are commonly used within a sleep mask.
Sleep Mask Windows API's:
- ntdll!NtDelayExecution / ntdll!ZwDelayExecution
- KERNEL32!Sleep / KERNELBASE!SleepEx
- KERNEL32!WaitForSingleObject / KERNEL32!WaitForSingleObjectEx
- USER32!MsgWaitForMultipleObjectsEx
Lastly, seeing a reference on the call stack to a Memory Address is a large red flag. If your beacon/sleep mask is sitting in memory (which they most commonly are) this will be the dead giveaway.
Dumping the call stacks to a notepad, a search can be done for 'Sleep' to identify any call stacks with a sleep function. After narrowing down based on call stack API's, we are left with three threads (9, 10, and 13).
![[Pasted image 20260223205155.png]]
One important thing to note is the call stack operates on a Last-In, First-Out principle (LIFO). Armed with this knowledge, we can examine each thread in more depth to derive the following:
- ***Thread 9:*** While containing the Sleep API, this call stack appears to mimic what a normal call stack looks like for .NET threads. Everything is backed by files on disk and the thread was originally spawned from RtlUserThreadStart. **Verdict: Benign**
- ***Thread 10:*** The thread was created from raw memory rather than a file on disk. This is likely indicative of a potential process injection or reflective loading. Subsequent calls are made to SleepEx and DelayExecution Windows API's. This is likely the Sleep Mask for the Cobalt Strike Beacon. **Verdict: Suspicious**
- ***Thread 13:*** While this does appear to be less suspicious. Outside of the suspicious naming convention, a warning indicates a checksum was unable to be verified. The BOFNET.dll is still likely in memory, its bypassed the Windows OS Loader and retained the structure of a PE. WinDbg simplifies this by using the PE Header Information in memory instead of the raw memory like in Thread 10. Thus, we can likely confer this was was a .NET file reflectively loaded into memory retaining it's headers. ***Spoiler Alert: While this may look like the Cobalt Strike beacon, this was likely a job from the beacon indicating post exploitation activity.*** **Verdict: Suspicious**
### **Disassembling the Sleep Mask**
Taking a peek at the disassembly with the command `u 0x0000025088011edd L30`
![[Pasted image 20260224212443.png]]
Looking for commonly found operands `ROR`, `ROL`,`XOR`,`ADD`, `SUB` yields little to no results. One initial item that stood out was the number of `MOV` and `JMP` being done, with the `JMP` going to the same memory address. After much searching around it seemed this calling style looked similar to what is known as Control Flow Flattening (CFF). Digging more into CFF, [Fortra provides this functionality within their mutator kit which can be used on a sleepmask](https://www.cobaltstrike.com/blog/introducing-the-mutator-kit-creating-object-file-monstrosities-with-sleep-mask-and-llvm)
Picking apart the first few dozen lines of the sleepmask, while the common operands are not showing up, there is plenty that still does.
- ***Redundant JMP Operations (Red Box)***- With JMP's being performed to the same address, the code is likely in some type of switch statement. This in turn breaks up the flow of the program to complicate the actual path that is taken by the sleep mask.
- ***State variables executed via Control Flow (Green Box)***- Hard coded 32 bit values are being moved to registers. With a `cmove` being performed which dictates the path through the if statements. These static variables combined with the jumps previously seen exemplify the complexity of the flow.
- ***Indirect API Calls (Purple Box)***- Standard x64 calling convention passing two arguments in the `rcx` and `rdx` registers. With the function call being made from `rax`, there is no reference to any Windows API's, which may be indicative of Dynamic API resolution.
### **Extracting Sleep Mask From Memory**
With the sleep mask identified as living in Thread 10, we can navigate to that address within WinDbg with `~10s`. Additionally, from our call stack above we can see the sleep mask beginning at address `0000025088010000`. After determining the size of the memory region at that base address it can be extracted for further analysis.
![[Pasted image 20260225195620.png]]
After extracting the Sleep Mask, it can be analyzed within Binja. Looking at the previous assumptions within the disassembly earlier, we can see the while and switch statements that are commonly seen in control flow flattening.
![[Pasted image 20260225201159.png]]
Looking at the Mini Graph, there is a staircase view represented by the switches. Additinoally, we can see everything leading to the final block at the end denoted with the `jmp 00000250'880128b9` seen earlier.
![[Pasted image 20260225202058.png]]
While statically reversing this would be a great next step. The likely RC4/AES encryption with XOR obfuscation, combined with sleep mask obfuscation through code control flow, substitution, and split blocks makes it very time consuming to go down that path. Having found the sleep mask is still a high indicator of compromise, and looking further into the stack of the thread where the beacon lives will give insight to where the encrypted beacon is likely living at.
### **Finding the Encrypted Beacon**
Turning back to the thread of the sleep mask, the stack can be analyzed to potentially determine the location of the encrypted beacon. Because this is a x64 Process, the registers were initially used to determine what was being passed, but those were unable to be recovered from the dmp. The Stack Pointer can be found by looking back at the Call Stack #02 within the thread.
![[Pasted image 20260225205539.png]]
Running `dps 000000b072ffe410 L40` will return:
![[Pasted image 20260225214538.png]]
Analyzing part of the stack we can see multiple values towards the top of `0000000000036490`. This hex value is equivalent to about ~222KB, which is in the size range for a beacon. Additionally looking around at other values, there is many references to Memory Addresses in the range `00000250xxxxxxxx`. Many of these fall within the range of the sleep mask and the heap. Memory allocated through API's like VirtualAlloc will normally end in a `000` to keep page alignment. Using this methodology it narrows down to a potential base address of `250b8f91000`.
Based on previous stack analysis we have a good starting point for the potential beacon size and the base address of the beacon. We can check the page size to determine if this base address matches the beacon size.
![[Pasted image 20260225220055.png]]
What came as an initial surprise was a region size of only 64kB. It was later learned that drip loading was used for beacon deployment. Analyzing the other memory addresses would yield similar results of small region sizes. It can't be confirmed at this time, but it's likely because of the drip loading and other mutations being done on the beacon why the region sizes are broken into smaller sizes. Although, the size can be specified with the base address to dump memory for the desired range.
![[Pasted image 20260225221239.png]]
While the beacon is still fully encrypted, combined with the sleep mask stub gives opportunity for further dynamic and static analysis.
### **Automating the Uncovering**
Based off the initial samples dissected, while hard coded values would change, the overall flow introduced by the mutator kit still had a pattern-like feel to it. [Much of these heuristics were able to be built into a Python script for quick analysis of common sleep mask routines](https://github.com/lukinneberg/DmpMempryAnalysis/blob/main/findstub.py). In particular, a function was able to be created for the Arsenal Mutator Kit, which when supplied a .dmp file will help pinpoint where in memory the mutated sleep mask may potentially be hiding.
![[Pasted image 20260308120841.png]]