This project is ( mostly ) a callstack scanner which tries to identify IOCs indicating an unpacked or injected C2 agent.
All checks are based on the observation that C2 agents wait between their callbacks causing the beacons thread to idle and this tool aims to analyze what potentially caused the thread to idle.
This includes traditional IOCs, such as unbacked memory or stomped modules, but also attempts to detect multiple implementation of sleepmasks using APCs or Timers. The latter is done by both: analyzing the callstack but also enumerating timers and their exact callbacks from userland.
(Almost) none of those IOCs can be considered a 100% true positive, the module stomping detection e.g. is very prone to false positives. Yet, the results might raise suspicion about the behaviour of a process.
DotNet and 32Bit binaries are ignored.
A private r(w)x page in a callstack might indicate a beacon which was unpacked or injected at runtime.
Multiple Sleepmasks change the page permissions of the beacon's page to non-executable. This leads to a suspicious non-executable page in the callstack.
Often, beacons avoid private memory pages by loading and overwriting a legitimate module from disk.
Thanks to the copy on write
mechanism, manipulated images can be identified by checking the field VirtualAttributes.SharedOriginal
of MEMORY_WORKING_SET_EX_INFORMATION
. If any page in the callstack is not private and SharedOriginal == 0
, it is considered an IOC.
This is probably the detection the most prone to false positives. :'(
Multiple implementations of sleepmasks queue a series of APCs to Ntdll!NtContinue
one of which triggers the execution of Ntdll!WaitForSingleObject
. Thus, if Ntdll!KiUserApcDispatcher
can be found on the callstack to a blocking function, this tool considers it an IOC.
Similar to the suspicious usage of APCs, this tool also checks for ntdll!RtlpTpTimerCallback
on the callstack to a blocking function to detect timer-based sleepmasks.
To my understanding, Timers are implemented on top of ThreadPools. As Alon Leviev has demonstrated those can be enumerated using NtQueryInformationWorkerFactory
with WorkerFactoryBasicInformation
.
The WORKER_FACTORY_BASIC_INFORMATION
struct embeds a FULL_TP_POOL
which in turn links to a TimerQueue
double linked list. Traversing that list of PFULL_TP_TIMER
allows accessing each registered callback. If any callback is found pointing to a set of suspicious api calls, such as ntdll!ntcontinue
, it can be considered a strong IOC.
Originally module proxying was introduced as a method to bypass suspicious callstacks. While the bypass works, it introduces an other strong IOC, as the NTAPI is used to call the WINAPI. This is odd, as WINAPI is an abstraction for NTAPI. Thus, if a callstack is observed in which a sequence of ntdll.dll->kernel32.dll->ntdll.dll is found ending up calling a blocking function it can be considered an IOC.
Most Returnaddress spoofing implementations I am aware of make use of a technique in which the called function returns to a jmp [Nonvolatile-Register]
gadget. This project simply iterates every return address in callstacks and searches for patterns indicating the return to a jmp gadget.
_ _ _____ ______
| | | | / ___| | ___ \
| |_| | \ `--. | |_/ /
| _ | `--. \ | ___ \
| | | | /\__/ / | |_/ /
\_| |_/ \____/ \____/
Hunt-Sleeping-Beacons | @thefLinkk
-p / --pid {PID}
--dotnet | Set to also include dotnet processes. ( Prone to false positivies )
--stackspoofing | Enables a check to detect stackspoofing
--commandline | Enables output of cmdline for suspicious processes
-h / --help | Prints this message?