Its no secret that MDSec provides a commercial command-and-control framework with a focus on evasion for covert operations. With this in mind, we are continuously performing on-going R&D in to techniques to not only hide a beacon, but also detect them. Indeed, some would argue that the best way to build an elusive beacon, is to not only understand the ways in which your opposition detects you, but to try and find new ways that they could detect you in the future.
During this research we will outline a number of effective strategies for hunting for beacons, supported by our BeaconHunter tool that we developed to execute these strategies and which we intend to open source in due course. In the following posts to this research, we will then step in to a number of case studies for applying these detections to a variety of both commercial and open source frameworks.
This material was originally discussed at x33fcon 2022, the slides for this material can be found here.
While there are a variety of different approaches for detecting a beacon running inside your network, we will focus less on the functionality of specific post-exploitation functionality, and more on generically identifying the beacons being residing or being loaded in to memory.
The behaviour of the beacon whilst its not only operating but also loading can give rise to detection opportunities for defenders. Many of the commercial frameworks are closed source, and as such some behaviours cannot trivially be changed, allowing defenders to create signatures aligned to these actions.
A great example of this is how the beacon loads itself and its dependencies; lets look at how behaviour associated with image loads might provide detection opportunities for defenders.
In order for a beacon to provide a rich post-exploitation framework, it often relies heavily on libraries native to the operating system, allowing the developer to keep the beacon size as small as possible by avoiding statically bundling many dependencies.
From analysing a number of beacon frameworks, we’ve noticed that many of these will proceed to load all dependencies required for the core beacon at load time as opposed to on use. In some cases, this leads to a unique sequence of events occurring on the endpoint, which can be trivially signatured by defenders.
For example, it is common to see beacons leveraging the native Windows HTTP libraries for egress beacons by loading winhttp.dll and wininet.dll; these may stand out as anomalies when loaded in to processes that would not normally perform HTTP interaction. Furthermore, some beacons have been noted to load libraries that are used more sparsely such as credui.dll, dbghelp.dll or samcli.dll.
Using these sequences of DLL loads, it becomes feasible to build signatures using EQL rules to detect when the beacon is executed.
For example, using an EQL rule similar to the following, it is possible to detect or hunt for all processes loading the credui.dll, and winhttp.dlls within a short period of time:
sequence by Image with maxspan=1m
[any where ImageLoaded == 'C:\\Windows\\System32\\credui.dll']
[any where ImageLoaded == 'C:\\Windows\\System32\\winhttp.dll']
This category of detection can of course be avoided by beacons that are designed to be modular or load dependencies on use or with delayed loads.
In many cases, beacons may remain memory resident in order to avoid on disk detections. The beacon is typically injected in to memory by a loader, which will create a new or hijack an existing thread in which to run the beacon in.
A typical loading process for the beacon may look as follows:
Once the beacon is running in memory, through analysis of the process there are often a number of indicators that we can leverage to detect the beacon; let’s look at some approaches for in-memory detections.
Perhaps one of the simplest, but most effective strategies for detecting in-memory beacons for known malware is through signature detection. While many anti-virus engines and EDRs implement their own memory scanning routines, threat hunters can readily achieve comprehensive memory scanning using Yara rules.
A simple Yara rule that can be used with the yara64.exe command line utility might look like this, which would match on detecting any of the three listed strings in memory:
rule howimetyourbeacon
{
meta:
author = "domchell"
description = "Simple yara rule"
strings:
$a = "How"
$b = "I"
$c = "Met"
$d = "Your"
$e = "Beacon"
condition:
3 of them
}
Creating Yara rules for strings/data embedded within the beacon, or code from the .text section can conceptually be used as an effective technique for detecting known in-memory malware.
Elastic have done some great work in the past on how this can be leveraged to detect Cobalt Strike in memory and is recommended reading for how these techniques can be applied in practice.
To evade such memory scanning, beacons can employ a number of techniques to obfuscate their footprint in memory, including replacing known strings such as can be achieved using Cobalt Strikes strrep malleable profile option and/or the use of a obfuscate and sleep strategy such as the one we use in Nighthawk to protect all strings, data and code of the beacon while sleeping.
In order to circumvent controls or change how a process might function, beacons (or the operator) may apply hooks to certain functions in memory. These hooks can leave hidden traces that can provide threat hunters opportunities to reveal hidden beacons. Lets examine some examples of this behaviour.
ETW and AMSI Patching
Patching security controls such as AMSI and/or impairing telemetry obtained through Event Tracing for Windows is no secret in the offensive community, indeed we have previously blogged about these tactics in the past.
These patches are typically applied by modifying memory; let’s look at two examples taken from the Sliver C2 armory:
https://github.com/sliverarmory/injectEtwBypass
https://github.com/sliverarmory/injectAmsiBypass
As we can see in the screenshots above, both of these examples will cause the beacon to apply a patch to either the ntdll!etwEventWrite or amsi.dll!AmsiOpenSession functions.
With this in mind, a low noise detection opportunity arises simply by hunting for processes with these patches applied to these and other commonly patched functions such as AmsiScanBuffer or SleepEx which a hook is applied to by Cobalt Strike’s thread stack spoofing feature from the arsenal kit.
Copy on Write Modifications
As noted above, it is not unusual for beacons to apply patches to process memory which can create detection opportunities for hunters. The fruitfulness of these hunts can however be reduced should the beacon remove the patches once the post-exploitation operations have been performed. For example, the implant may apply an AMSI patch prior to executing a .NET assembly in memory, then following execution revert patch to its original opcodes. This approach is moderately more intelligent than simply leaving dangling patches in-memory.
However, in order to avoid duplication, Windows will back common DLLs to physical memory that is shared across running processes. Should a beacon or the operator perform an operation that applies a patch to one of these DLLs, a copy on write operation will occur, making the page private to that process. Using the QueryWorkingSetEx API, we’re able to query information about the pages at a specific virtual address of a process. Within the returned PSAPI_WORKING_SET_EX_INFORMATION structure is a PSAPI_WORKING_SET_EX_BLOCK union that indicates the attributes of the page at the queried address. Within this union we’re able to determine if a copy on write operation occurred on the page by the return value of the Shared bit. This technique is used by memory scanners such as Moneta and is highly effective, at detecting in-memory patches, even if the original patch value is restored.
There are however some risks of false positive in applying this technique at scale in that it is not uncommon for EDRs and anti-virus software to apply their own in-memory hooks, meaning that they can invalidate some of the value we can obtain from looking for copy on write operations. However, to perhaps reduce the risk of false positives, we can apply more intelligence to this by resolving the exports on the modified pages and applying a greater weighting to functions that EDRs do not typically hook such as EtwEventWrite or AmsiScanBuffer.
As previously noted, once the beacon is operating in memory it will typically live inside one or more threads, depending on if the beacon is synchronous or asynchronous. Anomalies associated with these threads can provide high signal indicators of beacon activity, in particular when combined with other indicators or in conjunction with each other. Some common suspicious thread related indicators include the following:
Attempts to evade detections around suspicious threads have become recently popularised, with several proof of concept and commercial implementations being published.
These implementations typically work by either truncating the call stack of the thread (for example by setting the frame’s return address to null) or by cloning the context of an existing thread. With this in mind, we have further indicators that we can hunt for to add to our metrics:
Page Permissions
Generally speaking, the beacon will be operating from virtual memory, or from within a region backed by a DLL if module stomping.
In order for the beacon to recover and execute its tasking, the pages where the beacon is living need to have execute permissions applied to them. If this is from virtual memory and with the exception of jitted code (eg the CLR), it is highly irregular to see private committed memory with executable permissions.
For example, we can see in the following screenshot that the memory at 0x22f96c5000 is not backed by a DLL, it is marked as “Private:Commit” (i.e. virtual memory as a result of VirtualAlloc) and has the RX page permissions set:
These indicators are a strong signal that there is a beacon executing in this region.
The challenge then becomes how can this indicator be avoided; well the simple answer is it cannot if your beacon is operating from virtual memory, at some point the beacon needs to be executing. The compromise is really to only maintain the executable permissions while the beacon is performing tasking, and leverage strategies to remove the executable permissions while the beacon is sleeping. Therefore, avoiding tradecraft such as SOCKS proxying can help to minimise the exposure of this indicator:
Several implants employ strategies for shuffling the page protections according to the beacon state, as well as several open source implementations such as @Ilove2pwn_’s Foliage, @c5pider’s Ekko, @mariuszbit’s ShellcodeFluctuation and Josh Lospinoso’s Gargoyle.
These strategies will typically leverage some form of event driven execution to sleep and wake the beacon, reengaging execution using ROP gadgets to call VirtualProtect and reset the beacon’s pages back to executable permissions. The Timer based technique discovered by MDSec’s Peter Winter-Smith and as used by Ekko, was originally reverse engineered from MDSec’s Nighthawk c2. In short, this technique works by queueing a number of timers using CreateTimerQueueTimer, which then when the event triggers will return to a previously defined Context record, executed using NtContinue and calling VirtualProtect to reenable the execute bit.
To understand this technique in greater detail, the original write up by @Ilove2pwn_ can be found here.
Module Stomping
Module stomping provides an alternate method of hiding your beacon in memory, avoiding some of the common indicators associated with beaconing from unmapped memory. Conceptually, to achieve this, a legitimate DLL that is unlikely to be used by the process is loaded and the beacon copies itself over the module; a thread is then created backed by the stomped code:
Cobalt Strike has offered this feature since its 3.11 release and is available using the “set module_x64 / module_x86” malleable configuration options.
While this technique can provide a number of OpSec advantageous, it does leave behind several indicators which we can reliable detect on:
In conclusion, we’ve outlined a number of strategies for detecting in-memory beacons. In the next part of this series, we’ll begin to look at a number of case studies of real frameworks and see how we can apply these techniques.
This post was written by Dominic Chell.