Earlier in this year, I published research of the rootkit that belong to famous state-sponsored cybergroup called "Equation Group". Analyzed rootkit actually represents one of the Windows kernel mode network implant, which has been used by cybergroup as a network traffic sniffer on NDIS level. As we know from comprehensive research of Kaspersky Lab, Equation Group has an arsenal of various sophisticated malware, such as EQUATIONDRUG, DOUBLEFANTASY, TRIPLEFANTASY, GRAYFISH, etc. This arsenal of malware is pretty complex and sufficient for performing cyberattacks that allegedly have state-sponsored origins (NSA). As we know, not only Equation Group leverages kernel mode rootkits, for example, Sednit (APT28, Fancy Bear, Pawn Storm) and Strider cybergroups also resort to the same.
Unlike other malware families of Equation Group, GRAYFISH has on board Windows kernel rootkit for performing malicious operations in high privileged Ring 0 mode. Moreover, like other state-sponsored rootkits that were mentioned previously in my blog, GrayFish (or Trojan.Win32.GrayFish.b as Kaspersky called it) is pretty interesting for the analysis. As we can see from previous instances of such malware, their authors can choose non standard ways for rootkit development.
Typically, malware from cyberespionage platforms leverage kernel mode rootkits to achieve following advantages.
Despite DLL stores the rootkit file, it doesn't load him into memory. I suppose that driver loader is located in another GrayFish module. However, code inside the library communicates with driver via DeviceIoControl API. The rootkit supports various IOCTL codes for performing necessary operations.
The rootkit has the following properties.
On first step of initialization, the rootkit builds its own buffer of NT kernel exports and its hashes in following format: +0 FuncHash; +4 FuncAddr. It contains internal function for extracting specific item (function address) from this table by hash of function (NT export) name.
The rootkit also retrieves pointers to dynamic imports in the following way. It uses functions hal!HalAllocateCommonBuffer or nt!MmIsAddressValid for retrieving start address of hal.dll (HAL) or ntoskrnl (NT). Name of both functions are encrypted into its body and are decrypted on stack.
Next imports the rootkit tries to get during initialization phase (hash -> API).
As we know, Equation Group is known as professional in cryptography, their malware from cyberespionage platform leverage various crypto methods and algorithms that we haven't seen before in such scale. Looks like their love of encryption covers and kernel mode component too. The rootkit stores sensitive strings in encrypted state and doesn't decrypt it inside loaded in memory image, instead it copies necessary data on stack and decrypts it. Before exiting the function, it encrypts string on stack again.
After got address of HalAllocateCommonBuffer (or MmIsAddressValid), the rootkit tries to find start address of hal.dll for further retrieving addresses of dynamic imports. Interesting to see that pointers to HAL or NT functions the rootkit stores in memory in encrypted state, as shown above (it XORed value of each ptr with its offset). Same situation with NT imports, all stored into rootkit image as encrypted.
The rootkit uses some sort of code and data obfuscation, although stores imports of kernel API in the clear state (PE IAT). For masking its activity from researchers eyes, the rootkit creates new driver object with name \Driver\msvss, so after exiting from DriverEntry, original Windows kernel DriverObject will contain no information about created DeviceObject of rootkit as well as about its IRP_MJ handlers.
This method of hiding real DriverObject was used by Remsec rootkit authors. My part 2 of Remsec driver research contains information about this technique. The rootkit calls ObCreateObject for creating new DriverObject, ObInsertObject to create handle and ObMakeTemporaryObject for removing name of the object from its parent object manager directory \Driver\ (hiding from researchers eyes).
Unlike other malware families of Equation Group, GRAYFISH has on board Windows kernel rootkit for performing malicious operations in high privileged Ring 0 mode. Moreover, like other state-sponsored rootkits that were mentioned previously in my blog, GrayFish (or Trojan.Win32.GrayFish.b as Kaspersky called it) is pretty interesting for the analysis. As we can see from previous instances of such malware, their authors can choose non standard ways for rootkit development.
Typically, malware from cyberespionage platforms leverage kernel mode rootkits to achieve following advantages.
- Independent from user mode context method of files hiding, for example, with help of legacy FS filter (Stuxnet).
- Universal method of code injection based on Windows kernel callbacks (Stuxnet, EquationDrug). Also code injection into trusted Windows processes for masking malicious activity (Finfisher, EquationDrug).
- Execution of necessary user mode code directly in kernel mode (Remsec).
- To bypass Windows x64 kernel mode restrictions (DSE) with bootkit component (Sednit, GrayFish).
- To listen network traffic directly from physical network adapter (EquationDrug).
- To bypass FS/disk level sandboxes as well as other restrictions into a system with security products (Wingbird).
Despite DLL stores the rootkit file, it doesn't load him into memory. I suppose that driver loader is located in another GrayFish module. However, code inside the library communicates with driver via DeviceIoControl API. The rootkit supports various IOCTL codes for performing necessary operations.
The rootkit has the following properties.
- It creates unnamed device object, but registers IRP dispatch functions.
- It dispatches IOCTL requests.
- It specializes in run-time patching of Windows kernel code.
- It has huge driver size (153 600 bytes).
- It сontains an inarticulate logic of work, which means that it developed for targeting on one specific goal, but later was modified for another.
Authors of rootkit choose interesting way of its initialization. The rootkit file seems should be patched before loading into memory for storing special process identifier. When it's time to initialize DriverObject and performing other initialization steps, the rootkit code retrieves hardcoded into its body process id and tries to get address of process object with PsLookupProcessByProcessId API. After code got pointer to process, it performs attaching into it address space with KeAttachProcess. Recall that rootkit's DriverEntry function is executed into context of the System process address space.
In this way, if you try to load the rootkit manually, you will get BSOD or get the absence of any activity.On first step of initialization, the rootkit builds its own buffer of NT kernel exports and its hashes in following format: +0 FuncHash; +4 FuncAddr. It contains internal function for extracting specific item (function address) from this table by hash of function (NT export) name.
The rootkit also retrieves pointers to dynamic imports in the following way. It uses functions hal!HalAllocateCommonBuffer or nt!MmIsAddressValid for retrieving start address of hal.dll (HAL) or ntoskrnl (NT). Name of both functions are encrypted into its body and are decrypted on stack.
Next imports the rootkit tries to get during initialization phase (hash -> API).
As we know, Equation Group is known as professional in cryptography, their malware from cyberespionage platform leverage various crypto methods and algorithms that we haven't seen before in such scale. Looks like their love of encryption covers and kernel mode component too. The rootkit stores sensitive strings in encrypted state and doesn't decrypt it inside loaded in memory image, instead it copies necessary data on stack and decrypts it. Before exiting the function, it encrypts string on stack again.
After got address of HalAllocateCommonBuffer (or MmIsAddressValid), the rootkit tries to find start address of hal.dll for further retrieving addresses of dynamic imports. Interesting to see that pointers to HAL or NT functions the rootkit stores in memory in encrypted state, as shown above (it XORed value of each ptr with its offset). Same situation with NT imports, all stored into rootkit image as encrypted.
The rootkit uses some sort of code and data obfuscation, although stores imports of kernel API in the clear state (PE IAT). For masking its activity from researchers eyes, the rootkit creates new driver object with name \Driver\msvss, so after exiting from DriverEntry, original Windows kernel DriverObject will contain no information about created DeviceObject of rootkit as well as about its IRP_MJ handlers.
This method of hiding real DriverObject was used by Remsec rootkit authors. My part 2 of Remsec driver research contains information about this technique. The rootkit calls ObCreateObject for creating new DriverObject, ObInsertObject to create handle and ObMakeTemporaryObject for removing name of the object from its parent object manager directory \Driver\ (hiding from researchers eyes).
Before creating new DriverObject, the rootkit tries to get pointer to another specific system driver object. It contains encrypted names of four drivers listed below. Next, it tries to analyze all driver objects and is looking for one, which contains non-zeroed DriverSection field and checks if DriverObject->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] handler is located into ntoskrnl image.
In my case the rootkit choose \Driver\mountmgr.
During new driver object initialization, the rootkit copies some fields from \Driver\mountmgr into newly created DriverObject. It also copies value of DriverObject->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] of MountMgr into IRP_MJ handlers of the new driver. Later, it rewrites these handler with pointer to one dispatch function.
Next step of the rootkit execution is really interesting. It tries to get handle (FileObject) on its own device object calling IRP_MJ_CREATE handler directly with IoCallDriver. But before doing this, it creates handle to reserved system device object \Device\NULL that belong to driver \Driver\Null. Further, it hijacks DeviceObject field into Null FileObject on rootkit device object and uses this FileObject for sending IRP_MJ_Create request.
In my case the rootkit choose \Driver\mountmgr.
During new driver object initialization, the rootkit copies some fields from \Driver\mountmgr into newly created DriverObject. It also copies value of DriverObject->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] of MountMgr into IRP_MJ handlers of the new driver. Later, it rewrites these handler with pointer to one dispatch function.
Next step of the rootkit execution is really interesting. It tries to get handle (FileObject) on its own device object calling IRP_MJ_CREATE handler directly with IoCallDriver. But before doing this, it creates handle to reserved system device object \Device\NULL that belong to driver \Driver\Null. Further, it hijacks DeviceObject field into Null FileObject on rootkit device object and uses this FileObject for sending IRP_MJ_Create request.
Despite pretty clear logic of driver initialization, execution logic of remaining rootkit code is really incomprehensible. For example, created by rootkit table of ntoskrnl exports is used only in one case, when the rootkit receives special IOCTL code.
Next characteristics show inarticulate logic of rootkit work.
- As mentioned above, the rootkit builds table of ntoskrnl exports that stores pointers to functions and hashes of names. Strange thing is that the rootkit uses this table only one time during dispatching IOCTL request.
- As mentioned above, the rootkit has function for retrieving ntoskrnl API by its function name hash. It also uses obfuscation of pointers to these functions. Another strange thing is that it uses this method only in specific code pieces, like it was written by one person and recently was ignored by another.
- It is unclear how user mode code connects to rootkit for sending IOCTL, because rootkit's device object is unnamed.
Conclusion
GrayFish rootkit looks really strange...beginning from its initialization and finishing its objectives. If we run rootkit driver on machine and next scan it with various anti-rootkits, we will see no suspicious activity. This means that by default rootkit sets no hooks on Windows kernel functions like other rootkits. The rootkit also not registers any callback functions, for example, on process creation or modules loading.
Unlike other civilian and state-sponsored rootkits, GrayFish doesn't explore Windows kernel mode to monitor system's activity or hiding files on disk. At the same time it contains the code for patching Windows kernel functions. This code can be activated later.
Like other rootkits, GrayFish contains code for code/data injection into processes with help of ZwOpenProcess/PsLookupProcessByProcessId/KeStackAttachProcess. While rootkit works with user mode memory during injection, it calls interesting system function - MmSecureVirtualMemory. All instructions it receives from user mode client.