October 30, 2023 By Dave Cossa 13 min read

NightHawk, MDSec’s commercial C2 product, has focused on operational security and detection avoidance since its initial release in December 2021. While the core functionality of the framework has been effective within the scope of these objectives, our team noticed certain features were missing as we started incorporating NightHawk into our engagements alongside our other C2 options. Most notably, there was no equivalent in NightHawk to Cobalt Strike’s Aggressor scripting platform, severely limiting automation capabilities. While I know how big of fans we all are of the Sleep programming language, Aggressor’s functionality was invaluable in our team’s operations, streamlining complex multi-command actions and automating commands to run upon initial beacon check-in.

To address these gaps in automation I built DayBird, an automation package that extends the NightHawk operator UI to provide scripting functionality similar to Aggressor, as well as providing capabilities to automate the execution of code on initial beacon check-in. The best part of it all — DayBird plugins (the equivalent of Aggressor scripts) are entirely written in C#, so you can load plugin projects into Visual Studio, add the reference DLL, and quickly generate a set of steps to automate complex workflows. Plugins can also retrieve results of prior commands, allowing for logic to be implemented that can take appropriate actions based on previous information returned. For example, a plugin could automatically decide on what persistence mechanism to deploy based on the results of the ps command (showing which EDRs may be active on the system) or based on the integrity of the current session.

Due to NightHawk being a closed-source product sold by an independent vendor, we initially chose not to publicly release DayBird. However, earlier this year, we shared the source code with MDSec to explore the possibility of implementing these features or their equivalents in future product releases. With regular inquiries about automation arising in the NightHawk Slack community and version 0.3 scheduled for release sometime next year, complete with a new operator UI, we decided it would be prudent to release this tooling to the public now. This will serve as a bridge until an official operations automation solution is incorporated in the product.

DayBird initial setup and execution

The NightHawk operator UI (UI.exe) is written in C#, a language that provides several advantages both in being able to easily review the original source code of a compiled assembly, as well as in being able to use reflection to interact with running assemblies to insert our own code. Due to some prior work on one of our engagements that involved using .NET reflection to load a DLL into a client’s terminal application and intercept commands, we already had a decent handle on the basics of using reflection to interface with a running application.

Reflection is great, but our assembly still needs to get loaded and run from the same AppDomain that the UI is executing from to get a handle on it. To get our code into this position in a straightforward manner for the operator that doesn’t require secondary executions every time we want to use it, we use AppDomainManager injection (hackers gonna hack). This technique was first publicized by Casey Smith as a code execution avenue in 2020; further information can be found here. AppDomainManager injection allows us to interact with the UI without needing to inject into the already-running process or going through the steps of decompiling the UI, adding our code in, and rebuilding the project every time a new update is released.

DayBird assembly loading into the AppDomain of the running UI via AppDomainManager injection

To further streamline the setup process a small setup assembly has been included in the DayBird repository that performs the necessary file modifications to UI.exe.config, copies over the DayBird and NHAPI libraries, and creates subdirectories for Plugins and Autoruns. This setup assembly requires DayBird.dll and NHAPI.dll to exist in the same directory as the initial setup assembly, but beyond that shouldn’t require any additional information except for the path to the NightHawk UI folder as can be seen below:

DayBird setup execution

Once this setup script has run, DayBird will automatically be loaded any time the NightHawk UI is launched. This can be validated by the presence of the new AutoRuns button that should populate on the top of the UI’s window as soon as the console loads.

Additional menu AutoRuns context loaded by DayBird

Plugin functionality

Plugins are small standalone .NET Framework projects that extend a shared base abstract class located in the NHAPI library included within the DayBird project. Wrapper methods included in this library allow for straightforward automated calls to commonly used NightHawk commands. Additionally, plugins can also retrieve command output, allowing for complex operations that will conditionally implement multiple commands on a beacon. Further information on designing plugins can be found below in the “Creating and Building Plugins” section.

To use a plugin you’ve built, first drop the compiled plugin DLL into the Plugins sub-folder in your NightHawk UI folder, along with any other dependencies (e.g., BOFs or assemblies the plugin will execute, unless you provide explicit paths within your plugin code). Once all requisite files have been added in, load the UI as normal and connect to a beacon. After connecting to a beacon, you can use the plugin command implemented via DayBird to access existing plugins, either by entering the full plugin name or using tab-complete functionality to flip between all loaded plugins or auto-filling a partial plugin name. All plugins have a required help string implemented, which returns basic usage information, as can be seen in the example below:

Plugin help output

Plugin execution itself is relatively straightforward, arguments are passed directly in after the plugin and are processed by logic within the plugin itself:

Basic plugin execution with arguments

Output from plugins can be optionally stored and synced across multiple operator instances, based on configuration within the plugin.

While the above example is quite basic and could likely be achieved with a NightHawk alias, it demonstrates the foundational components of plugin execution. Another example demonstrating a plugin reading in and parsing prior output to perform multiple steps automatically can be seen below:

Plugin technical implementation

Note: This section covers the internals of how DayBird works and doesn’t really provide important info for operational usage. If that’s more to your interest feel free to skip to the next section — AutoRun Functionality.

Once running, DayBird first uses reflection to get references to a variety of internal UI types, which it then uses to register custom event handlers, extend the GUI with an additional button for autorun plugin management, and begin checking for new inbound beacon connections. When a new inbound beacon connection is detected, new key handlers are added to the associated UI console object to allow for interception of commands entered by the operator, as can be seen in the image below. This in turn allows for redirection of execution to DayBird logic, such as loading and executing plugins.

Key handlers added by DayBird to allow for command interception and cleanup

Initially, when trying to implement the ability to intercept commands a roadblock was hit as handlers are processed in the order they are registered, meaning operator commands would be sent to the original UI handler before the one registered by DayBird. However, PreviewKeyDown event handlers are always processed first, with the caveat that they can only read data instead of being able to modify it. To get around this issue, we can again use reflection to get a reference to the current console text window and manually clear the command, meaning commands won’t get processed a second time by the UI. This process allows for implementation of any additional custom commands or checks we want to add on commands being passed into the NightHawk operator UI.

As all commands are being intercepted, another use-case for DayBird is to perform opsec checks on inputs in order to intercept specific commands known to trigger EDR under certain circumstances. While MDSec has addressed these opsec concerns since our initial identification of them as potential issues, one example of an opsec check that could be made has been left in the code.

DayBird logic inspecting inbound commands to allow for conditional execution of code

If the plugin command is passed in by the operator, execution is transferred to the PluginManager class, where secondary actions are taken based on the command state and key pressed (e.g., tab complete, running a plugin, displaying a help message, etc.). Plugin execution is facilitated through the usage of a shared DLL containing a base plugin object pattern and methods to send commands to the associated beacon via the UI. This shared library is located within the NHAPI DLL, source code for which is included in the DayBird repo. The shared base abstract class is light on implementation requirements, with the goal of making plugin development less daunting.

Defined properties and methods in the abstract PluginBase class

An instantiated Console object is passed to the plugin on initialization to ensure that execution occurs in the correct beacon context. This object is subsequently referenced by the wrapper methods which handle execution of specific NightHawk commands in the context of a plugin. An example of the NHAPI wrapper method that executes the WhoAmI command on a beacon can be seen below:

NHAPI wrapper method to execute WhoAmI in a NightHawk beacon

Things get a bit trickier when attempting to retrieve output from running a command. At a high level, a unique GUID is assigned to each new task registered in NightHawk, which is then appended to a List<Guid> object in the operator’s UI. When a command returns results, the UI checks its list of waiting GUIDs to determine if it needs to retrieve or process data. If there is a match, it will parse the result and write it to the console, typically causing the output to sync to any other operator consoles. As a brief aside, this is also why if you are waiting for a command to come back and close your UI you never receive output — although the command may have sent data back, there is no longer an operator UI with that specific task’s GUID referenced anywhere, meaning it never gets collected and written to the console.

In DayBird, command output retrieval functions by retrieving the GUID for a passed-in command from the UI’s list of commands currently waiting to receive output. This GUID is then added to a secondary dataset managed by DayBird, which is queried by the custom event handlers we registered on initial startup to determine if the received output should be processed by a plugin. Note that this GUID is intentionally not deleted from the UI’s list of GUIDs awaiting results so that output will automatically be written to the screen (and thus logged) for all operations. Due to this, a plugin author doesn’t need to manually output everything to console and doesn’t need to remember to always implement full syncing for all commands as they will be recorded in the same manner as any manually entered commands would be.

Command event handlers currently defined in DayBird

When one of these custom event handlers is hit successfully, output is parsed using the UI’s native output parser for the command and sent to a <Guid, String> Dictionary that contains retrieved output waiting to be collected by a plugin. An example of a custom event handler for retrieving output from the WhoAmI command can be seen below:

WhoAmI event handler code

To retrieve output from an executed command, plugins use the GetJobOutput method, which loops for a plugin author-defined period while checking for results related to the specific task id GUID of the passed-in command, after which it will return an empty string if no results have been collected. The plugin author can determine what level of saving and synchronization they want their custom output to have, as well as the text output color. A plugin calling WhoAmI and waiting for the result before printing it to the operator’s console can be seen below.

Plugin code to run WhoAmI in the calling beacon, retrieve the results, and write them to console

AutoRun functionality

The other main gap in NightHawk that DayBird addresses revolves around automating actions when a new beacon checks in for the first time. This is typically of most interest to our team during initial phishing on an assessment, where an initial access mechanism may necessitate a quick move to another process, or we may want to immediately set up persistence to secure the ingress into the remote environment. Autorun functionality can be accessed in DayBird via the AutoRuns button that is appended to the UI’s top ribbon and allows for configuration of plugins in the Autoruns subdirectory off of the primary UI execution directory. Any plugin can be run as an autorun as long as it is placed in the appropriate directory, and no special steps are required on the part of the developer to build a plugin to be designed to run in this manner.

AutoRuns menu options

Clicking on Manage AutoRuns will open a secondary menu allowing you to enable and set execution priority for plugins. All enabled plugins will run on all newly registered beacons and will run in the order specified through the operator-defined priority.

AutoRuns management window

Any plugins built with required arguments specified will then prompt the operator for configuration of each required argument prior to completing the setup.

Configuring required variables for an AutoRun plugin

After configuring plugins, you can further validate their current configuration via the View Enabled option under the AutoRuns menu.

Viewing enabled AutoRun plugins

When a new beacon connects in after configuring an autorun to execute, a new console window will automatically be generated to instantiate the necessary objects required for execution. From this new window execution should kick off automatically, as can be seen below:

The only downside of this is that it is a UI extension, meaning that unlike an aggressor script that can be run server-side, this automation does require your operator UI to stay up and your computer to not go to sleep.

Building DayBird

Most importantly, downloading the repo and attempting to build it as-is will not work as DayBird needs a reference to the NightHawk operator UI (UI.exe) to compile. We haven’t included that assembly in this public release, so if you would like to build this project you’ll need to drop a copy of it into the primary project folder (DayBird\DayBird — the folder containing DayBird.csproj and other .cs files) or manually rebuild the path to the reference on your side.

Everything in DayBird is built using .NET Framework 4.8, as this is the version the NightHawk UI is built in. NHAPI is automatically loaded into the UI.exe AppDomain when the process is started, so there is no need to use Costura to merge the NHAPI DLL in your plugin or anything like that, the exported functions and types will already be available when running.

Additionally, everything is currently being built as x64 as this is what the agent was built in. Building the project and plugins as x64 is recommended to avoid any issues, but Any CPU should also work.

Finally, you may get some build errors when you first download and attempt to build projects in the DayBird solution saying that the NHAPI.dll reference cannot be found. If you run into this, doing a Build -> Clean and then retrying the build typically resolves this issue. This is due to the other projects in the solution relying on NHAPI, which is built first but may not exist at the beginning of the build process.

Creating and building plugins

Plugins are small C# programs compiled as DLLs which are derived from the PluginBase class in the NHAPI.dll library. This gives them a basic structure so that they can be accessed programmatically in DayBird and allows for all necessary objects to be passed into them. From the developer’s perspective, this just means that when creating a new plugin ensure you’re referencing NHAPI.dll and that you either use one of the example plugins as a template base or let VS auto-populate the correct structure for your Plugin class so that it correctly derives from PluginBase.

To use NightHawk functions in your plugin, utilize the nhFunctions object, which should be directly accessible once you’ve set up the NHAPI reference. This object provides wrappers that facilitate interaction with various NightHawk functions implemented within the NHAPI library so far. While not every command has been implemented, an effort has been made to incorporate the majority of the commands most commonly used by our team in the course of operations.

The majority of functions have two supported mechanisms — “blind” execution, and execution with output retrieval. Blind execution functions as a fire-and-forget; essentially you pack the arguments up, ship the command back to NightHawk, and then continue execution without much care for the output of what you just sent. This type of functionality works best for things like BOF wrappers where the plugin is primarily functioning as a front door to make command line parsing easier.

On the other hand, execution with output retrieval allows for the plugin to send a command to NightHawk and then subsequently retrieve output associated with that command back as a string value that can be used by logic within your plugin to make further decisions. For example, a plugin could make a decision on what persistence mechanism to deploy based on the results of the ps command (showing which EDRs may be active on the system) or based on the integrity of the current session. These commands offer more execution options and are better suited for larger plugins whose focus is on automation of operations. For a better and more practical example of how some of these functions work, source code for two demo plugins has been included within the DayBird repo. A table outlining currently implemented NightHawk functions can be seen below:

NightHawk Command Execution Output Retrieval
cd yes yes
download yes yes
execute-bof yes yes
impersonate yes yes
inject-shellcode yes yes
inproc-execute-assembly yes yes
link yes yes
ls yes yes
mkdir yes yes
mv yes yes
ps yes yes
pwd yes yes
revert yes no
rmdir yes yes
rmdir yes yes
sleep yes no
spawn-shellcode yes yes
upload yes yes
whoami yes yes
Scroll to view full table

In addition to these, there are several other NightHawk-specific methods available, including:

  • PrintToConsole: Functions like Console.WriteLine – prints output to the NH Agent console. Has options that allow for configuration of output color, and if you want the output to be saved to history + synced across operator consoles.
  • BofPack: Convert a list of BofArg objects into a string formatted for sending to NH’s execute-bof command.
    • BofArg objects are a simple datatype consisting of a string + an enum that types the arg into one of the pre-existing expected bof arg types (e.g., int, string, etc.) Check out TestPlugin2 for an example of usage.
  • DetailedMachineInfo (object): This object is passed into all plugins and contains info on the environment you are operating in (system name, IP, username, pid, etc.)

For commands that require loading a third-party file (e.g., you call the execute-bof command, which will attempt to load a .o file), drop additional required files in the same folder as your plugin (Plugins/AutoRuns folder).

This should hopefully give a decent baseline to get you going on development, if people are interested in using and have problems feel free to submit issues on the repository.

More from Adversary Services

Getting “in tune” with an enterprise: Detecting Intune lateral movement

13 min read - Organizations continue to implement cloud-based services, a shift that has led to the wider adoption of hybrid identity environments that connect on-premises Active Directory with Microsoft Entra ID (formerly Azure AD). To manage devices in these hybrid identity environments, Microsoft Intune (Intune) has emerged as one of the most popular device management solutions. Since this trusted enterprise platform can easily be integrated with on-premises Active Directory devices and services, it is a prime target for attackers to abuse for conducting…

Racing Round and Round: The Little Bug That Could

13 min read - The little bug that could: CVE-2024-30089 is a subtle kernel vulnerability I used to exploit a fully updated Windows 11 machine (with all Virtualization Based Security and hardware security mitigations enabled) and scored my first win at Pwn2Own this year. In this article, I outline my straightforward approach to bug hunting: picking a starting point and intuitively following a path until something catches my attention. This bug is interesting because it can be reliably triggered due to a logic error.…

Q&A with Valentina Palmiotti, aka chompie

4 min read - The Pwn2Own computer hacking contest has been around since 2007, and during that time, there has never been a female to score a full win — until now.This milestone was reached at Pwn2Own 2024 in Vancouver, where two women, Valentina Palmiotti and Emma Kirkpatrick, each secured full wins by exploiting kernel vulnerabilities in Microsoft Windows 11. Prior to this year, only Amy Burnett and Alisa Esage had competed in the contest's 17-year history, with Esage achieving a partial win in…

Topic updates

Get email updates and stay ahead of the latest threats to the security landscape, thought leadership and research.
Subscribe today