Not very gentlemanly: Analyzing a zero-day exploit used by The Gentlemen ransomware to disable targets’ EDRs

By Marcus Hutchins

June 30, 2026  •  18 minute read



Placeholder image for Not very gentlemanly: Analyzing a zero-day exploit used by The Gentlemen ransomware to disable targets’ EDRs

TL;DR

  • The Gentlemen are a relatively new ransomware group who first emerged in July of 2025.
  • In an incident investigated by Expel, the group used a zero-day vulnerability to disable the target’s EDR, preventing it from intervening in their ransomware attack.
  • The threat actor relies heavily on bring-your-own-vulnerable-driver (BYOVD) style attacks to disable endpoint protection.
  • Expel’s Threat Intelligence team captured and analyzed both the vulnerable driver and exploit code the threat actor used, which at the time of reporting is a zero-day, and not present in any public vulnerable driver blocklists.

 

 The threat actor's EDR killer exploit in action.
The threat actor’s EDR killer exploit in action.

Ransomware groups have long relied on disabling endpoint detection and response (EDR) tools before deploying their payloads, and in recent years have utilized bring-your-own-vulnerable-driver (BYOVD) attacks to do so. 

The use of BYOVD by ransomware groups began trending as a result of the discovery of a major vulnerability in GIGABYTE’s gdrv.sys. Although the vulnerability was responsibly disclosed by SecureAuth, the vendor refused to acknowledge the issue, resulting in a public proof-of-concept being made available. In February 2020, the exploit was picked up by RobinHood ransomware group, who weaponized it into an EDR killer. This marked the first known case of ransomware actors abusing BYOVD to disable security software.

The Gentlemen, a group that first surfaced in mid-2025, makes extensive use of BYOVD to take down EDR tooling and carry their attacks forward. During an incident investigated by Expel in early April, the group deployed tooling to disable the target’s EDR, which was likely intended to defang its ransomware-prevention features. The tool leveraged a zero-day vulnerability in an obscure third-party vendor driver.

What’s notable here isn’t the technique itself, or the choice of drivers that The Gentlemen target, but the sophistication of the toolkit they’ve built around it. By chaining together several advanced techniques to navigate around various Windows exploit mitigations, the attackers built a way to call privileged kernel mode functions from within a user mode process.

The following is a technical run-through of how this specific exploit works, from the vulnerable drive all the way through to the moment it terminates your EDR. 

 

The vulnerable kernel driver

The exploit leverages a major vulnerability in a driver named ktapi.sys, which is part of an application programming interface (API) developed by Kontron. Prior to our publication, there were no publicly documented instances of this driver being abused by threat actors. At the time of our discovery, Google contained only a single reference to ktapi.sys, which contained no download link. It’s still unclear how the threat actors came into possession of the file or gained knowledge of its vulnerability.

The driver itself is fairly simple, having minimal functionality other than the vulnerable code. It exposes a few main capabilities via the Windows IOCTL interface. This is a common interface designed to allow user mode processes to communicate with kernel drivers safely. However, it’s reliant on the kernel driver implementing sufficient security checks on any data passed from user mode. 

Kernel drivers can define semi-arbitrary IOCTL codes, allowing a caller to invoke different behavior via the same call path. For user mode applications, a driver’s IOCTL handler is typically invoked by calling DeviceIoControl on a handle to the driver’s DeviceObject, along with an IOCTL code, and the address of an input and/or output buffer. 

A snippet from the MSDN page documenting the function arguments for the DeviceIoControl function.
A snippet from the MSDN page documenting the function arguments for the DeviceIoControl function.

The encoding format for an IOCTL code is illustrated below:

A simple chart breaking down what each of the 32 bits of the IOCTL code is used for.
A simple chart breaking down what each of the 32 bits of the IOCTL code is used for.

The vulnerable driver implements three different IOCTL codes: 

  • 0x82007000 maps a caller-supplied hardware bus address into the process’s memory for Memory Mapped I/O (MMIO).
  • 0x82007100 unmaps a hardware bus address mapped by the previous IOCTL code.
  • 0x82007200 performs port I/O to user-specified I/O ports with user-specified data.
The driver's main IRP handler, which is responsible for handling all 3 IOCTL codes (0x82007000, 0x82007100, 0x82007200).
The driver’s main IRP handler, which is responsible for handling all 3 IOCTL codes (0x82007000, 0x82007100, 0x82007200).

This driver’s behavior is not uncommon for code designed to interface with proprietary hardware, typically for the purpose of debugging.

Once loaded, a user mode process can open a handle to the driver via calling CreateFile on the driver’s device name (\\.\ktapi). The process can then communicate with the driver via the DeviceIoControl API, supplying it with the IOCTL code of the specific functionality it wishes to invoke.

When the caller invokes IOCTL 0x82007000, it passes a buffer containing the type, virtual address, and size of the bus it wishes to map. The driver first calls HalTranslateBusAddress to translate the virtual address of the hardware bus into a physical memory address. Then ZwOpenSection & ZwMapViewOfSection is used to map the specified region of physical memory into the calling process’s address space (from \Device\PhysicalMemory).

The function used to translate and map the user-supplied bus address.
The function used to translate and map the user-supplied bus address.

The problem exists with how HalTranslateBusAddress behaves when passed something other than a bus address. With the InterfaceType parameter set to certain values, the function simply “fails open” by returning the same address the caller input, along with a success response.

This behavior allows an attacker to abuse functions which assume HalTranslateBusAddress will reject addresses that don’t belong to a memory bus, such as arbitrary kernel memory. Since this function maps memory directly from \Device\PhysicalMemory, the attacker can simply give it any address within the system’s RAM and the driver will happily map it into the caller’s process.

 

The EDR killer exploit

Dealing with physical memory

Each process gets its own virtual address space. For example, address 0x1234000 in one process isn’t the same RAM address as address 0x1234000 in another process. They both map to different addresses in the computer’s RAM (physical memory). This is designed to stop address collisions across processes. However, this poses a bit of a problem.

User mode processes only see virtual addresses, but the vulnerability only enables the user mode process to read/write physical addresses. So first, the exploit must find a way to translate virtual addresses to physical addresses.

Each process has a Page Map Level 4 (PML4) assigned to it. This table is used to translate process-specific virtual addresses into system-wide physical addresses. It’s stored in the cr3 register, but since cr3 is a kernel-mode register, the process cannot read it directly. Sometimes vulnerable drivers also expose the ability to read the cr3 register, but this one does not.

Instead, the exploit code obtains the cr3 value by sequentially scanning through the system’s RAM and signature matching to find memory pages which look like PML4s. It does this by leveraging the vulnerable driver to map a block of physical memory pages into the current process, scans them for PML4 signatures, then unmaps them.

he code for scanning through the system memory in blocks of 0x200 pages (2 MB) at a time.
The code for scanning through the system memory in blocks of 0x200 pages (2 MB) at a time.

Every potential PML4 the exploit code finds gets pushed to a list of candidates to validate. Each process has its own PML4, so the signature will match multiple different processes’ PML4s. Once the list is built, the application attempts to resolve the virtual address of one of its own stack variables (virtual_address) using each discovered PML4.

A partial snippet of the loop attempting to resolve the target address with each PML4 candidate.
A partial snippet of the loop attempting to resolve the target address with each PML4 candidate.

If the virtual address is successfully resolved to a physical address via one of the page maps, the exploit then maps the resulting physical memory using the vulnerable driver, then scans the page for the value stored in the stack variable (virtual_address).

The value of the stack variable virtual_address is always set to 0xdeadbeefcafebabe, which is a form of hexspeak unlikely to appear in memory naturally. Thus, the process can be sure if the address resolves successfully via a given PML4, and the physical address it resolves to contains the value 0xdeadbeefcafebabe, then it has found the PML4 for its own process.

Every process’s PML4 also has a mapping for the kernel address space; thus, the process can also use its own PML4 to resolve virtual addresses belonging to the kernel. This is important because it gives the malicious code the ability to find the physical page belonging to a specific kernel address, map it via the vulnerable driver, then read/write kernel memory directly.

 

Gaining kernel mode code execution

Bypassing Kernel Patch Protection (KPP)

The most common way to call a kernel function is via a system call (commonly abbreviated to  syscall). A syscall can be invoked by a user mode function, and causes the kernel to run predefined code with kernel mode privileges. However, kernel functions which are exposed to user mode perform strict security checks. Despite running with kernel privileges, these functions will validate the calling process’s privileges and refuse to perform any operation it doesn’t possess the correct permissions to perform.

Previously, exploits could simply just bypass this by redirecting syscalls to point to different kernel functions. Since many kernel functions are designed to only be called from kernel mode, most of them don’t validate the caller’s permission. This enabled a user mode application with an arbitrary-write vulnerability to call kernel functions with full kernel mode privileges. 

On modern systems, Kernel Patch Protection (KPP), also known as PatchGuard, prevents many kernel functions, function pointers, and vtables from being modified. Overwriting a protected region results in the system triggering a bug check (colloquially called the blue screen of death, or BSoD). This limits the ability of exploits or malware to simply redirect one kernel function to another.

Instead, the exploit’s developers use a clever trick based on how Win32k.sys works. Similar to the main kernel, Win32k also exposes various functions to user mode via the syscall interface (implemented in win32ku.dll). However, the kernel mode side of these functions have some interesting quirks that can be abused by exploits.

An example of the kernel mode side of a Win32k syscall (NtUserFrostCrashedWindow).
An example of the kernel mode side of a Win32k syscall (NtUserFrostCrashedWindow).
The same code as above, but in Assembly language for clarity.
The same code as above, but in Assembly language for clarity.

The syscall lands at a stub function, which simply loads the address of the real function from a global variable, then calls that instead. This is referred to as an indirect call (a call that loads the address to call from memory, rather than encoding it directly into the call instruction).

While these pointers should ideally be validated by PatchGuard, they appear not to be. Therefore, they can be safely modified to redirect the kernel side of the syscall to an address of the attacker’s choosing. Additionally, since no privilege checks happen in the stub function, the overwritten address will be called with full kernel mode privileges.

The exploit abuses both Win32k!NtUserFrostCrashedWindow and Win32k!NtUserSetGestureConfig by overwriting their call pointers. These functions are likely chosen due to their relatively sparse use in day-to-day system operations. This ensures that temporarily redirecting them is unlikely to cause system instability.

Abuse of NtUserSetGestureConfig in this way has been previously documented on game cheat development forums, where developers also commonly leverage BYOVD attacks to bypass kernel mode anti-cheat software. It’s entirely possible some of the other techniques this exploit uses were also learned from cheat development communities.

 

Bypassing supervisor mode exploit mitigations

An introduction to Supervisor Mode Access Prevention and Supervisor Mode Execution Prevention

Modern CPUs contain many built-in exploit mitigations, including Supervisor Mode Access Prevention (SMAP) and Supervisor Mode Execution Prevention (SMEP).

Because the kernel has privileges to access user mode addresses, by default it can read, write, and execute user mode memory. To maintain proper privilege separation, the kernel should never execute code located in user mode memory without transitioning to user mode first. However, prior to SMEP, this boundary was only enforced at the software level.

Older privilege escalation exploits would abuse this by simply allocating some executable memory in user mode and writing shellcode to it. Attackers could simply use a kernel vulnerability to replace a kernel pointer with the address of their shellcode in user mode memory, resulting in the kernel executing arbitrary user mode code with kernel mode privileges.

SMEP is designed to prevent this. CPUs with SMEP enabled will check whether a memory page belongs to user mode or kernel mode before executing it. If the memory was allocated by user mode, but the CPU is currently running in kernel mode, SMEP will trigger a page fault, crashing the system. Since this mitigation is implemented at the hardware level, it’s much more difficult to bypass.

SMAP works similarly, but with access instead of execution. It’s designed to prevent the kernel from inadvertently reading from or writing to user mode memory. However, since the kernel needs to read and write user mode memory, this feature can be disabled with the stac instruction and re-enabled with the clac instruction.

What this means in practice is the kernel only performs a clac operation when it knows it needs to access user mode memory. Any other read or write operations will simply crash the system if given a user mode address. This mitigates many classes of exploits which rely on tricking their kernel into using user mode addresses for privileged operations.

Together, these two mitigations provide powerful protections against many privilege escalation exploits. However, exploits can still bypass both SMAP and SMEP by chaining together multiple vulnerabilities.But because this vulnerability allows an attacker to read and write any physical memory they want, it’s powerful enough to bypass both mitigations without the need for a vulnerability chain.

Bypassing Supervisor Mode Access Protection (SMAP)

First, the exploit reads the kernel file from disk (C:\Windows\System32\ntoskrnl.exe) then maps it into its own process memory. It scans the kernel’s code for a regex-like signature corresponding to the Assembly instructions mov qword [rcx], rdx; retn. This sequence typically belongs to a non-exported function named nt!MiSetPfnLink. It simply saves the value stored in the rdx register to the address stored in the rcx register.

The function for setting up the regex-like pattern matching routine to find nt!MiSetPfnLink.
The function for setting up the regex-like pattern matching routine to find nt!MiSetPfnLink.

Once the instruction offset is found, the code then calculates the offset of the function in physical memory. This is achieved by getting the kernel’s base address via a call to NtQuerySystemInformation with the SystemModuleInformation flag. It then adds the offset to the kernel’s virtual address before translating it to a physical address using the page table it resolved earlier.

The exploit then overwrites the pointer called by the NtUserFrostCrashedWindow we looked at earlier. This pointer is overwritten with the address of the scanned instruction. Now, every time a user mode process called NtUserFrostCrashedWindow, it will simply execute mov qword [rcx], rdx with kernel mode privileges.

The target of NtUserFrostCrashedWindow after being overwritten.
The target of NtUserFrostCrashedWindow after being overwritten.

Since the first and second argument for an x86_64 function call are in the rcx and rdx register respectively, the function will simply store the value in the second argument (rdx) at the address stored in the first argument (rcx). This provides an easy way for the user mode application to write arbitrary kernel mode virtual addresses, rather than having to deal directly with physical memory all the time.

The attack abuses a shared page known as KUSER_SHARED_DATA. This page is mapped into every user-mode process at address 0x7FFE0000 and into the kernel at address 0xFFFFF78000000000. The user mode side of the mapping is read-only, but the kernel mode side is readable and writable. Because it’s a shared page, anything written to the kernel mode side is reflected by the user mode side.

Writing the kernel mode side of KUSER_SHARED_DATA doesn’t trigger an SMAP violation, as it’s a kernel mode page. The OS allows the same physical memory to be mapped multiple times as separate virtual pages. It’s the permissions of the virtual page that SMAP checks, not the underlying physical memory.

So in short, the exploit targets the KUSER_SHARED_DATA shared page that exists simultaneously in both kernel and user space. By writing to the kernel mode mapping using mov qword [rcx], rdx, the changes are automatically visible in user space, since both virtual addresses point to the same physical memory. This allows for the manipulation of kernel data from user mode without triggering an SMAP violation.

 

Bypassing Supervisor Mode Execution Protection (SMEP)

Next up, the exploit does the same pointer substitution, but this time with the function NtUserSetGestureConfig. It replaces the call address with the virtual address of ExAllocatePoolWithTag

The function ExAllocatePoolWithTag is essentially the kernel mode equivalent of HeapAlloc, enabling drivers to dynamically allocate memory to store arbitrary data. Although, by default, kernel pool memory is executable, in addition to readable and writable.

Because this function’s parameters are all immediate values, and the allocation address is returned in the rax register, SMAP isn’t an issue since no user mode addresses are read or written.

The exploit calls the user mode version of NtUserSetGestureConfig (ntdll!NtUserSetGestureConfig) which uses a syscall to transition to kernel mode then executes nt!NtUserSetGestureConfig. Since the function now simply redirects to ExAllocatePoolWithTag, the returned value is the virtual address of a kernel pool allocation.

The exploit code setting up the kernel side of NtUserSetGestureConfig to call ExAllocatePoolWithTag instead, then calling that function.
The exploit code setting up the kernel side of NtUserSetGestureConfig to call ExAllocatePoolWithTag instead, then calling that function.

The parameters passed are 0, 0x10, and 0x726c6c6b. 0 sets the pool type for NonPagedPool, 0x10 is the size of the memory to allocate (16 bytes), and 0x726c6c6b is an arbitrary ‘pool tag’ used to identify the pool allocation. 0x726c6c6b is ‘kllr‘ (killer) in ASCII, likely a reference to the exploit being used as an EDR killer.

The returned value is the virtual address of a 16 byte kernel pool allocation that’s marked as RWX (readable, writable, and executable).

Once the allocation is complete, the exploit translates the virtual address of the kernel allocation to a physical address, then uses the vulnerable driver to write a piece of shellcode to it. The pointer used by NtUserSetGestureConfig is then changed once again, this time to point to the shellcode.

The code for writing shellcode to the kernel pool.
The code for writing shellcode to the kernel pool.

The shellcode simply does the following:

mov rax, [0xfffff78000000810]; 

jmp rax

The address 0xfffff78000000810 is an address 0x810 bytes into KUSER_SHARED_DATA (the kernel side of the shared memory we talked about earlier). This means that NtUserSetGestureConfig will call whatever address is placed at address KUSER_SHARED_DATA + 0x810. As long as the address the exploit stored there is a kernel virtual address, the call will not violate SMAP or SMEP.

 

The final exploit setup

Once both NtUserFrostCrashedWindow and NtUserSetGestureConfig have been redirected, the exploit has everything it needs to call any kernel function using its virtual address. The function responsible for doing this is one I’ve named “KernelCall“.

The 'KernelCall' function.
The ‘KernelCall’ function.

The function first calls NtUserFrostCrashedWindow with the address 0xfffff78000000810 as the first parameter. The second parameter is an arbitrary address passed as the 2nd parameter of KernelCall. The mov qword [rcx], rdx instruction pointed to by NtUserFrostCrashedWindow cause the given address to be stored at 0xfffff78000000810 (KUSER_SHARED_DATA + 0x810).

The function then jumps to NtUserSetGestureConfig, which has been redirected to shellcode then retrieves and calls the address stored at 0xfffff78000000810.

The universal kernel functional caller ‘KernelCall’ forms the basis for the rest of the EDR killer. The code now has the ability to call arbitrary kernel mode functions via their virtual address. The vulnerable driver is now no longer needed and can now be unloaded.

Terminating EDR processes

Typically, EDRs run as protected processes, which means no user mode application can interfere with them (even if it’s running at NT AUTHORITY\SYSTEM).

This is where the EDR Killer’s universal kernel function caller comes in. Its ability to redirect the kernel portion of the NtUserSetGestureConfig at will means any function it calls is executed with full kernel mode privileges, bypassing any process protection checks.

The application abuses this by resolving the virtual address of the following kernel functions:

  • PsLookupProcessByProcessId: Returns the EPROCESS structure associated with a given process ID. EPROCESS is the kernel mode equivalent of a process HANDLE.
  • ObDereferenceObject: Decreases the reference counter of an object. When PsLookupProcessByProcessId returns the EPROCESS structure, it increments its reference counter. The kernel will not allow an object to be deleted until all references to it are closed.
  • PsTerminateProcess: The function NtTerminateProcess uses to actually terminate a process. It’s likely the attackers avoided using NtTerminateProcess directly to either bypass any extra sanity checks the function does, or because EDR drivers commonly monitor calls to NtTerminateProcess via a kernel callback.

With these 3 functions, the exploit code can look up any process ID, get a kernel mode handle to said process, then terminate it using kernel mode privileges. This bypasses Protected Process, Protected Process Lite, and several other EDR self-defense mechanisms.

The code for killing a target process.
The code for killing a target process.

As you can see, the code avoids SMAP by having any kernel calls read from/write to an offset within the kernel mode address of KUSER_SHARED_DATA (0xfffff78000000800), while the user mode process reads from the user mode one (0x7ffe0800).

The code endlessly loops through the list of currently running processes, checking for and terminating any process that matches a list of hardcoded names. This not only kills the EDR process but prevents it from being restarted either automatically or manually.

The list of processes terminated by this version of the EDR killer is:

  • MsMpEng.exe (Windows Defender)
  • MpDefenderCoreService.exe (Windows Defender)
  • eOPPMonitor.exe (ESET)
  • EIConnector.exe (ESET)
  • EHttpSrv.exe (ESET)
  • cortex-xdr-payload.exe (Palo Alto Cortex XDR)
  • cysandbox.exe (Palo Alto Cortex XDR)
  • cyserver.exe (Palo Alto Cortex XDR)
  • cytray.exe (Palo Alto Cortex XDR)
  • cyuserserver.exe (Palo Alto Cortex XDR)
  • cywscsvc.exe (Palo Alto Cortex XDR)
  • tlaworker.exe (Palo Alto Cortex XDR)
  • SentinelAgent.exe (SentinelOne)
  • SentinelAgentWorker.exe (SentinelOne)
  • SentinelServiceHost.exe (SentinelOne)
  • SentinelStaticEngine.exe (SentinelOne)
  • SentinelStaticEngineScanner.exe (SentinelOne)
  • SentinelMemoryScanner.exe (SentinelOne)
  • SentinelHelperService.exe (SentinelOne)
  • SentinelUI.exe (SentinelOne)
  • SentinelBrowserNativeHost.exe (SentinelOne)
  • SentinelCtl.exe (SentinelOne)
  • SentinelRemediation.exe (SentinelOne)
  • SentinelScanner.exe (SentinelOne)
  • SentinelCleaner.exe (SentinelOne)
  • SentinelRemoteShellHost.exe (SentinelOne)
  • SentinelScanFromContextMenu.exe (SentinelOne)
  • SentinelRanger.exe (SentinelOne)
  • LogProcessorService.exe (SentinelOne)
  • LogCollector.exe (SentinelOne)

Mitigations and Expel’s recommendations

Leaving cross-signing disabled (Windows 11 25H2+ & Windows Server 2025+)

On Windows versions with the kernel build version 10.0.26100.32690 and higher, Microsoft rolled out a security feature which disables cross-signing. Prior to Windows 10, anyone with an Extended Validation (EV) code signing certificate could sign Windows kernel drivers.

With Windows 10 1607, Microsoft introduced the Windows Hardware Quality Labs (WHQL). WHQL requires vendors to submit their drivers to Microsoft for approval before they become valid on Windows 10 1607 and above; however, existing non-WHQL drivers were grandfathered in.

Following the update to build 26100.32690, the grandfathering of cross-signed legacy drivers was disabled. This change prevents millions of legacy unsecure and unmaintained drivers from being loaded unless the system administrator manually approves them.

The program was intended to deny overtly malicious drivers, as well as drivers with severe security defects, but the latter was never really enforced. This means that plenty of extremely easy to abuse, vulnerable drivers (similar to the one abused in the article) get approved.

With that said, the feature does drastically reduce the attack surface. New, vulnerable drivers may still get through, but cutting off millions of old legacy drivers does meaningfully boost security posture.

Ktapi.sys, the BYOVD driver itself, is a legacy cross-signed driver and therefore would have been automatically blocked by this feature.

 

Enabling core isolation/virtualization-based security (VBS)

Core isolation, also known as virtualization-based security (VBS) is a set of security mitigation which uses hypervisor technologies to protect various parts of the operating system, especially the kernel. While, unfortunately, this feature does require hardware that supports virtualization and typically must be manually enabled via the system BIOS, it’s an extremely worthwhile feature.

VBS essentially creates a third privilege level (the hypervisor), which is used to protect sensitive kernel code. It means that even with a vulnerable driver, certain parts of the kernel still can’t be modified due to VBS. There are many different features such as memory integrity, kernel stack protection, LSASS protection, credential guard, and more.

The 'core isolation' exploit mitigation settings page.
The ‘Core isolation’ exploit mitigation settings page.

The ‘memory integrity’ toggle enables several features, including kernel Control Flow Guard (kCFG), which prevents the exploit from calling its shellcode, causing it to fail. Although the same driver could be used to bypass kCFG by leveraging a different exploit technique, it would have prevented this specific exploit.

Additionally, Credential Guard prevents even kernel-mode code from dumping credentials with tools like Mimkikatz, so it can reduce lateral movement opportunities, or obtain admin credentials. Both are important for preventing ransomware. Admin privileges are required to load BYOVD drivers, so by enabling exploit mitigations and credential guard, paths to obtaining admin become limited.

 

Enabling the vulnerable driver blocklist

An additional part of ‘Core isolation’ is the ‘vulnerable driver block list’ toggle, which blocks known-vulnerable drivers. It’s helpful, but not as bullet-proof as it sounds. The list is only updated twice a year, and is missing many vulnerable drivers, including this one.

A better, more frequently updated list, is loldrivers.io/drivers/; however, since the vulnerable driver blocklist doesn’t support customization, you’ll need to find other means of blocking these drivers (such as via detection rules, or Windows Defender Application Control, which is described below).

 

Windows Defender Application Control

The best protection, and the one Expel recommends, is Windows Defender Application Control (WDAC). Included in Windows 10 version 1709 and above, WDAC is Windows’s built-in application allow-listing functionality. The feature is not just limited to standard software, but extends to blocking the loading of unapproved kernel drivers as well.

The feature is highly configurable, enabling you to allowlist drivers by name, hash, vendor certificate, and more. WDAC also contains a “learning mode” which builds an allowlist based on the drivers currently used by the system, preventing downtime due to necessary drivers being blocked.

WDAC can be configured to approve all drivers from a specific publisher or vendor certificate, removing the need for having to manually approve new versions of already trusted drivers every time a vendor updates their software. Additionally, you can set a minimum build number, removing an attacker’s ability to downgrade a trusted driver to an older vulnerable version.

When configured well, WDAC can reduce the BYOVD attack surface to near zero. This limits attackers to only being able to target approved drivers, or built-in kernel features. Even the more permissive settings such as enabling blanket approvals for specific publishers or driver names are a massive step-up in security.

 

The last resort

Unfortunately, all of these features do require Windows 10 1709+. For other systems, the best recommendation we can offer is adding ktapi.sys to your security product’s detection ruleset.

Driver: ktapi.sys, SHA-256: 7ee17efef04bb7c9de90d5210263ed6993f867e5a11f86e65e3bb1362c7de237
Exploit: was.exe, SHA-256: c277ae5a4dd62f51de5278790796cd2700de7f77ea17762e97729f27872d076b

Note: the driver was retrieved from a third party source. While it does work with the exploit, we cannot confirm it’s the same version used by the threat actors. Therefore, we do not recommend blocking the driver by hash alone. A filename base rule is likely a better bet, assuming it does not collide with the name of any required drivers on the system.

BYOVD continues to be a huge threat to enterprises, enabling attackers to disable state-of-the-art endpoint security systems in seconds. Even using the latest Windows version, with all exploit mitigations enabled, does not provide complete protection.

This is why Expel is focusing resources on finding and building detections for BYOVD exploits that are being leveraged by threat actors. Additionally, we are also proactively looking for new vulnerable drivers that attackers may abuse in the future, enabling us to deploy proactive detections.

Not only do we intend to keep expanding our detection capabilities, but will also continue to publicly share research on BYOVD exploits and vulnerable drivers we encounter in the wild, enabling the wider security community to better defend against these attacks.

 

Indicators of compromise (IOCs)

IOC Description

ktapi.sys

Vulnerable driver name

was.exe

Exploit payload name

7ee17efef04bb7c9de90d5210263ed6993f867e5a11f86e65e3bb1362c7de237

Vulnerable driver SHA-256

9ca9432b0d29204cb5420a1a6b01533d4552130c2a8a5ecd7837efadefb4a046

Vulnerable driver SHA-256

c277ae5a4dd62f51de5278790796cd2700de7f77ea17762e97729f27872d076b

Exploit payload SHA-256

\Device\ktapi

Vulnerable driver device name

\DosDevice\ktapi

Vulnerable driver DosDevice name

\.\ktapi

User mode device name 

Kontron Technology Application Programming Interface

Vulnerable driver description

KTAPI System Driver

Vulnerable driver product name

11 21 24 89 1F 00 67 8B 52 F9 3B 9C 37 5B D8 91 3A 62

Vulnerable driver vendor’s digital signature serial number