The flow of infection starts with a PowerShell dropper. The purpose of this component is to stage the subsequent element in the chain by installing it as a service. Before doing so, it creates a couple of registry keys that it assigns encrypted data to, one of which corresponds to a payload that will be deployed in the later stages.
It’s worth noting that the script itself is delivered in a packed form, whereby its complete execution is dependent on a command-line argument that is used as a key to decrypt the bulk of its logic and data. Without this key, it’s impossible to recover the flow that comes after this stage.
The next stage, which is executed as a service by the former, is intended to serve as yet another precursor for the next phases. It is used to read the encrypted data from the previously written registry keys and decrypt it to initiate the execution of an in-memory implant.
Analysts identified two variants of this component, one developed in C++ and another in .NET.
The latter, uses the GUID of the infected machine to derive the decryption key, and is thus tailored to be executed on that specific system. The C++ variant, on the other hand, relies on hardcoded AES 256 encryption keys.
The third stage is the core implant that operates in memory after being deployed by the aforementioned loader, and is injected into the address space of a newly created svchost.exe process.
Its main goal is to facilitate a communication channel with a C2 server, whereby malicious traffic is masqueraded under the guise of communication with a benign service, based on a Malleable C2 profile embedded within its configuration.
It is important to note that the implementation of the Malleable C2 feature, which is originally provided in the Cobalt Strike framework, is customized and most likely rewritten based on reverse engineering of Cobalt Strike’s code.
On modern 64-bit Windows operating systems, it is generally not possible to load an unsigned driver in a documented way due to the Driver Signature Enforcement mechanism introduced by Microsoft.
For this reason, attackers have abused vulnerabilities in signed drivers to allow execution of unsigned code to kernel space. A typical approach1 taken by many actors to date, and mostly in older versions of Windows, is to disable the Code Integrity mechanism by switching the nt!g_CiEnabled flag that resides within the CI.DLL kernel module after getting write and execution primitives via vulnerable signed drivers. After shutting down the Code Integrity mechanism, an unsigned driver can be loaded.
The approach used by the developer of this rootkit allows loading an unsigned driver without modifying the Code Integrity image and dealing with a potential crash. It abuses features of a legitimate and open-source2 signed driver named dbk64.sys which is shipped along with Cheat Engine, an application created to bypass video game protections and introduce cheats into them.
This driver provides capability to write and execute code in kernel space by design, thus allowing it to run arbitrary code in kernel mode.
After dropping the dbk64.sys driver with a randomly generated filename to disk and loading it, the malware issues documented3 IOCTLs to the driver that allow shellcode to be run in kernel space.
The loaded rootkit, which was dubbed Demodex, serves the purpose of hiding several artefacts of the malware’s service.
To access the rootkit’s functionality, the malware ought to obtain a handle to the corresponding device object, after which the following IOCTLs are available for further use:
– 0x220204: Receives an argument with the PID of the svchost.exe process which runs the code of the malicious service and stores it within a global variable. This variable is used by other IOCTLs later on.
– 0x220224: Initializes global variables that are later used to hold data such as the aforementioned svchost.exe PID, the name of the malware’s service, the path to the malware’s DLL and a network port.
– 0x220300: Hides the malware’s service from a list within the services.exe process address space. The service’s name is passed as an argument to the IOCTL, in turn being sought in a system-maintained linked list. The corresponding entry is being unlinked, thus hiding the service from being easily detected. The logic in this handler is reminiscent of the technique outlined here.
– 0x220304: This IOCTL is used to register a file system filter driver’s notification routine by using the IoRegisterFSRegistrationChange API. The notification routine invoked upon registration of a new file system verifies if it is an NTFS-based one and if so, creates a device object for the rootkit which is attached to the subject file system’s device stack. Additionally, both the file system’s device object and the associated rootkit device object are registered in a global list maintained by the rootkit’s driver. Subsequent attempts to retrieve information from, access or modify the file will fail and generate error codes such as STATUS_NO_MORE_FILES or STATUS_NO_SUCH_FILE.
– 0x220308: Hides TCP connections that make use of ports within a given range from utilities that list them, such as netstat. This is done through a known4 method whereby the IOCTL dispatch routine of the NSI proxy driver is hooked and the completion routine is set to one that inspects the port of a given connection. If the underlying connection’s port falls within the given range, its entry is removed from the system’s TCP table. The two ports that constitute the range are passed as arguments to the IOCTL.
– 0x22030C: Hides malware-related registry keys by hooking several registry operations through the CmRegisterCallback API.
The authors of the malware components used in the GhostEmperor cluster of activity have made some development choices that have implications on the forensic analysis process.