Praetorian has developed a custom YAML-based domain-specific language (DSL) to allow operators to specify red team dropper behavior. The YAML-based DSL works with a custom compiler we’ve named Janus, which handles converting the YAML representation to a fully-functioning dropper. The dropper generator’s purpose is to provide operators more control over the runtime behavior of payloads at execution time. For example, a red team operator may wish to swap out the code injection mechanism used to dynamically increase or decrease the sophistication of the payload depending on the target environment’s maturity level.
The motivation for Janus stems from the GRASSHOPPER payload generator described within the Vault 7 leaks , which detailed a modular payload generator that provided operators with extensive control over the resulting payload generated by the tool. The GRASSHOPPER tool detailed within these documents allowed pre-execution checks to be evaluated before performing a malicious action.Additionally, the tool supported swapping out tactics, techniques, and procedures (TTPs)  that could trigger detection by a security product on a host (e.g. persistence).
The payload generation process is performed to generate a payload consisting of several components. For example, this could be a completed macro-enabled document containing an embedded dropper responsible for loading an implant. Typically when we think of and describe a payload, we like to break it down into three distinct parts detailed below:
- Artifact: The artifact is the initial item downloaded to the disk responsible for launching a dropper. In the previous example, the macro-enabled document would be the initial artifact.
- Dropper: The dropper’s primary responsibility is handling pre-execution checks and subsequently decoding and executing a payload.
- Implant: The implant is the final stage of the payload execution process. It represents the bridge between the target system and the attacker’s infrastructure, providing the ability to perform actions on the target host, exfiltrate data, and perform other malicious actions. An important thing to note here is that the dropper and implant are loosely coupled. The dropper must be able to load any implant that conforms to the appropriate specification.
An analogy in this scenario might be similar to that of an operating system’s boot process. The artifact is analogous to the system bootloader. Its purpose is to perform the minimum steps required to load the operating system kernel (analogously, the dropper). Finally, the operating system kernel is responsible for performing the appropriate pre-boot initialization checks and startup tasks before launching the system initialization process (similar to the implant).
We’ve found that logically decomposing the payload generation process into three separate components provides us with a strong mental model for building flexible modular dropper and artifact generators. In some instances, the dropper and artifact are the same. For instance, we might perform lateral movement by dropping a DLL to disk and leveraging DLL hijacking to execute code on a remote host. In this case, the dropper and the artifact would be the same file. In another scenario, we might utilize a macro-enabled document to execute MSBuild.exe. In this case, the dropper would be the MSBuild XML file, and the artifact would be the macro-enabled document. Internally, we decompose the dropper, implants, and artifacts to further sub-components to support additional customization.
Janus is an internal tool we have developed to generate DotNet droppers through a custom YAML-based domain-specific language. The Janus tool handles taking in a user-specified YAML file and set of modules or plugins as input to generate a final payload consisting of the requisite modules merged with code inserted to perform the user-specified logic.
In its current state, Janus only supports generating DotNet payloads. The reasoning behind this is that the DotNet intermediate language (IL) provides an extensive amount of metadata and other information to manipulate and merge assemblies. This is by design, as the intermediate language must include enough detail to allow the DotNet runtime to convert the IL to native code. Furthermore, DotNet provides a comparatively powerful degree of control over the generated output file types, including support for mixed-mode assemblies used to generate DLL exports that can be called from native code. Additionally, an extensive set of third-party libraries exist to support the modification of DotNet assemblies such as Mono.Cecil, allowing for rapid development and prototyping.
In its current state, improvements to DotNet runtime instrumentation and the increasing focus on DotNet by defenders have not meaningfully impacted our ability to conduct operations successfully. However, in the future, we plan to diversify our payload generation tooling to include native code generation capabilities in anticipation of further improvements of defensive capabilities. This topic is further discussed under “Future Work”.
The Janus compiler features an extensible modular architecture allowing operators to drop in a new module to, for instance, add support for a new type of code injection method within the dropper generator. Operators with the plugin installed can then reference that plugin within their YAML markdown file (known as the Janus Descriptor Language file or JDL file).
The Janus compilation process is broken up into distinct steps including lexical analysis, parsing, semantic analysis, dependency resolution, assembly merging, code generation, and obfuscation. These steps are described in-depth below:
- The module loader leverages reflection to recursively load from the modules directory. These modules are used later during semantic analysis and linking to determine which modules are required for generation of the payload. This allows for only the minimum number of modules to be included in the generated assembly. This is critically important to avoid unnecessarily exposing capabilities.
- After the module loader completes execution the lexical analysis and parsing stage are completed, producing a parsed representation of the JDL file.
- The parsed representation of the JDL file is analyzed during the semantic analysis phase to build a list of required modules for the payload to function.
- The semantic analysis stage then verifies the meaning of the program is correct and that the program doesn’t represent any non-existent modules.
- After semantic analysis, all the modules are merged using the ILRepack library to combine all the dependencies into a single assembly.
- Glue code is then inserted into the generated assembly to implement the program logic and handle invocation of modules in the appropriate order. This is primarily done using the Mono.Cecil library.
- The generated assembly is then optionally obfuscated using an open-source DotNet obfuscator, such as ConfuserEx, to encrypt strings and rename method, variable, and class names.
- The final assembly is written to disk with optional target-blending modifications made to mimic a legitimate binary on the system.
Janus Module Types
Janus supports a wide variety of module types to support flexible customization of the dropper. These module types are described below:
- Output Format Module: An output format is used to specify the output format of an assembly; for instance, an assembly may need to export certain functions through its export address table to work with a specific lateral movement or LOLBAS technique . Each module implements the required attributes for the chosen output format using metadata embedded within the assembly to specify how it can be linked to other modules.
- Process-Injection Module: Process-Injection modules accept a bytearray of shellcode and are responsible for injecting that shellcode payload into a foreign process using a chosen code injection technique. Each process injection module supports a distinct code injection technique.
- Self-Injection Module: A self-injection module is exactly like a Process-Injection Module except for it injects into the currently running process. This is a useful fallback mechanism in certain scenarios where the endpoint security solution on the host very aggressively blocks remote process injection.
- Input/Output Transforms: The input/output transforms represent a pair of invertible functions for performing modifications on a file. The purpose of these is to modify a file embedded in an assembly, for instance shellcode injected into a remote process. Here, an input plaintext byte array is transformed into ciphertext, then subsequently transformed back from ciphertext to plaintext on target using the output transform. Users can create their own invertible input and output transform plugins.
Example Janus Dropper
In this section, we walk through an example Janus Descriptor Language (JDL) file used by an operator during a red team operation. The JDL file consists of several subsections, including the attributes, files, entry, metadata, and rules sections. A summary of these sections is given below:
- Attributes: The attributes section is used to specify the output format and architecture of the generated assembly (this is discussed in more detail in future sections).
- Files: The files option is used to specify files embedded within the generated dropper. For instance, an operator may wish to embed shellcode responsible for loading a second-stage implant within the generated dropper.
- Entry: The entry section represents the main function of the program. Here, the user specifies the dropper’s runtime behavior (e.g., if and how it should inject code into a foreign process).
- Metadata: This section allows the operator to control the metadata embedded within the generated dropper.
- Rules: This section allows the operator to specify a set of pre-execution checks which should be performed to determine if the dropper should continue execution. For example, the dropper may wish to perform checks for a sandbox before loading the implant on the host.
To start, we begin by examining the attributes section of an example JDL file, as shown in the snippet given below. The primary two items within this section are the format and architecture attributes. The format attribute is used to specify the required output format of the module. In this example, we are using the comikaze module, an output format that implements compatibility with LOLBINS such as RegAsm.exe and RegSvr32.exe, which expect a valid COM object to successfully execute the payload.
attributes: format: comikaze architecture: x86_64 obfuscate: true # (optional attribute)
The architecture attribute specifies the required architecture of the generated assembly. While it is true that DotNet assemblies are compiled to an intermediate language, in a similar way to Java, DotNet supports the concept of mixed-mode assemblies, which can be used to embed native code alongside a generated assembly. This allows DotNet to support exporting functions through the export address table which can be invoked by native code. In Janus, we use the dnlib library to implement function exports through the generation of mixed-mode assemblies. In this JDL example, we are specifying the x86_64 value meaning that the generated DLL for the COM object should be 64-bit. This is required because the COM object must support exporting the DllRegisterServer and DllUnregisterServer functions to support being loaded as a COM object by native utilities.
The files section defines all files referenced by the dropper that need to be bundled within the generated dropper payload. This is where the operator would specify how an embedded implant loaded by the dropper should be stored, and what input transforms should be performed against the file during the embedding process.
files: - file: name: implant.bin storage: array transforms: - AES - EnvKey - KeyServer - AES transform_parameters: - url:https://frontabledomain.com/jquery.js - host: somecdn.com - domain_sid: S-1-5-21-012345678-0123456789-0123456789
In this example, we are specifying a file containing shellcode called implant.bin with a storage method of “array”. This means that the file will be embedded as a byte array within the generated dropper. A series of transforms are specified by the user on how the file should be encrypted. First, the data is encrypted with a randomly generated AES key embedded within the dropper. Next, the payload is encrypted using the domain SID of the Active Directory domain associated with the target environment, a key retrieved from a remote keyserver via domain fronting, and then finally encrypted with a final random AES key. In future posts, we plan to discuss how we leverage environmental keying along with our keyserver during red team operations.
The transform parameters are used to pass information to the input and output transforms. For instance, in the snippet below, we specify that the keyserver module should perform domain fronting to retrieve the key and specify the domain SID the key should be encrypted with. The domain SID string is not hardcoded into the binary but derived from the environment during runtime.
The entry section specifies the entrypoint function of the JDL file. In this case, we are using a single-statement which specifies that the code embedded within the dropper should be injected into the calc.exe process with a spoofed parent process of explorer.exe along with the BlockDlls mitigation enabled on Windows 10 systems. At compile time, the compiler inserts code that invokes the output transforms in the inverse order of the input transforms (this is done because the input and output transforms represent a pair of invertible functions). The code is then passed to the injection module which invokes the CreateRemoteThread module which is responsible for handling the code injection.
entry: - injector: Method: CreateRemoteThread Process: SpawnProc: "%SYSTEMROOT%System32calc.exe" ParentProc: explorer BlockDlls: true File: implant.bin
The metadata section is responsible for modifying the attributes embedded within the resource section VERSIONINFO of the generated assembly.
metadata: - FileDescription: Distributed COM Services - InternalName: rpcss.dll - OriginalFilename: rpcss.dll
The rules section allows us to specify runtime checks which can be performed because the entrypoint function is executed. In this case, we configure the dropper to check the number of processors, ensure that Wireshark is not running, and that the current user is not “administrator”.
rules: - processor: - at_least: 2 - process: - does_not_exist: Wireshark - username: - does_not_match: administrator
Future work will be focused on adding support for native code generation in a similar manner to what is currently implemented within DotNet code. Unfortunately, native code generation is a bit more complicated as compiled object files typically lack much of the metadata included in DotNet assemblies. Additionally, there is a dearth of tooling available for performing such in-depth modification of compiled object (COFF) files. While we believe that developing such a capability is possible, the time requirement would be significant due to the development overhead of building out the supporting tooling (which already exists for DotNet).
We are also developing enhanced capabilities for dropper telemetry, runtime error handling, and recovery we plan to cover in more depth in future articles. For instance, we are implementing broker processes that monitor the payload during execution to detect if the injection process was terminated by security software. Additionally, we are working to develop an emergency response capability into our droppers which provides a rudimentary shell to remotely debug a failed implant along with a “crash reporter”, to collect system information for diagnostics and debugging purposes. This functionality will increase the probability of success within future droppers when an execution unexpectedly fails or even launch an alternative dropper via an emergency shell.
Janus provides a flexible system for generating dropper payloads that allow operators to exercise a high degree of control over the payload’s behavior. It allows operators to modify the behavior of a dropper without performing extensive code modifications or even opening Visual Studio. This allows operators to create highly sophisticated droppers in minutes as opposed to hours or days. Furthermore, operators specializing in network operations who lack the requisite development skills can easily create highly sophisticated payloads independent of the capabilities development team. The modular architecture makes it easy for developers to quickly add modules to, for example, support a new code injection technique while eliminating redundancy from the codebase.
We are constantly working on improving and tweaking red team payloads to keep pace with the defensive security landscape. As new technologies are developed or encountered during operations we are continually upgrading payloads to keep pace with modern security technologies.