Nighthawk 0.1 – New Beginnings
Introduction
MDSec’s ActiveBreach red team operate in the some of the highest maturity environments, where a significant degree of in-memory and post-exploitation operational security is often required to counteract defensive controls that might be in place. For many of our operations, we found that the c2 tooling that was available to us, both open source and off the shelf, was insufficient for us to operate in the way we wanted.
With this in mind, we began full time development of our own c2 in early 2020 and it quickly became apparent, to do this properly and to keep it relevant, it needed to be more than just a hobby that’s juggled around other full time work. Initially, our expectation was this would remain in-house, but as ideas, feature requests, bug fixes and R&D grew, along with expanding the development team, we decided to commercialise Nighthawk in to a product in order to sustain the model, as well as fund some of the future innovations on our roadmap.
Over the past few months, we’ve worked with a number of other red teams to get some external eyes on Nighthawk and incorporated their feedback to improve the product where we could. That brings us to today, where we’re happy to officially make Nighthawk 0.1 available to subscribers. In this post we will briefly introduce more information around Nighthawk, it’s design and overview of some of its features.
Architecture
One of the key design decisions when creating Nighthawk was to make the beacon as customisable and extensible as possible. To achieve that, we opted to build the management interface using a HTTP API; the rationale behind this was that the operator console actions would be abstracted from the the actual beacon management and as such automation and extensibility could be built-in using scripts, decoupled from the User Interface.
A typical red team infrastructure deployment for an operation using Nighthawk might look as follows, where the yellow zone contains the API management server (which the operators interact with through the UI) and n number of C2 servers which can be deployed to support multiple profile configurations:
Natively, Nighthawk supports HTTP(S) for egress and SMB named pipes and TCP for peer-to-peer c2 (more on this later). However, the transport mechanism is abstracted from the c2, and with support for multiple custom transports inside the same beacon, it means that every link in the c2 network can be entirely customised to talk a unique protocol. This concept provides a unique advantage to operators as it means that network layer indicators of compromise can be reduced by not only blending the c2 traffic with legitimate network communications, but also avoiding reuse of the same transport across each p2p link, making the c2 more complex to hunt for.
Here’s a simple example of the beacon injected in to the teams.exe process, and communicating over a Microsoft Teams channel for egress:
Taking this to the extreme, it would be even possible to customise Nighthawk such that it could circumvent air gaps, for example by using USB as a transport.
The native HTTP(S) c2 transport is also extremely flexible and extensible, providing the operator with full control over the requests and responses and with support for multiple URIs, beaconing hosts and retry counts. The logic that governs the c2 request and responses uses regex for pattern matching, meaning that the c2 comms can be blended in any way the operator chooses, as long as the regex matches. For example, a tasking request/response could be sent embedded within an image’s metadata or within a block of javascript. As our c2 servers support the execution of arbitrary python from within the malleable configuration profiles, the c2 can trivially be extended to layer multiple levels of obfuscation, encryption, randomness or intelligence.
The native HTTP(S) can also be encoded as it leaves the beacon through the use of .NET custom encoders; this allows the operator to programatically mutate the c2 traffic to blend in with classic web traffic. Here’s a simple example of this where we “hot swap” our c2 and mutate the traffic in to Italian words:
Overall, the architecture and design of Nighthawk provides the operator with significant flexibility to achieve automation and extend the beacon beyond its default feature set.
Operational Security
One of the key factors for us in developing Nighthawk was we had a significant project driven requirement for OpSec, and as such we tried to embed OpSec concepts in to the beacon from the outset.
Nighthawk is developed in c++
and comes as a reflective DLL which can be exported in to a number of different artifacts, including compressed shellcode for integration with other tools. The reflective loader used by Nighthawk is a custom implementation that can be optionally configured to use direct system calls or native APIs; the bootstrapping code for this is then of course cleaned up following execution.
The Nighthawk beacon is multi-threaded, meaning that tasks will be executed synchronously and can additionally be batched through the UI. The beacon spoofs the start address and stacks of its threads according to the configured profile, making them less trivial to find in memory when at rest and leaving it absent of IoCs affecting other c2s such as threads originating from virtual memory. Furthermore, as a result of MDSec’s R&D, all thread creation incorporates several techniques to confuse EDR telemetry obtained through kernel callbacks and EtwTi.
Nighthawk will periodically sleep inline with the configured sleep time and jitter, then wake to receive tasks. While the beacon is sleeping, the reflective DLL remains fully encrypted in memory; this is possible without the need to suspend all threads within the process, meaning we’re able to operate smoothly in injected processes and without disruption to the user experience. Furthermore, the self encryption process is fully configurable by the operator, with multiple strategies available for performing self decryption, all of which can be executed in an OpSec safe manner to avoid IoCs hunted for by many memory scanners and hunt tools, including publicly available ones such as pe-sieve and Moneta.
A problem for many c2s is the data that gets leaked through memory acquisition or identified during in-memory scanning. To counteract this and in addition to encrypting the Nighthawk reflective DLL while sleeping, Nighthawk also offers a relatively unique feature that allows the operator to encrypt data placed on the heap. To achieve this, we offer two modes of operation; “implant” or “process” heap encryption. The former ensures that any heap allocations originating from a Nighthawk thread are encrypted, including those from BOFs (more on this later). While this strategy is highly effective, allocations made from Windows libraries remain out of our control and they cannot necessarily be relied on to free or clear allocations in an OpSec friendly manner. For example, any c2 using winhttp.dll
for performing HTTP(S) requests may find its c2 URLs sitting on the heap in plaintext indefinitely. With this in mind, the other mode for heap protection we offer is “process” which causes Nighthawk to deploy a number of hooks across the heap routines within the process, replacing them with the Nighthawk secure allocation implementations which encrypt and clean memory.
User mode hooks put in place by EDRs is a known challenge for post-exploitation in many operations. Nighthawk alleviates these problems with an aggressive strategy for removing EDR hooks, with a number of configurable options to not only unhook DLLs, but also the in-memory syscall stubs that may have been modified by the EDR. Our strategy for this only removes hooks where necessary, using similar techniques to Moneta to detect when an EDR has tampered with a process, and configurable options to determine whether syscalls or NT APIs should be used to perform the unhooking strategy. These are then subsequently dynamically calculated and executed using ROP gadgets sourced from NTDLL. Without giving too much detail away, this really just scratches the surfaces of the evasion techniques we’ve implemented to circumvent hooking.
A few years ago, Cobalt Strike introduced the “block DLLs”
feature to prevent EDRs injecting non-Microsoft signed DLLs in to processes. At the time, this was a somewhat effective technique for defeating EDR telemetry, hooks and other controls and in some situations can still work today, but of course this leaves the problem of the dangling PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
mitigation flag which is somewhat irregular for many processes and is often hunted for by threat hunting teams. Conceptually though, we felt the premise had legs and we liked the idea of being able to prevent DLLs being loaded in to our implanted process. As such, we introduced our own “block-dlls”
configuration option that allows the operator to define a list of DLLs that will be blocked from being loaded in the process; this is achieved through hooks to the Windows library loading routines. This can not only be used to prevent EDRs loading their user mode DLLs, but is also highly effective in preventing AMSI being instantiated during CLR loads, avoiding the traditional AMSI bypasses such as patching of AMSIScanBuffer
which many EDRs are now detecting.
There is of course much more done to bring OpSec to the beacon, but hopefully this gives you a taster of some of the highlights. Here’s a short video of Nighthawk spoofing its thread stack as an example:
Process Injection
Nighthawk is predominantly designed to operate in-process and to maintain a minimal footprint on the endpoint. There are of course circumstances where you may want to perform injection, typically this may include situations such as deploying a backup beacon, gaining access to an alternate desktop session or wanting to blend your tradecraft in with a specific process. Nighthawk provides the spawn-rdll, spawn-shellcode, inject-rdll and inject-shellcode commands within the UI to perform these operations.
To accommodate such situations, Nighthawk provides a number of configurable options, leaving each step of the injection chain in the hands of the operator. To achieve this, we separated the injection chain in to the following steps for both fork and run and cross process injection:
- ProcessCreate: the injector functions that should be used when the operator wants to create a new process,
- ProcessOpen: the functions used to open a handle to a process,
- AllocMemory: the strategy to be used to allocate memory in the process,
- WriteMemory: the technique to write memory in to the remote process,
- ProtectMemory: the injector functions that should be used to change protections on memory in the remote process,
- ThreadOpen: the technique used to open a thread in the remote process, either by thread creation or hijacking of existing threads,
- ExecuteMemory: the injector function used to execute memory within a remote thread.
A number of options exist within each of these steps, for both Windows or NT APIs, or alternatively if the use-syscalls
configuration option is set to true, system calls. Furthermore, when performing cross architecture injection, in WoW64 mode the strategies will use x64 system calls (not to be confused with NT APIs), dynamically calculated using an ASM implementation of the SSN hash lookup technique previously described on our blog. Cross architecture injection is fully supported with our injector chain strategy.
A sample injector chain from the beacon configuration might look something like this:
"injector": {
"methods": {
"ProcessCreate": "CreateProcessWinApi",
"ProcessOpen": "OpenProcessNative",
"AllocMemory": "CreateSectionNative",
"WriteMemory": "CreateAndMapSectionNative",
"ProtectMemory": "VirtualProtectNative",
"ThreadOpen": "GetRandomThreadNative",
"ExecuteMemory": "QueueAPCNative"
},
}
Performing cross process injection with such a chain would open a handle to the process using NtOpenProcess
, allocated memory in it through NtCreateSection
, map memory to the process using NtMapViewOfSection
, set the appropriate page permissions through NtProtectVirtualMemory
, find an already existing thread using NtGetNextThread
then finally queue an APC on it using NtQueueApcThread
. Of course, if the use-syscalls
configuration option is set, our API will dynamically enumerate the equivalent syscalls and execute them using ROP using randomly sourced gadgets.
Within the operator console, we also provide a set-injector-step
command that allows the operator to dynamically update any of these steps at runtime, meaning injections during operations can be configured based on intelligence gathered from the endpoint:
Tool Integration
Like many other red teams, we develop much of our tooling in .NET or using Beacon Object Files. As such, it was important to us that Nighthawk incorporated harnesses for both of these to allow us execution of not only our in-house tools, but also those contributed by the community. As such, we provided a BOF loader for both x86 and x64 BOFs via the execute-bof
command. Nighthawk fully supports the Cobalt Strike BOF API and as such BOFs require no modifications and can be precompiled to execute in either Cobalt Strike or Nighthawk:
For any actions within Nighthawk we support aliases (with varargs), this allows you to trivially extend the commands available within the beacon such as the above, which demonstrates aliasing a BOF to an internal Nighthawk command. These get persistently stored within the Nighthawk UI configuration meaning the operator can customise their console by building up a convenient list of shortcuts.
Nighthawk’s CLR harnesses avoid the nasty fork and run model, offering two modes of operation; in-process or remote process, where the latter allows you to inject an assembly in to an already running process (e.g. one that may already have the CLR loaded) and capture the output. In the injection scenario, it of course respects the aforementioned injector steps strategy and the use of syscalls where applicable.
Performing CLR execution in-process sounds risky you might say, and it’s true many of the public PoCs for in-process .NET execution are risky and could leave your beacon unstable in the event of a badly behaving assembly. However, this is an area we have given significant attention over the past 18 months and you may remember some of our previous research on the topic. Considerable effort has gone in to maintaining the stability and integrity of the beacon when executing .NET assemblies in process, and through many lessons learned through not just QA testing but also field work by our red team, we’ve been able to produce a highly reliable and fault tolerant harness. This feature brings a lot of power to the operator as it means they’re able to reliably and safely perform .NET post-exploitation in their beacon, without opening any handles to other processes or needing to perform injection.
You may also wonder about other factors that can hinder .NET post-exploitation such as ETW and AMSI. We of course introduced various configurable options for hindering these controls, including patching of EtwEventWrite
, ETW system calls and AMSI, in addition to the previously described block-dlls option which can neuter AMSI entirely. Our strategy for this mirrors other patching that we perform, where by the patches are made only for the life of the CLR execution, then restored on completion to avoid unnecessarily increasing the window for detection by in-memory scanners or threat hunting teams.
Post Exploitation
In addition to the above, Nighthawk is packed with post-exploitation features that align to the OpSec principles we’ve already detailed. We’ve loosely grouped some of these in to several high level areas to provide a flavour of some of the capabilities, though the list is not conclusive and excludes other basic c2 core features like impersonation and SOCKS.
Process and Host Triage
The ability to triage the host and other running process is important for an operator when making decisions on their tradecraft. To assist with this, Nighthawk provides a number of built-in Windows API driven commands, including:
- logons: recovers detailed information on the logon sessions on the host,
- pipelist: list the named pipes available on the host,
- proflist: list the user profiles and information about the user,
- windows: recover a list of registered windows for a given process,
- applist: provide a detailed list of applications installed on the host, including publishers, versions and registered uninstall handlers,
- portlist: list current network connections akin to a netstat,
- services: retrieve detailed information about the services on a host,
- threads: list details of threads in a remote process, including backed image locations,
- modules: display information about the loaded DLLs in a remote process, inclusive of image path and description,
- objects: list objects in the NT directory manager, including their type,
- handles: display information about the handles for a remote process.
User Monitoring
The ability to monitor a user is often pivotal to an operation when attempting to achieve objectives, in particular where intimate knowledge of business processes is required. Nighthawk provides operators with built-in screenshot and keylogging commands aligned to our OpSec principles, which allow the operator to closely monitor user behaviour:
Credential and Token Access
Recovering and leveraging existing user access is an important part of a red team engagement, Nighthawk empowers the operator with a number of features to hijack tokens and credential material.
The first of these seemed to inspire other c2s when we first released details of it and allows the operator to steal impersonation tokens from other user’s sessions on the host. Duped handles to the tokens get placed inside a token store within the beacon and allow the operator to hot swap between them. This feature is useful for scenarios where the operator has compromised a host with multiple users logged on such as a jump server, and wants to be able to capture tokens to impersonate the user either locally or across other resources on the network:
Another built-in command that we offer for credential recovery is “credman“, this command uses a custom implementation of James Forshaw‘s “Dumping Stored Credentials with SeTrustedCredmanAccessPrivilege” technique to recover stored credentials from the Windows Credential manager:
Aside from the Windows Credential Vault, we know red teams love to harvest those sweet plaintext credentials you can find hiding inside lsass or other processes like password managers. That is why, we’ve provided an OpSec friendly process memory dumper that will extract a full dump over the c2 and straight down to the operator endpoint, without touching disk on the compromised host; here’s a peak at it in action:
These are just a taster of some of the features we’ve packed in to the 0.1 release, and a huge thank you to Peter and @modexp who’ve invested a considerable amount of time getting it over the line.
If you’re interested in purchasing Nighthawk, you can find further details here.