In the first part of the DUALITY series, we introduced the idea of DUALITY and its pipeline to backdoor DLLs for persistence. The tradecraft associated with the backdooring process and process injection, with regard to modern EDR capabilities, was relatively primitive. An easy detection opportunity for AV / EDR companies from the first blog post would be to signature the “.duality” section added to backdoored DLLs. In this blog post, we will discuss improvements to tradecraft for DUALITY.
We will also discuss how the same pipeline can be utilized for initial access by introducing DUALITY’s web-based stager, which contains several useful features such as low-privilege DLL enumeration to give the operator the remote option of which DLL to backdoor, as well as the ability to handle multiple, simultaneous payload detonations in order to handle the backdooring of dependencies from multiple hosts.
Note that we have intentionally released a more neutralized version of DUALITY in Part I. We unfortunately do not intend on releasing a completely weaponized version of DUALITY primarily because we want to limit the potential damage it might cause and secondarily due to the amount of research time it involved.
Tradecraft Improvements
When it comes to tradecraft versus EDR, there are two primary categories we will consider for this iteration:
- Signature-based detections: Which parts of the DLL that DUALITY drops to disk for persistence or initial access can be signatured?
- Behavioral-based detections: During DUALITY’s execution, which aspects of DUALITY does EDR have visibility into?
We’ve intentionally left out reputation-based detections as competent stager / beacon communications setup is up to the reader.
Signature-Based Detections
We previously presented a few ideas for detecting the first iteration of DUALITY. One key issue was that section names added to backdoored DLLs are hardcoded. Section names such as “.duality” should be a red flag. To resolve this, any added sections are now randomly named during the shellcode compilation and packing process.
This is depicted in the following screenshot, where the C# pipeline program replaces template variables in the shellcode-C (SCC) file prior to compilation for each DLL.
Replacing Template Variables in SCC File
Behavioral-Based Detections
The first iteration of DUALITY performed process injection by resolving function pointers from the current process’s modules / functions, which are hooked by AV / EDR. To resolve this, we’ve introduced functionality in the initial access stager that will send the host’s “ntdll.dll” file to the backdooring backend, along with the DLLs we are interested in backdooring. This will allow us to pack “ntdll.dll” as a section to the backdoored DLL. In our shellcode, we will then dynamically resolve “clean” functions from this clean packed “ntdll.dll”, such as “NtAllocateVirtualMemory” or other “Nt” functions that malware may utilize.
We go a step further beyond this by combining the assembly from the packed “ntdll.dll” section with the syscall instruction from the “ntdll.dll” in the current process’s memory space, in order to perform an “indirect syscall”. This is technically overkill for evading EDR hooks, as we are not operating from a random heap location (such as the case for an implant operating from memory) when we execute these “Nt” calls. When the stack trace is examined after a syscall is performed, if we are operating from a disk-backed image of a PE, the syscall instruction will be coming from a valid, executable section in memory that reflects a section on disk. This is how DUALITY operates. However, if this syscall instruction originated from a random heap address that does not reflect some valid section on disk (such as when an implant is operating from memory), that would likely be a high-fidelity indicator that something malicious is executing.
The following function from the SCC template resolves a “clean ntdll.dll” function from the packed section passed in as a “buffer” address. The passed in “ntdllHandle” variable contains a handle to the in-process hooked “ntdll.dll”, which we can use for an indirect syscall.
Resolving Functions from Packed ntdll.dll
Later in the code we can observe how the indirect syscall is constructed:
Construction of Indirect Syscall
The following depicts the function “create_jmp_opcode”:
Creating "jmp" Opcode for Indirect Syscall
Note that the usage of common functions such as “memcpy” are prefixed with “_” in the case of “_memcpy”, because we assume no access to the C runtime during shellcode execution; therefore, we use our own version defined in the Position Independent Code (PIC) so we can support our shellcode. Remember, all of this C code gets compiled into PIC and operates from our “.duality” section (which is going to be a randomly named section during compilation).
DUALITY for Initial Access
The same pipeline utilized for persistence can be re-purposed to be used for initial access. The “persistence” pipeline currently performs the following:
- Receive DLLs to backdoor from persistence script
- Compile pre-written shellcode-C file (SCC file)
- Inject / Backdoor DLLs with compiled shellcode
For this blog post, we are only sharing how our implementation of the initial access web stager works. Note that the pipeline for persistence as well as the persistence script are available publicly.
Auth Headers + C2 Agnostic
The first feature of the DUALITY operator dashboard is that authorization keys are required in order for the stager to reach out. These keys can be generated and revoked at will. This allows an operator to detonate the stager, stage in and implant, and then render the stager non-operational.
Auth Keys Utilized
For the second feature, note the “SC Prefix” text box. This allows the stager to be associated with any previously uploaded shellcode (from any C2) to be utilized as part of the initial access pipeline. A PowerShell one-liner and web friendly one-liner are present for public proof-of-concept and for testing against systems with basic AV / EDR. We recommend creating a stealthier stage-0 one-liner to load in the DUALITY stager, which will perform the actual backdooring.
Logging
Since the backdooring process takes up to 5 minutes sometimes due to enumeration of low-privilege DLLs, uploading / downloading files, and the backdooring process in the backend itself, it’s useful to the operator to see which step in the backdooring process the stager is in. This is rendered to the operator from the DUALITY dashboard.
Logging for Backdooring Process
Payload Detonation Information + Staging Rejection
When a callback comes in, the operator can examine important information such as the username, user domain, and external IP address registrant organization. This can greatly inform the payload detonation information of the stager and help the operator reject payload detonations if they occur within sandbox environments. If sandbox execution takes place, it is crucial for operators to have the ability to prevent payloads from being fully staged. The prevention can result in truly malicious payloads being marked as benign, buying more time for the operation. If the stager detonation context appears satisfactory however, the operator may click the red callback ID link.
Stager Detonation Information
Simultaneous Detonation
The operator is also able to handle simultaneous detonation in cases where multiple callbacks are received. In this way, the backdooring process can take place for multiple hosts in parallel.
Simultaneous Detonation
Backdoor Choice
When the operator clicks a callback ID (red link), they are taken to the backdooring selection screen where they can choose to backdoor a DLL from the enumerated DLLs. Remember that the stager had enumerated low-privilege locations for DLLs and associated EXEs prior to calling back with the options available. The operator also has the option to specify an executable associated with the DLL. Usually, this should be the executable that immediately loads in the DLL after spawning. For example, Microsoft Teams loads in “ffmpeg.dll” after spawning.
In any case, DUALITY is written to allow execution continuity, so the user is not affected by DUALITY logic. If no executable is specified, the DUALITY stager will backdoor the selected DLL but will not force-restart the associated process. For example, if we target “ffmpeg.dll”, DUALITY will send this DLL to the backend, backdoor it, and then swap it out with the original. This way, when Microsoft Teams naturally closes and restarts (such as after system reboot), the backdoored DLL is loaded into Microsoft Teams and the DUALITY logic runs.
If an executable is specified, the DUALITY stager will backdoor the DLL, and then terminate all processes with that executable’s name. The stager will then restart the executable. This allows for obtaining a shell immediately, rather than waiting for natural program usage.
Sometimes, no desirable program to be backdoored is available. In this case, the operator can specify an index of “-1”, which will utilize a generic backdoored DLL and load the DUALITY logic using a short-lived “rundll32” process. One “feature” of rundll32 is that even if you specify a non-existent entry point, it will do you the favor of running “DLLMain” anyway, which will run your DUALITY logic, and then inform you that the entry point does not exist. This works well in cases where no desirable DLLs to backdoor are present. This is assuming you backdoor “DLLMain”, which is what the public proof-of-concept does.
Backdooring Choice
Future Plans
Some items in this iteration of DUALITY address the previous version’s “Future Plans” section. There are other items left in the public release version to improve tradecraft. Hopefully, this serves as pointers to where AV / EDR detections may exist.
-
Sign DLLs: It would be relatively trivial to add a code signing step in the backdooring pipeline.
- Encrypt “NTDLL” section: The presence of an unencrypted “ntdll.dll” as a section is an easy / trivial detection opportunity. The way around this is to encrypt the section while at rest and decrypt it competently during execution. If you’re an AV / EDR company, start here to detect this iteration of DUALITY’s public proof-of-concept.