core-jmp core-jmpdeath of core jump

SakDriver: Reversing a Windows Kernel Driver Rootkit

A reverse-engineering walkthrough of SakDriver, a Windows kernel-mode rootkit first mistaken for a Cobalt Strike Beacon. It patches ETW, hides processes via DKOM, masquerades as a minifilter and a fake Microsoft service, and — most notably — receives C2 commands through the Windows registry. Includes the full recovered command table, IOCs and a matching YARA figure.

oxfemale July 29, 2026 13 min read 79 reads
Export PDF
SakDriver: Reversing a Windows Kernel Driver Rootkit
Original text: “SakDriver: Reversing a Kernel Driver Rootkit”0xSec, 0xsec.gitbook.io. Disassembly screenshots, the command-ID table, indicators of compromise and the YARA figure below are reproduced with attribution captions.
SakDriver: Reversing a Kernel Driver Rootkit
The SakDriver rootkit — a malicious Windows kernel driver dissected in this analysis. Source: original article.

Executive Summary

What began as a routine look at a “Cobalt Strike Beacon” sample turned out to be something far more dangerous: a full Windows kernel-mode rootkit that loads as a driver. Internally named SakDriver, the sample operates from Ring 0, where it blinds Event Tracing for Windows (ETW), hides and unhides processes through Direct Kernel Object Manipulation (DKOM), conceals its command-and-control (C2) ports by hooking the NSI driver, and manipulates the Windows Filtering Platform (WFP) to block security products from reaching the attacker’s infrastructure. It even probes the hypervisor time-stamp counter to sense when it is running inside a sandbox.

This walkthrough follows the driver from DriverEntry through its installation, persistence, self-defense and C2 logic. The most notable design decision is its covert control channel: instead of opening a socket, a user-mode agent writes attacker commands into an ordinary registry value, and the driver’s registry callback picks them up — a technique that sails past most endpoint telemetry. A recovered command dispatch table exposes more than two dozen kernel primitives, ranging from arbitrary physical-memory read/write and process injection to hardware-input spoofing and reflective PE loading. The relevant indicators of compromise and a matching YARA figure are included at the end.

Technical Analysis

Cobalt Strike is a commercial penetration-testing platform whose “Beacon” agent gives an operator command execution, keylogging, file transfer, SOCKS proxying, privilege escalation, credential theft, port scanning and lateral movement. Beacon is largely in-memory and file-less: it is stageless or multi-stage shellcode that, once loaded, reflectively maps itself into a process without touching disk, and it supports C2 over HTTP, HTTPS, DNS, SMB named pipes and forward/reverse TCP. Because it is stable, well written and highly customisable, Beacon is popular with both targeted attackers and criminal crews — which is exactly why a sample that looked like Beacon was worth a second look.

Initial triage told a different story. The usual first-pass tooling revealed that this was not a user-land Beacon at all but a kernel rootkit that loads as a driver. Recovered debug strings alone give away much of the design — references to k_hook::EventTraceControl, the syscall table, GetCpuClock and HvlGetQpcBias point straight at ETW/syscall hooking and hypervisor time manipulation.

Recovered debug strings and hook names from the SakDriver sample
Debug strings recovered from the binary — ETW control hooks, syscall-table references and hypervisor QPC (HvlGetQpcBias) tampering. Source: original article.

At a high level the driver actively hooks and patches the ETW (Event Tracing for Windows) structures in the kernel, blinding the defensive stack before an event is ever logged. It hides and reveals processes through DKOM, conceals its C2 port with an NSI_HIDE_PORT routine, and unblocks its own traffic with WFP_UNBLOCK_IP by driving the Windows Filtering Platform — the architecture that underpins the Windows firewall.

SakDriver capability overview in the disassembler
The rootkit’s advertised capabilities: ETW patching, DKOM process hiding, NSI port hiding and WFP-based IP unblocking. Source: original article.

There is also logic tied to the hypervisor time-stamp counter, most likely a sandbox check that fires when the rootkit suspects it is being detonated in an analysis environment. The driver identifies itself as SakDriver, and a leftover PDB path points at the author’s build tree: E:\tools\oldfilelaoda\x64\Release\CrackerDrv.pdb.

PDB path string CrackerDrv.pdb
The embedded PDB path leaking the internal project name CrackerDrv. Source: original article.
DriverEntry routine loaded in IDA
The sample opened in IDA, ready to analyse the driver’s entry point. Source: original article.

Execution starts, as with every driver, in DriverEntry — the routine the I/O manager calls for each installed and loaded driver. It receives a DriverObject, the structure the I/O manager creates to hold entry points for driver routines such as DriverEntry, Unload and the MajorFunction table, along with details like the driver name.

DriverObject usage inside DriverEntry
Early DriverEntry code working with the DriverObject. Source: original article.
DriverEntry continued
Continuation of the entry routine. Source: original article.

Further in, the driver walks the DriverSection member — a pointer to a _KLDR_DATA_TABLE_ENTRY that describes the loaded module in kernel space — sets its unload routine to DriverUnload, and then reads the DLL name from offset 0x48.

DriverSection and _KLDR_DATA_TABLE_ENTRY handling
Parsing the _KLDR_DATA_TABLE_ENTRY and wiring up DriverUnload. Source: original article.
Reading the module name from offset 0x48
Loading the DLL/module name from offset 0x48 of the loader entry. Source: original article.

If the driver is already present on the system, it iterates over FullDllName->Buffer looking for the string \SystemRoot\System32\Drivers using a hand-rolled loop rather than the documented kernel APIs, and searches for its own driver name — a small but deliberate step to avoid the API calls a monitoring product would expect to see.

Custom loop scanning FullDllName buffer
A custom character loop scanning FullDllName->Buffer for \SystemRoot\System32\Drivers instead of calling kernel string APIs. Source: original article.

It then calls a routine that takes the driver name and opens a handle to \Registry\Machine\System\CurrentControlSet\Services (which stores per-service configuration). It allocates a non-paged, no-execute pool region of 0x218 bytes with the tag CvrD, iterates over every subkey under Services, and fills that pool region with KEY_BASIC_INFORMATION records.

Enumerating the Services registry key with pool tag CvrD
Enumerating CurrentControlSet\Services into a non-paged pool allocation tagged CvrD. Source: original article.

Next it opens a handle to the intended driver, queries its ImagePath (the on-disk location of the driver file) and stashes the path in a global. It then registers as a minifilter driver — the driver class used to monitor, intercept and modify file-system I/O — and installs IRP_MJ_DIRECTORY_CONTROL as a major-function handler so it can police directory-enumeration requests and hide its files from anti-virus scans.

Minifilter registration and IRP_MJ_DIRECTORY_CONTROL hook
Registering as a minifilter and claiming IRP_MJ_DIRECTORY_CONTROL to intercept directory I/O. Source: original article.
OS fingerprinting and C2 POST request
Building the victim fingerprint and the outbound C2 request. Source: original article.

The driver then fingerprints the victim’s operating system and prepares an HTTP POST to its C2 server at 43.160.247.24, hitting the /api/event.php endpoint with the distinctive user-agent SakDriver 1.0.

Driver install paths and sak_XXX.sys naming
Installation paths and the randomised sak_XXX.sys file name. Source: original article.
Service registration as Microsoft Player Service
Registering the malicious service under a benign display name. Source: original article.

On a fresh installation it drops the driver to one of the paths \Device\HarddiskVolume%lu\Windows\System32\drivers\DRIVER_NAME or \??\C:\Windows\System32\drivers\DRIVER_NAME, naming the file sak_XXX.sys where XXX is a random seed derived from XORs of a memory region, the performance-counter frequency, the PID and the TID. It registers the service as msXXXX under \Registry\Machine\System\CurrentControlSet\Services\SERVICE_NAME, using the display name Microsoft Player Service and the description Provides support for %04X operations to blend in with legitimate Windows services. If something goes wrong it falls back to registering itself as SakDriver (not exactly OPSEC-friendly). When everything succeeds it reports an install_success marker to the C2 and deletes the dropper.

Service registration continued
The service-registration routine masquerading as a Microsoft service. Source: original article.
Resolving ntoskrnl.exe and building a private IAT
Locating ntoskrnl.exe and constructing a private import table in memory. Source: original article.

To call kernel APIs without leaving an obvious import table, the driver queries the base address of ntoskrnl.exe and builds its own Import Address Table in memory. API names are de-obfuscated and resolved at runtime by sub_14000E34C, which takes the kernel base and a decrypted API name and manually parses the PE header to find the Export Directory, resolving each address by hand. Besides hiding the driver’s intentions, this defeats an EDR’s static import scanning.

Manual PE export-directory parsing
Manually walking the PE Export Directory of ntoskrnl.exe to resolve API addresses. Source: original article.
CmRegisterCallbackEx registration
Registering a registry-operation callback with CmRegisterCallbackEx. Source: original article.

The driver then calls CmRegisterCallbackEx to install a registry callback that runs on every registry operation performed by any thread. Named Function, this callback does double duty. First, it is a self-defense mechanism: if an anti-virus product tries to modify the rootkit’s registry keys, the callback intercepts the operation and returns STATUS_ACCESS_DENIED, aborting the change.

Registry callback denying modification with STATUS_ACCESS_DENIED
The callback returning STATUS_ACCESS_DENIED to block tampering with the rootkit’s keys. Source: original article.

Here is the clever part: the author repurposes that same registry callback as a covert C2 channel. A user-mode agent simply writes a command into a benign registry value using ordinary user-land APIs — an action that generates no suspicious kernel calls and slips past EDR — and the driver reads it back inside the callback. For every write, the driver checks for the magic bytes 0x2625B7146B and, when they are present, extracts the embedded command. The recovered dispatch table is reproduced below.

Command IDWhat it does
22661Arbitrary read/write of process memory (attaches a kernel thread to the target’s address space, locks and maps the memory into the kernel, then reads or writes it).
13592Performs a KVA-shadowing bypass to inject into and steal information from a user-land process.
13129Allocates memory in the victim process to stage process injection.
13128Frees the previously allocated memory after the injected thread finishes.
12937Copies data between two different processes (i.e. the rootkit and a user-land process).
10246Attaches to a user-land process and parses a DLL to locate exported API addresses (e.g. VirtualAlloc).
9072Forcefully deletes protected or currently-executing files.
8784Kernel-to-user-mode DLL injection via kernel APC queuing (parses the PEB to find kernel32.dll and resolves LoadLibrary).
8306Defensive routine: sets a callback on OpenProcess/OpenThread and strips rights such as PROCESS_TERMINATE and PROCESS_QUERY_INFORMATION from handles opened against the rootkit; it can also elevate low-privilege access to PROCESS_ALL_ACCESS (specifically for v8 — purpose still to be determined).
16964Queries memory for basic information about a region.
22323Reads/writes any physical memory address by resolving its virtual pointer — building a custom page table with RW permissions and injecting it into a free PML4 slot — and can copy data directly from a physical address.
13831Resolves a PID from a process name.
13664Spoofs hardware input, forging mouse or PS/2 keyboard/mouse packets and invoking the callback with the fabricated input (\Driver\mouhid and \Driver\i8042prt).
37720Reads a process’s memory by querying the PFN database and mapping the addresses.
39029Changes the protection of a page.
38536Returns a process’s section base address from its EPROCESS structure.
38435Walks a process PEB to loop through loaded DLLs, matches an attacker-supplied name (e.g. kernel32.dll) and returns the DLL base address.
37973Extracts a driver name.
29557De-obfuscates APIs and resolves their addresses, then applies system-wide hooks on CKCL, ETWP, the syscall table, HvlGetQpcBias and GetCpuClock; otherwise it scans win32kfull.sys for a specific pattern in the text section to locate an undocumented function.
29556Spoofs hardware input as keyboard or PS/2 keyboard/mouse packets and invokes the callback (\Driver\kbdhid and \Driver\i8042prt).
25736Performs hardware spoofing for the disk, NVIDIA GPU and SMBIOS.
25897Executes a user thread running the attacker’s DLL / malicious function.
29462Reflective PE loading.
Recovered SakDriver command dispatch table. Source: original article.
Registry service-key comparison code in IDA
Comparing service-key value names (Type, Start, ImagePath, …) as part of the registry stealth layer. Source: original article.

Beyond the command handlers, the callback applies a stealth layer over the registry so that any read or write against the driver’s service key by an AV/EDR is intercepted. Returning to the main entry routine, the driver then performs network filtering, blocking requests made by security solutions to its C2 IP and port, and hides a set of ports — 6891, 12341, 12342, 12343, 6543, 7543 and 9199 — by hooking the NSI driver.

WFP device object creation and IP/domain blocking
Creating a WFP device object and adding just-do-it.icu and 91.99.165.207 to the block table. Source: original article.

Finally it attempts to create a WFP device object so it can observe the network stack, adding just-do-it.icu and 91.99.165.207 to its blocking table — ensuring defensive tooling cannot reach the attacker’s infrastructure while the rootkit’s own traffic flows freely.

Indicators of Compromise (IOCs)

  • Domains / IPs: just-do-it.icu, 91.99.165.207, 43.160.247.24 — at the time of the original analysis these were not yet flagged on VirusTotal and the infrastructure was still live.
  • Hidden ports: 6891, 12341, 12342, 12343, 6543, 7543, 9199
VirusTotal result for 43.160.247.24
VirusTotal: the C2 IP 43.160.247.24 (Tencent, SG) shown with a clean community score at analysis time. Source: original article.
VirusTotal result for the second C2 indicator
VirusTotal reputation for a second piece of the C2 infrastructure. Source: original article.
VirusTotal result for the third C2 indicator
VirusTotal reputation for a third C2 indicator — still unflagged at the time. Source: original article.
  • Endpoint: /api/event.php
  • SHA-256: 4e95aba17c1a423cda5cc9f9f04f7cf8db17e294eb31ed1aa85063601b82fe8d
  • Driver / service names: SakDriver, sak_xxx.sys, ms_xxxx
  • User-agent: SakDriver 1.0

YARA Rule

A YARA rule keyed to the sample matches the analysed hash, confirming detection of the SakDriver rootkit.

YARA rule matching the SakDriver sample
A YARA rule (cobalt_strike / SakDriver_Rootkit) matching the sample hash 4e95aba1…b82fe8d. Source: original article.

Key Takeaways

  • A sample that fingerprints as “Cobalt Strike” can actually be a kernel-mode rootkit — triage the load type, not just the strings.
  • SakDriver blinds defenses at the source: it patches ETW, hooks the syscall table, and tampers with hypervisor QPC timing to frustrate sandboxes.
  • Its registry-callback C2 channel is the standout technique — user-mode writes a benign registry value guarded by the magic bytes 0x2625B7146B, and the driver executes the command with zero network syscalls from the agent.
  • It hides on disk as a minifilter (IRP_MJ_DIRECTORY_CONTROL), hides its ports by hooking NSI, and blocks defensive traffic through WFP.
  • API resolution is done by hand-parsing ntoskrnl.exe exports, defeating static import-based detection.
  • The command table exposes powerful primitives: arbitrary physical memory R/W via a rogue PML4 entry, DKOM, process injection, hardware-input spoofing, hardware ID spoofing, and reflective PE loading.
  • OPSEC slips — the CrackerDrv.pdb path and the SakDriver fallback service name — give defenders concrete hunting artifacts.

Defensive Recommendations

  • Enforce driver-signing and blocklists: enable HVCI/Memory Integrity and Microsoft’s vulnerable-driver blocklist so unsigned or known-abusable drivers cannot load in the first place.
  • Don’t rely solely on ETW: because this rootkit patches ETW in kernel, corroborate with sensors it cannot easily reach — hypervisor-based telemetry, network egress monitoring and periodic integrity scans of ETW structures and the syscall table.
  • Watch registry-callback and minifilter registration: alert on unexpected CmRegisterCallbackEx and minifilter altitudes, and on new CurrentControlSet\Services entries with generic display names such as Microsoft Player Service.
  • Hunt the IOCs: block and alert on 43.160.247.24, 91.99.165.207 and just-do-it.icu; look for the /api/event.php endpoint and the SakDriver 1.0 user-agent; hash-hunt 4e95aba1…b82fe8d.
  • Flag hidden ports: reconcile kernel-reported connections against an out-of-band view; discrepancies around ports 6891/12341-12343/6543/7543/9199 can reveal NSI hooking.
  • Deploy the YARA rule across endpoints and your sample pipeline to catch the driver on disk and in memory.
  • Monitor for physical-memory abuse: unexpected PML4 modifications or raw physical R/W are strong signals of a kernel implant.
  • Baseline loaded drivers: alert on new sak_*.sys / ms_* drivers and on drivers carrying leftover PDB paths like CrackerDrv.pdb.

Conclusion

SakDriver is a compact but capable Windows kernel rootkit that trades noisy user-land tradecraft for quiet, Ring-0 subversion: it silences ETW, hides itself as a minifilter and a fake Microsoft service, resolves its APIs by hand, defends its own registry keys, and — most memorably — takes its orders through registry writes rather than the network. Its de-obfuscated command table reads like a toolkit for total host control. For defenders, the lesson is layered visibility: assume kernel-level telemetry can be blinded, watch the callback and filesystem-filter surfaces the rootkit depends on, and turn the author’s OPSEC mistakes — the PDB path, the fallback service name, the live infrastructure — into detections.

Original text: “SakDriver: Reversing a Kernel Driver Rootkit” by 0xSec at 0xsec.gitbook.io.

oxfemale Vulnerability research, reverse engineering, and exploit development.
// Discussion