Cyril François

Shedding light on the ABYSSWORKER driver

Elastic Security Labs describes ABYSSWORKER, a malicious driver used with the MEDUSA ransomware attack-chain to disable anti-malware tools.

16 min readMalware analysis
Shedding light on the ABYSSWORKER driver

Summary

Cybercriminals are increasingly bringing their own drivers — either exploiting a vulnerable legitimate driver or using a custom-built driver to disable endpoint detection and response (EDR) systems and evade detection or prevention capabilities.

Elastic Security Labs has monitored a financially motivated campaign deploying MEDUSA ransomware through the use of a HEARTCRYPT-packed loader. This loader was deployed alongside a revoked certificate-signed driver from a Chinese vendor we call ABYSSWORKER, which it installs on the victim machine and then uses to target and silence different EDR vendors. This EDR-killer driver was recently reported by ConnectWise in another campaign, using a different certificate and IO control codes, at which time some of its capabilities were discussed. In 2022, Google Cloud Mandiant disclosed a malicious driver called POORTRY which we believe is the earliest mention of this driver.

In this article, we take an in-depth look at this driver, examining its various features and techniques. We also provide relative virtual addresses (RVA) under each reversed code screenshot to link the research with the reference sample, along with a small client example that you can use to further experiment with this malware.

Technical Analysis

PE header

The binary is a 64-bit Windows PE driver named smuol.sys, and imitates a legitimate CrowdStrike Falcon driver.

ABYSSWORKER driver PE header description
ABYSSWORKER driver PE header description

At the time of analysis, we found a dozen samples on VirusTotal, dating from 2024-08-08 to 2025-02-24. Most were VMProtect packed, but two — referenced in the observable tables below — weren’t protected.

All samples are signed using likely stolen, revoked certificates from Chinese companies. These certificates are widely known and shared across different malware samples and campaigns but are not specific to this driver. The certificate fingerprints are listed below:

FingerprintName
51 68 1b 3c 9e 66 5d d0 b2 9e 25 71 46 d5 39 dcFoshan Gaoming Kedeyu Insulation Materials Co., Ltd
7f 67 15 0f bb 0d 25 4e 47 42 84 c7 f7 81 9c 4fFEI XIAO
72 88 1f 10 cd 24 8a 33 e6 12 43 a9 e1 50 ec 1dFuzhou Dingxin Trade Co., Ltd.
75 e8 e7 b9 04 3b 13 df 60 e7 64 99 66 30 21 c1Changsha Hengxiang Information Technology Co., Ltd
03 93 47 e6 1d ec 6f 63 98 d4 d4 6b f7 32 65 6cXinjiang Yishilian Network Technology Co., Ltd
4e fa 7e 7b ba 65 ec 1a b7 74 f2 b3 13 57 d5 99Shenzhen Yundian Technology Co., Ltd

Obfuscation

ABYSSWORKER uses functions that always return the same value, relying on a combination of opaque predicates and other derivation functions. For example, the zero-returning function below always returns a 0 based on hardcoded derived values.

Zero-Returning function 0x3238
Zero-Returning function 0x3238

Below is one of the derivation functions:

Derivation function 0xF0B4
Derivation function 0xF0B4

These constant-returning functions are called repeatedly throughout the binary to hinder static analysis. However, there are only three such functions, and they aren't used in any predicate but are simply called. We can easily identify them, making this an inefficient obfuscation scheme.

Example of constant-returning function calls 0x10D2
Example of constant-returning function calls 0x10D2

Initialization

Upon initialization, the driver begins by obtaining pointers to several kernel modules and its client protection feature, which will be discussed in the following sections.

Loading pointers on kernel modules 0x63E2
Loading pointers on kernel modules 0x63E2

Initializing client protection feature 0x65c3
Initializing client protection feature 0x65c3

Then, it creates a device with the path \\device\\czx9umpTReqbOOKF and a symbolic link with the path \\??\\fqg0Et4KlNt4s1JT.

Creating device 0x2F45
Creating device 0x2F45

Creating symbolic link 0x2FDA
Creating symbolic link 0x2FDA

It completes initialization by registering callbacks for its major functions.

Registering driver major functions callbacks 0x3067
Registering driver major functions callbacks 0x3067

Client protection on device opening

When the driver device is opened, the IRP_MJ_CREATE major callback is called. This function is responsible for adding the process ID to the list of processes to protect and for stripping any pre-existing handles to the target process from the list of running processes.

The function retrieves the process ID from the current kernel thread since the kernel callback is executed in the context of the client process when the device is opened.

Get client PID from current thread 0x138B
Get client PID from current thread 0x138B

Before adding the process ID to the protection list, ABYSSWORKER searches for and strips any existing handles to the client process in other running processes.

To achieve this, the malware iterates over existing processes by brute-forcing their Process IDs (PIDs) to avoid reliance on any API. For each process, it iterates over their handles, also using brute force, and checks if the underlying object corresponds to the client process. If a match is found, it strips the access rights using the value passed as a parameter (0x8bb).

ABYSSWORKER stripping existing handles to the client from other processes 0x9EDB
ABYSSWORKER stripping existing handles to the client from other processes 0x9EDB

ABYSSWORKER setting access rights of client handle if found in process 0xA691
ABYSSWORKER setting access rights of client handle if found in process 0xA691

Finally, it adds the PID to the global list of protected processes.

Client PID is added to the global protected processes list 0x9F43
Client PID is added to the global protected processes list 0x9F43

As mentioned earlier, the driver sets up its protection feature during the initialization phase. This protection relies on registering two pre-operation callbacks using the ObRegisterCallback API: one to detect the opening of handles to its protected processes and another to detect the opening of handles to the threads of those protected processes.

The two callbacks operate in the same way: they set the desired access for the handle to zero, effectively denying the creation of the handle.

Registration of callbacks to catch thread and process opening to protected client 0xA2B0
Registration of callbacks to catch thread and process opening to protected client 0xA2B0

Denying access to protected process handle 0xA0A6
Denying access to protected process handle 0xA0A6

DeviceIoControl handlers

Upon receiving a device I/O control request, ABYSSWORKER dispatches the request to handlers based on the I/O control code. These handlers cover a wide range of operations, from file manipulation to process and driver termination, providing a comprehensive toolset that can be used to terminate or permanently disable EDR systems.

We detail the different IO controls in the table below:

NameCode
Enable malware0x222080
Copy file0x222184
Remove callbacks and devices by module name0x222400
Replace driver major functions by module name0x222404
Kill system threads by module name0x222408
Detach mini filter devices0x222440
Delete file0x222180
Disable malware0x222084
Load api0x2220c0
Decrease all drivers reference counter0x222100
Decrease all devices reference counter0x222104
Terminate process0x222144
Terminate thread0x222140
Removing hooks from Ntfs and Pnp drivers' major functions0x222444
Reboot0x222664

Enabling the malware (0x222080)

As discussed in this blog post, the client must enable the driver by sending a password (7N6bCAoECbItsUR5-h4Rp2nkQxybfKb0F-wgbJGHGh20pWUuN1-ZxfXdiOYps6HTp0X) to the driver, in our case it’s through the 0x222080 IO control.

The handler simply compares the user input with the hardcoded password. If correct, it sets a global flag to true (1). This flag is checked in all other handlers to permit or deny execution.

Hardcoded password 0x12000
Hardcoded password 0x12000

Enabling malware if the password is correct 0x184B
Enabling malware if the password is correct 0x184B

Loading the API (0x2220c0)

Most handlers in the malware rely on kernel APIs that must be loaded using this handler. This handler loads these globals along with several structures, using the kernel module pointers previously loaded during initialization. Once the loading is complete, a global flag is set to signal the availability of these APIs.

Set the global flag to 1 once the API is loaded 0x1c28
Set the global flag to 1 once the API is loaded 0x1c28

This handler has two modes of operation: a full mode and a partial mode. In full mode, it loads the APIs using a mapping structure of function names and RVA provided by the user as input to the IO control. In partial mode, it searches for some of the APIs on its own but does not load all the APIs that are loaded in full mode, hence the term partial mode. If the user opts for partial mode due to the inability to provide this mapping structure, some handlers will not execute. In this chapter, we only cover the full mode of operation.

We detail the structures used below:

#define AM_NAME_LENGTH 256
typedef struct _struct_435
{
uint64_t rva;
char name[AM_NAME_LENGTH];
} struct_435_t;
#define AM_ARRAY_LENGTH 1024
typedef struct _struct_433
{
struct_435_t array[AM_ARRAY_LENGTH];
uint32_t length;
} struct_433_t;

We provide a short example of usage below:

struct_433_t api_mapping = {
.length = 25,
.array = {
[0] = {.rva = 0xcec620, .name = "PspLoadImageNotifyRoutine"},
[1] = {.rva = 0xcec220, .name = "PspCreateThreadNotifyRoutine"},
[2] = {.rva = 0xcec420, .name = "PspCreateProcessNotifyRoutine"},
// (...)
[24] = {.rva = 0x250060, .name = "NtfsFsdShutdown"},
}};
uint32_t malware_load_api(HANDLE device)
{
return send_ioctrl(device, IOCTRL_LOAD_API, &api_mapping, sizeof(struct_433_t), NULL, 0);
}

To load its API, the function starts by loading three 'callback lists' from different kernel object types. These are used by the handler that removes registered notification callbacks belonging to a specific module.

ABYSSWORKER getting callback list from kernel’s _OBJECT_TYPEs 0x5502
ABYSSWORKER getting callback list from kernel’s _OBJECT_TYPEs 0x5502

Then, it loads pointers to functions by using the provided structure, simply by searching for the function name and adding the associated RVA to the module's base address.

Get function RVA from structure 0x5896
Get function RVA from structure 0x5896

Search RVA associated with function name in structure 0x3540
Search RVA associated with function name in structure 0x3540

This is done for the following 25 functions:

  • PspLoadImageNotifyRoutine
  • PspCreateThreadNotifyRoutine
  • PspCreateProcessNotifyRoutine
  • CallbackListHead
  • PspSetCreateProcessNotifyRoutine
  • PspTerminateThreadByPointer
  • PsTerminateProcess
  • IopInvalidDeviceRequest
  • ClassGlobalDispatch
  • NtfsFsdRead
  • NtfsFsdWrite
  • NtfsFsdLockControl
  • NtfsFsdDirectoryControl
  • NtfsFsdClose
  • NtfsFsdCleanup
  • NtfsFsdCreate
  • NtfsFsdDispatchWait
  • NtfsFsdDispatchSwitch
  • NtfsFsdDispatch
  • NtfsFsdFlushBuffers
  • NtfsFsdDeviceControl
  • NtfsFsdFileSystemControl
  • NtfsFsdSetInformation
  • NtfsFsdPnp
  • NtfsFsdShutdown

File copy and deletion (0x222184, 0x222180)

To copy or delete files, ABYSSWORKER relies on a strategy that, although not new, remains interesting. Instead of using a common API like NtCreateFile, an I/O Request Packet (IRP) is created from scratch and sent directly to the corresponding drive device containing the target file.

Creating a file

The file creation function is used to showcase how this mechanism works. The function starts by obtaining the drive device from the file path. Then, a new file object is created and linked to the target drive device, ensuring that the new object is properly linked to the drive.

Building a new file object 0x7A14
Building a new file object 0x7A14

Then, it creates a new IRP object and sets all the necessary data to perform the file creation operation. The major function targeted by this IRP is specified in the MajorFunction property, which, in this case, is set to IRP_MJ_CREATE, as expected for file creation.

Building new IRP 0x7C68
Building new IRP 0x7C68

Then, the malware sends the IRP to the target drive device. While it could have used the IoCallDriver API to do so, it instead sends the IRP manually by calling the corresponding device's major function.

Sending IRP to device 0x9B14
Sending IRP to device 0x9B14

At this point, the file object is valid for further use. The handler finishes its work by incrementing the reference counter of the file object and assigning it to its output parameter for later use.

Copying a file

To copy a file, ABYSSWORKER opens both the source and destination files, then reads (IRP_MJ_READ) from the source and writes (IRP_MJ_WRITE) to the destination.

Copying file using IRPs 0x4BA8
Copying file using IRPs 0x4BA8

Reading and writing files using IRPs 0x66D9
Reading and writing files using IRPs 0x66D9

Deleting a file

The deletion handler sets the file attribute to ATTRIBUTE_NORMAL to unprotect any read-only file and sets the file disposition to delete (disposition_info.DeleteFile = 1) to remove the file using the IRP_MJ_SET_INFORMATION IRP.

Setting file attribute to normal and deleting it 0x4FB6
Setting file attribute to normal and deleting it 0x4FB6

Building IRP_MJ_SET_INFORMATION IRP to delete file 0x67B4
Building IRP_MJ_SET_INFORMATION IRP to delete file 0x67B4

Notification callbacks removal by module name (0x222400)

Malware clients can use this handler to blind EDR products and their visibility. It searches for and removes all registered notification callbacks. The targeted callbacks are those registered with the following APIs:

  • PsSetCreateProcessNotifyRoutine
  • PsSetLoadImageNotifyRoutine
  • PsSetCreateThreadNotifyRoutine
  • ObRegisterCallbacks
  • CmRegisterCallback

Additionally, it removes callbacks registered through a MiniFilter driver and, optionally, removes devices belonging to a specific module.

Deleting notifications callbacks and devices 0x263D
Deleting notifications callbacks and devices 0x263D

To delete those notification callbacks, the handler locates them using various methods, such as the three global callback lists previously loaded in the loading API handler, which contain callbacks registered with ObRegisterCallbacks and CmRegisterCallback. It then deletes them using the corresponding APIs, like ObUnRegisterCallbacks and CmUnRegisterCallbacks.

Blinding EDR using these methods deserves a whole blog post of its own. To keep this post concise, we won’t provide more details here, but we invite the reader to explore these methods in two well-documented projects that implement these techniques:

Replace driver major functions by module name 0x222404

Another way to interfere with a driver is by using this handler to replace all its major functions with a dummy function, thus disabling any interaction with the driver, given a target module name.

To achieve this, ABYSSWORKER iterates through the driver objects in the Driver and Filesystem object directories. For each driver object, it compares the underlying module name to the target module, and if they match, it replaces all of its major functions with IopInvalidDeviceRequest.

Replacing targeted driver major functions with dummy functions 0x9434
Replacing targeted driver major functions with dummy functions 0x9434

Detach mini filter devices (0x222440)

This handler iterates over all driver objects found in the Driver and FileSystem object directories. For each driver, it explores its device tree and detaches all devices associated with the mini filter driver: FltMgr.sys.

Searching object directories for FltMgr.sys driver to delete its devices 0xE1D8
Searching object directories for FltMgr.sys driver to delete its devices 0xE1D8

The function works by iterating over the devices of the driver through the AttachedDevice and NextDevice pointers, retrieving the module name of each device's associated driver, and comparing it to the target module name passed as a parameter (”FltMgr.sys”). If the names match, it uses the IoDetachDevice function to unlink the device.

Iterating and detaching all devices by module name 0xB9E
Iterating and detaching all devices by module name 0xB9E

Kill system threads by module name (0x222408)

This handler iterates over threads by brute-forcing their thread IDs and kills them if the thread is a system thread and its start address belongs to the targeted module.

Brute-forcing threads to find and terminate targeted module system threads 0xECE6
Brute-forcing threads to find and terminate targeted module system threads 0xECE6

To terminate the thread, the malware queues an APC (asynchronous procedure call) to execute code in the context of the targeted thread. Once executed, this code will, in turn, call PsTerminateSystemThread.

ABYSSWORKER queuing APC to terminate target thread 0x10A6
ABYSSWORKER queuing APC to terminate target thread 0x10A6

Terminate process and terminate thread (0x222144, 0x222140)

With these two handlers you can terminate any process or a thread by their PID or Thread ID (TID) using PsTerminateProcess and PsTerminateThread.

Terminating process by PID 0x2081
Terminating process by PID 0x2081

Terminating thread by TID 0x1F07
Terminating thread by TID 0x1F07

Removing hooks from Ntfs and Pnp drivers' major functions (0x222444)

On top of registering notification callbacks, some EDRs like to hook major functions of the NTFS and PNP drivers. To remove those hooks, the malware can call this driver to restore the original major functions of those drivers.

Restoring hooked NTFS and PNP driver major functions 0x2D32
Restoring hooked NTFS and PNP driver major functions 0x2D32

ABYSSWORKER simply iterates over each registered major function, checks if the function belongs to the driver module, and if not, it means the function has been hooked, so it replaces it with the original functions.

Restoring major function if hooked 0x43AD
Restoring major function if hooked 0x43AD

Reboot 0x222664

To reboot the machine, this handler uses the undocumented function HalReturnToFirmware.

ABYSSWORKER reboot the machine from the kernel 0x2DC0
ABYSSWORKER reboot the machine from the kernel 0x2DC0

Client implementation example

In this blog post, we provide a small client implementation example. This example works with the reference sample and was used to debug it, but doesn’t implement all the IOCTRLs for the driver and is unlikely to be updated in the future.

However, it contains all the functions to enable it and load its API, so we hope that any motivated reader, with the help of the information in this article, will be able to extend it and further experiment with this malware.

Client example output
Client example output

The repository of the project is available here.

Malware and MITRE ATT&CK

Elastic uses the MITRE ATT&CK framework to document common tactics, techniques, and procedures that threats use against enterprise networks.

Tactics

Techniques

Techniques represent how an adversary achieves a tactical goal by performing an action.

Mitigations

YARA

Elastic Security has created the following YARA rules related to this post:

Observations

The following observables were discussed in this research:

ObservableTypeReferenceDate
6a2a0f9c56ee9bf7b62e1d4e1929d13046cd78a93d8c607fe4728cc5b1e8d050SHA256ABYSSWORKER reference sampleVT first seen: 2025-01-22
b7703a59c39a0d2f7ef6422945aaeaaf061431af0533557246397551b8eed505SHA256ABYSSWORKER sampleVT first seen: 2025-01-27

References