When I think about defense in the current era, I think of it as a game of instrumentation and telemetry. A well-instrumented endpoint provides a defense team and an automated security solution with the potential to react to or have visibility into a lot of events on a system. I say a lot, because certainly some actions are not easy to see [or practical to work with] via today’s instrumentation methods.
A popular method to instrument Windows endpoints is userland hooking. The process for this instrumentation looks like this:
(a) load a security product DLL into the process space [on process start, before the process starts to do anything]
(b) from the product DLL: installs hooks into certain APIs of interest. There are a lot of different ways to hook, but one of the most common is to patch the first instructions in a function-of-interest to jump to the vendor’s code, do the analysis, execute the patched over instructions, and resume the function just after the patch.
This method of instrumentation is popular because it’s easy-ish to implement, well understood, and was best practice in security products for a very long time. It’s still common in a lot of security technologies today.
The downside of the above instrumentation method is that it’s also suscpetible to tamper and attack by an adversary. The adversary’s code that lives in a process has the same rights and ability to examine and change code as the security product that installed itself there.
The above possibility is the impetus for this blog post. I’d like to walk you through a few strategies to subvert instrumentation implemented as userland hooks with the Cobalt Strike product.
Which products use hooks and what do they hook?
Each of these techniques does benefit from awareness of the endpoint security products in play and how [also, if] they use userland hooks to have visibility. Devisha Rochlani did a lot of work to survey different products and document their hooks. Read the Anti-virus Artifacts papers for more on this.
To do target-specific leg work, consult Matt Hand’s Adventures in Dynamic Evasion. Matt discusses how to identify hooks in a customer’s environment right now and use that information to programatically craft a tailored evasion strategy.
Avoid Hooks with Direct System Calls
One way to defeat userland hooks is to avoid them by making system calls directly from our code.
A direct syscall is made by populating registers with arguments and a syscall number that corresponds to an API exposed to userland by the operating system kernel. The system call is then invoked with the syscall instruction. NTDLL is largely thin wrappers around these kernel APIs and is a place some products insert their hooks. By making syscalls directly from our code, and not calling them via NTDLL (or an API that calls them via NTDLL), we avoid these hooks.
The value of this technique is that we deny a security product visibility into our actions via this means. The downside is we have to adapt our code to working with these APIs specifically.
If a security product isn’t using userland hooks this technique provides no evasion value. If we use system calls for uninteresting (e.g., not hooked) actions–this technique provides no evasion value.
Also, be aware that direct system calls (outside of specific contexts, like NTDLL) can be disabled process-by-process in Windows 10. This is the ProcessSystemCallDisablePolicy. If something can be disabled, I surmise it can also be monitored and used for detection purposes too. This leads to a familiar situation. A technique that provides evasion utility now can also provide detection opportunities later on. This is a trueism with most things offense. Always keep it in mind when deciding whether or not to use a technique like this.
With the above out of the way, what are some opportunities to use system calls from Cobalt Strike’s Beacon?
One option is to use system calls in your EXE and DLL artifacts that run Cobalt Strike’s Beacon. The blog post Implementing Syscalls in the Cobalt Strike Artifact Kit walks through how to do this for Cobalt Strike’s EXEs and DLLs. The post’s author shared that VirtualAlloc, VirtualProtect, and CreateThread are calls some products hook to identify malicious activity. I’d also go further and say that if your artifact spawns a process and injects a payload into it, direct syscalls are a way to hide this behavior from some security stacks.
Another option is to use system calls within some of your Beacon post-exploitation activities. While Beacon doesn’t use direct system calls with any of its built-ins, you can define your own built-ins with Beacon Object Files. Cornelis de Plaa from Outflank authored Direct Syscalls from Beacon Object Files to demonstrate how to use Jackson T.‘s Syswhispers 1 (Syswhispers 2 just came out!) from Beacon Object Files. As a proof-of-concept, Cornelis released a Beacon Object File to restore plaintext credential caching in LSASS via an in-memory patch.
Building on the above, Alfie Champion used Outflank’s foundation and re-implemented Cobalt Strike’s shinject and shspawn as Beacon Object Files that use direct system calls. This provides a way to do process injection from Cobalt Strike, but evade detections that rely on userland hooks. The only thing that’s missing is some way for scripts to intercept Cobalt Strike’s built-in fork&run actions and override the built-in behaviors with a BOF. Hmmmmm.
Refresh DLLs to Remove Function Hooks
Another way to defeat userland hooks is to find hooks implemented as code patches and restore the functions to their original uninstrumented state. One way to do this is to find hooked DLLs in memory, read the original DLL from disk, and use that content to restore the mapped DLL to its unhooked state. This is DLL refreshing.
The simplest case of DLL refreshing is to act on NTDLL. NTDLL is a good candidate, because its really easy to refresh. You don’t have to worry about relocations and alternate API sets. NTDLL is also a good candidate because it’s a target for security product hooks! The NTDLL functions are often the lowest-level API that other Windows APIs call from userland. A well-placed hook in NTDLL will grant visibility into all of the userland APIs that use it.
If NTDLL is not enough, you can refresh all of the DLLs in your current process. This path has more peril though. The DLL refreshing implementation needs to account for relocations, apisets, and other stuff that makes the unhooked code on disk differ from the unhooked code in memory. Jeff Tang from Cylance’s Red Team undertook this daunting task in 2017 and released their Universal Unhooker (whitepaper).
I’ve put together a Beacon Object File implementation of Cylance’s Universal Unhooker. The script for this BOF adds an unhook alias to Beacon. Type unhook and Beacon will pass control to the unhooker code, let it do its thing, and then return control back to Beacon.
Both of these techniques are great options to clean your Beacon process space before you start into other offense activities.
While the above are Beacon Object Files and presume that your Beacon is already loaded, you may also find it’s worthwhile to implement DLL refreshing in your initial access artifact too. Like direct system calls, this is a way to defeat userland hooking visibility that could affect your agent loading or its initial communications.
Prevent Hooks via Windows Process Mitigations
So far, we’ve discussed ways to defeat hooks by either avoiding them or undoing them. It’s possible to prevent hooking altogether too.
I became interested in this approach, when I learned that Google Chrome takes many steps to prevent security products from loading into its process space. Google was tired of entertaining crash reports from poorly implemented endpoint security products and opted to fight back against this in their own code. I share Google’s concerns about allowing an endpoint security product to share space with my post-exploitation code. My reasons are different, but we’re very much aligned on this cause!
The above led me to experiment with the Windows 10 process mitigation policy, BinarySignaturePolicy. A process run with a BinarySignaturePolicy of MicrosoftSignedOnly will refuse to load any DLL not signed by Microsoft into that process space. This mitigation prevents some security products from loading their DLLs into the new process space.
I opted to use the above to implement blockdlls in Cobalt Strike 3.14. blockdlls is a session prepping command to run processes with this flag set. The idea of blockdlls is processes spawned by Beacon will be free to act with less scrutiny, in some situations.
There are caveats to blockdlls. The mitigation is a recent-ish Windows 10 addition. It doesn’t work on versions of Windows where this mitigation isn’t implemented. Duh! And, security vendors do have the option to get Microsoft to sign their DLLs via an attestation service offered by Microsoft. A few made this exact move after Cobalt Strike weaponized this mitigation in version 3.14.
For more information on this technique and its variations, read Adam Chester’s Protecting Your malware with blockdlls and ACG. It’s a great overview of the technique and also discusses variations of the same idea.
Like direct system calls, I see the use of process mitigations as an evasion that is also potentially its own tell. Be aware of this tradeoff. Also, like direct system calls, this is an option that has use both during post-exploitation and in an initial access artifact. Any initial access artifact that performs migration (again, Cobalt Strike’s service executables do this) could benefit from this approach in some security stacks too.
And, there you have it. This blog posted presented a few different techniques to defeat userland hooks with Cobalt Strike. Better, each of these techniques delivers benefit at different places in Cobalt Strike’s engagement cycle.
Be aware that each of these methods is beneficial in very specific circumstances. None of the above will have impact against technologies that do not use userland hooks for instrumentation. Offense is always about trade-offs. Knowing the techniques available to you and knowing their trade-offs will help you assess your situation and decide the best way forward. This is key to good security testing engagements.