Skip to content

Bit-Manipulation Primitives

Modern kernel mitigations have made function pointer hijacking increasingly difficult. kCFG validates that indirect call targets are legitimate function entries. kCET prevents return address corruption through hardware shadow stacks. HVCI blocks execution from writable memory. These defenses have closed the traditional path from UAF or type confusion to arbitrary code execution in the kernel. But they have not closed the path to arbitrary data modification, because the Windows kernel exports several bitmap manipulation functions that are valid kCFG targets, take a controlled structure as their first argument, and modify memory at addresses derived from that structure.

RtlSetBit, RtlClearBit, RtlSetAllBits, and RtlClearAllBits are the functions that make this possible. After gaining control of an indirect call (through a UAF callback, a corrupted work item, or a hijacked I/O completion routine), the attacker redirects execution to one of these bitmap functions. By controlling the RTL_BITMAP structure pointer passed as the first argument, individual bits or entire memory regions at arbitrary kernel addresses can be set or cleared. The functions pass CFG validation at every indirect call site. No bypass needed. No shellcode. No ROP chain. Just a valid function call that happens to modify memory at a location the attacker controls.

This approach bridges the gap between "I have a controlled indirect call" and "I can modify kernel data structures" without violating any code integrity policy. On HVCI-enabled systems where traditional exploitation paths are blocked, bit-manipulation primitives provide the only viable path from callback hijacking to privilege escalation.

The RTL_BITMAP structure

The bitmap functions all operate on the same simple structure:

typedef struct _RTL_BITMAP {
    ULONG SizeOfBitMap;    // Number of bits in the bitmap
    PULONG Buffer;         // Pointer to the bitmap buffer
} RTL_BITMAP, *PRTL_BITMAP;

To exploit these functions, the attacker places a fake RTL_BITMAP in controlled memory (a sprayed pool object, a user-mapped page, or a region within a corrupted kernel structure) with SizeOfBitMap set large enough to cover the target bit index and Buffer pointing to the target kernel address (ULONG-aligned). When the hijacked callback fires with this fake bitmap as its argument, the bitmap function reads Buffer and modifies the bits at that address.

The fake bitmap structure occupies only 16 bytes (8 for SizeOfBitMap and Buffer on x64), making it easy to embed in spray objects or corrupted structure fields.

The four functions

Each bitmap function provides a different modification granularity, and the choice depends on the exploitation target.

RtlSetBit: enable a single bit

VOID RtlSetBit(PRTL_BITMAP BitMapHeader, ULONG BitNumber);

RtlSetBit sets bit BitNumber within the bitmap buffer. The BitNumber is typically controlled through the second argument to the hijacked callback, or through a field in the structure that passes the bit index. Setting a single bit is enough to enable a specific privilege in a token's Privileges.Enabled bitmask. For example, RtlSetBit(&fakeBitmap, 20) with Buffer pointing at _TOKEN.Privileges.Enabled sets bit 20, which is SeDebugPrivilege. A process with SeDebugPrivilege can open any process with PROCESS_ALL_ACCESS, including SYSTEM processes, and duplicate their tokens.

RtlClearAllBits: zero an entire region

VOID RtlClearAllBits(PRTL_BITMAP BitMapHeader);

RtlClearAllBits zeros SizeOfBitMap / 8 bytes starting at Buffer. This is effectively a controlled memset(Buffer, 0, SizeOfBitMap / 8). Setting SizeOfBitMap to 512 and Buffer to a DACL address zeros 64 bytes, which is enough to wipe the entire ACL structure. The result is a NULL DACL, granting everyone full access to whatever the security descriptor protects.

CVE-2026-21241 used RtlClearAllBits to zero the DACL within SepMediumDaclSd, bypassing NtQuerySystemInformation restrictions and enabling kernel address enumeration from Low integrity. The fake RTL_BITMAP was planted in a sprayed pool object that reclaimed a UAF slot in afd.sys's notification path. See ACL/SD Manipulation for the full technique.

RtlSetAllBits: set all bits in a region

VOID RtlSetAllBits(PRTL_BITMAP BitMapHeader);

RtlSetAllBits sets all bits in the bitmap buffer, effectively filling SizeOfBitMap / 8 bytes with 0xFF. Setting SizeOfBitMap to 64 and Buffer to &token->Privileges.Enabled sets all 64 bits in the enabled privileges bitmask, granting every privilege simultaneously. This is less precise than RtlSetBit but faster when multiple privileges are needed.

The Devcore CVE-2024-35250 writeup (angelboy) demonstrated RtlSetAllBits for token privilege escalation, enabling all privileges in a single callback invocation.

RtlClearBit: disable a single bit

VOID RtlClearBit(PRTL_BITMAP BitMapHeader, ULONG BitNumber);

RtlClearBit clears a single bit, the inverse of RtlSetBit. Use cases include disabling specific integrity checks by clearing flag bits, removing deny ACEs from ACLs, disabling ETW provider enable bits to blind defensive telemetry, and clearing PPL protection bits from _EPROCESS.Protection.

Exploitation targets

The choice of bitmap function depends on the target data structure and the desired modification:

Target Function Effect
_TOKEN.Privileges.Enabled RtlSetBit Enable SeDebugPrivilege (bit 20), SeImpersonatePrivilege, etc.
_TOKEN.Privileges.Enabled RtlSetAllBits Enable all 36 privileges at once
SepMediumDaclSd DACL RtlClearAllBits Zero the DACL, removing NtQuerySystemInformation restrictions
WIL feature flag state RtlSetBit Flip Feature_RestrictKernelAddressLeaks to disable KASLR hardening
_EPROCESS.Protection RtlClearBit Remove PPL protection from a target process
ETW provider enable bits RtlClearBit Disable specific ETW providers to blind defenders

These targets illustrate why bit-manipulation is sufficient for full exploitation. The kernel's access control model relies on small flags, bitmasks, and short integer fields. A single bit in a privilege bitmask separates a restricted user from one who can open any process. A single bit in a DACL separates "access denied" from "access granted." The bit-manipulation primitives are precisely calibrated for this granularity.

Delivery mechanisms

The bitmap functions must be reached through a controlled indirect call. The attacker corrupts a function pointer in a kernel structure so that when the kernel invokes the callback, it calls the bitmap function instead of the original target. Several delivery mechanisms work with kCFG, since the bitmap functions are valid call targets at every indirect call site.

I/O completion packet callbacks are the most commonly used delivery mechanism. The _IO_MINI_COMPLETION_PACKET_USER structure has an ApcContext field that is called as a function pointer during I/O completion dispatch. Setting this to RtlSetBit's address and controlling the surrounding structure fields to provide the fake RTL_BITMAP pointer and bit number as arguments creates a clean, repeatable invocation.

Work item callbacks through _WORK_QUEUE_ITEM provide another path. The WorkerRoutine field is invoked by the kernel's worker thread pool, with Parameter passed as the first argument. Setting WorkerRoutine to a bitmap function and Parameter to a fake RTL_BITMAP pointer triggers the modification when the work item is dequeued.

Timer DPC routines and IRP completion routines offer additional delivery vectors, though they are less commonly used because their argument passing is less convenient for bitmap function exploitation.

The key insight is that all of these delivery mechanisms are legitimate kernel dispatch paths with valid kCFG indirect call sites. The bitmap functions are valid targets at these sites because they are normal exported kernel functions. kCFG does not distinguish between "this callback should only call I/O completion handlers" and "this callback could call any valid function." This semantic gap is what bit-manipulation primitives exploit.

Multiple callbacks for complex exploitation

A single callback invocation provides one bitmap operation. For exploitation scenarios that require multiple modifications (e.g., zeroing a DACL and then setting a privilege bit), the attacker can chain multiple callback invocations. Each invocation targets a different address and bit position, building up the desired set of modifications across several controlled calls.

CVE-2026-21241 demonstrated this chaining: the first callback used RtlClearAllBits to zero SepMediumDaclSd for KASLR bypass, and subsequent callbacks used RtlSetBit for privilege escalation. The UAF was triggered multiple times, each time with a different fake RTL_BITMAP in the reclaimed spray object.

CVE Driver Description
CVE-2026-21241 afd.sys UAF in notification path, NPNX spray, RtlClearAllBits/RtlSetBit for DACL corruption and privilege escalation

AutoPiff Detection

  • spinlock_acquisition_added -- Detects patches that extend lock coverage to prevent UAF conditions that enable callback hijacking
  • added_refcount_guard -- Detects lifetime management fixes that close the callback-abuse window

Mitigations

Kernel Control-flow Enforcement Technology (kCET) uses hardware shadow stacks to prevent return address corruption but does not block forward-edge abuse through valid CFG targets. kCET alone does not mitigate bit-manipulation primitives because the attack does not involve return address manipulation.

kCFG with strict target validation currently validates that the indirect call target is a valid function entry point, but RtlSetBit and the other bitmap functions qualify as valid targets. A future evolution toward type-based CFI, where the call site specifies the expected function signature and only functions with matching signatures are valid targets, would prevent bitmap functions from being called through callback sites that expect a different prototype. Microsoft has discussed type-based CFI but has not deployed it in the kernel.

Kernel Data Protection (KDP) through VBS can protect critical structures (tokens, security descriptors) by marking their pages as read-only through the hypervisor. If the target of a bit-manipulation write is in a KDP-protected page, the write faults instead of succeeding. This is the most effective defense but is not yet applied to all sensitive kernel data.

Secure Pool allocates sensitive structures in VBS-protected pool regions, preventing direct memory corruption. When security descriptors or tokens are allocated in Secure Pool, bitmap function writes cannot reach them.

See Also

  • Pool Spray / Feng Shui -- required to reclaim UAF slot with controlled data containing fake RTL_BITMAP
  • ACL / SD Manipulation -- primary target for RtlClearAllBits in KASLR bypass chains
  • Token Manipulation -- RtlSetBit enables individual privilege bits in token
  • Token Swapping -- alternative privilege escalation approach (replaces entire token pointer)
  • KASLR -- WIL feature flag bypass via RtlSetBit