CVE-2024-26230 — Windows Telephony Service UAF (EoP)

Last updated: 2026-04-11
Severity: High (CVSS 7.8)
Component: tapisrv.dll (Windows Telephony Service RPC)
Bug Class: Use-After-Free
Privilege Escalation: User → SYSTEM (via Network Service + SeImpersonate → PrintSpoofer)
Patch: November 2024 Patch Tuesday
Related: Use After Free, Primitives, Researchers
Tags: uaf, user-mode, rpc, cfg, cfgbypass

Researchers: Đào Tuấn Linh (StarLabs, 2025) and k0shl (Cyber Kunlun) independently discovered and exploited this vulnerability. k0shl’s version specifically addresses XFG (Extended Flow Guard) bypass.

Vulnerability Summary

CVE-2024-26230 is a Use-After-Free in FreeDialogInstance within tapisrv.dll, the Windows Telephony Service RPC server running as NT Authority\Network Service. By controlling a registry key (HandoffPriorities\RequestMakeCall) that governs a heap allocation, an attacker can place a fake object into the freed slot and achieve controlled vtable dispatch, bypassing CFG by invoking only imported Win32 functions. The chain culminates in LoadLibraryW loading an attacker DLL, granting Network Service execution, and then PrintSpoofer escalating to SYSTEM via SeImpersonate.

Root Cause Analysis

FreeDialogInstance frees the GOLD object (identified by magic value 0x474F4C44) without clearing the pointer held by the RPC context handle. The context handle thus retains a dangling pointer to the freed memory.

Later, TUISPIDLLCallBack accesses v12+0x20 on the GOLD object — a virtual function pointer — and calls it. Since v12 now points to freed memory that can be reclaimed and controlled, this is the UAF trigger.

FreeDialogInstance → HeapFree(GOLD object) → context handle still holds ptr
TUISPIDLLCallBack  → call [v12+0x20]        ← attacker controls v12 content

Memory Primitive

The attacker-controlled allocation primitive is TRequestMakeCall, which reads from:

HKCU\Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities\RequestMakeCall

This registry key has Full Control for the current user by default. The function allocates a heap buffer sized and populated from the registry value. By writing to this key before the UAF, the attacker controls the exact size and content of the allocation that reclaims the freed GOLD object slot, creating a fake vtable.

Exploitation Technique

RPC Interface

The Telephony service exposes the tapsrv RPC interface with three methods:

interface tapsrv {
    typedef [context_handle] void* PCONTEXT_HANDLE_TYPE;

    long ClientAttach(
        [out][context_handle] PCONTEXT_HANDLE_TYPE* pphContext, ...);
    void ClientRequest(
        [in] PCONTEXT_HANDLE_TYPE phContext,
        [in, out] unsigned char* pBuffer,
        [in] long lNeededSize,
        [in, out] long* plUsedSize);
    void ClientDetach(
        [in, out] PCONTEXT_HANDLE_TYPE* pphContext);
};

ClientRequest dispatches via gaFuncs table: pBuffer[0] = function offset (1 = GetUIDllName, 2 = TUISPIDLLCallback, etc.). Each element is a 4-byte integer.

XFG Constraint (k0shl’s analysis)

tapisrv.dll enforces XFG (Extended Flow Guard), not just CFG. The vtable dispatch in TUISPIDLLCallBack uses:

call qword ptr [tapisrv!_guard_xfg_dispatch_icall_fptr]
→ ntdll!LdrpDispatchUserCallTarget

XFG validates both (1) that the call target is a valid function entry point in the CFG bitmap AND (2) that the target’s type hash matches the expected type at the call site. This blocks not just arbitrary addresses but also mismatched-type valid functions that would otherwise pass CFG.

However: if the attacker can supply a function whose type hash matches the expected vtable method type, XFG passes. The functions VirtualAlloc, MIDL_user_allocate, memcpy_s, and LoadLibraryW all happen to be valid XFG targets with matching type hashes for this call site. The exploit abuses this by constructing a multi-step chain through these normally-benign functions.

CFG Constraint (general)

tapisrv.dll is also CFG-protected. An indirect call via a corrupted vtable can only target valid CFG targets — in practice, functions imported by the binary. ROP chains are not viable. The attacker is restricted to invoking legitimately-imported Win32 APIs from the controlled vtable pointer.

Exploit Chain (5 Steps)

Step 1 — Heap Address Leak:
Invoke UAF to call malloc. TUISPIDLLCallBack writes the lower 32 bits of the return value into the RPC output buffer and returns it to the client. This leaks a heap address (32-bit pointer in 32-bit tapisrv space), breaking ASLR for subsequent steps.

Step 2 — Allocate RWX Region:
Use the leaked 32-bit base to construct the fake vtable entry pointing to VirtualAlloc. Invoke UAF again with crafted args to call VirtualAlloc(addr, size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE), creating an RWX buffer at a predictable address.

Step 3 — Copy DLL Path:
Use memcpy_s (also an imported function) via repeated UAF invocations, 3 characters at a time, to write the attacker’s DLL path (e.g., C:\Users\user01\Desktop\hack.dll) into the RWX region.

Step 4 — Load Malicious DLL:
Invoke LoadLibraryW on the RWX buffer containing the DLL path. The DLL is loaded into tapisrv.dll’s process context with NT Authority\Network Service privileges.

Step 5 — SeImpersonate → SYSTEM:
NT Authority\Network Service holds SeImpersonate. Abuse via PrintSpoofer (or similar potato exploit) to impersonate SYSTEM and spawn a privileged shell.

Setup Notes

  • The Telephony service (TapiSrv) does not run by default but can be started by any standard user: sc start Tapisrv
  • The hack.dll must have a NULL DACL (world-readable) so the service can load it
  • The service runs under NT Authority\Network Service — 32-bit heap addresses

Key Primitives Used

PrimitiveTechnique
Controlled allocationRegistry key HandoffPriorities\RequestMakeCall (Full Control for user)
UAF triggerFreeDialogInstance + TUISPIDLLCallBack vtable dispatch at v12+0x20
Info leakmalloc return value (lower 32 bits) via RPC output buffer
CFG bypassOnly imported Win32 functions; no arbitrary code ptr needed
Code execLoadLibraryW on attacker-controlled path in RWX region
Privilege escalationSeImpersonate → PrintSpoofer → SYSTEM

XFG Address Prediction Trick (k0shl)

A key challenge: the third argument to VirtualAlloc is flAllocationType (must be MEM_COMMIT|MEM_RESERVE = 0x3000), but in the constrained call, it’s a 64-bit pointer whose low 32 bits the function reads as a DWORD. The attacker cannot directly set the pointer value.

Trick — large allocation size makes heap addresses predictable: When the RPC input buffer size is set to 0x40000000, the low 32 bits of heap allocation addresses increase linearly and predictably from one call to the next.

Step 1: Call MIDL_user_allocate via UAF → returns 64-bit heap addr
        tapisrv writes low 32 bits into RPC output → attacker learns ptr_low32
Step 2: With ptr_low32 known, compute offset such that:
        (ptr + offset) mod 0x100000000 = 0x3000  (MEM_COMMIT|MEM_RESERVE)
        i.e., when ptr_low32 > 0xC0000000, adding offset causes the 32-bit 
        sum to overflow past 0x100000000, leaving low 32 bits = 0x3000
Step 3: Call VirtualAlloc via UAF with: lpAddress=0xBA000000 (predicted 32-bit addr),
        dwSize=3, flAllocationType=ptr+offset (low32=0x3000), flProtect=0x40 (RWX)

This allocates an RWX buffer at a predictable 32-bit address. The return value (low 32 bits) is again returned to the attacker via the RPC output buffer.

WinDbg confirmation (k0shl):

r8d=3000   (flAllocationType = MEM_COMMIT|MEM_RESERVE ✓)
r9d=40     (flProtect = PAGE_EXECUTE_READWRITE ✓)
rcx=ba000000  (lpAddress — target 32-bit range ✓)
rdx=3      (dwSize — minimum, doesn't matter ✓)

Mitigations Bypassed

  • XFG: Bypassed by constructing a call chain through valid XFG-checked function exports (MIDL_user_allocate → VirtualAlloc → memcpy_s → LoadLibraryW) whose type hashes match the vtable call site. No type-hash bitmap corruption needed.
  • CFG: Same as XFG bypass — only legitimately-imported Win32 APIs are called.
  • ASLR: Bypassed via the malloc/MIDL_user_allocate return value leak (lower 32 bits via RPC output buffer).

Bonus: CVE-2024-43626

Discovered by Chen Le Qi (StarLabs) during research into CVE-2024-26230.

Bug: GetPriorityList calls _wcsupr without ensuring the input is null-terminated. If the registry value lacks a null terminator, _wcsupr reads beyond the buffer until it finds \0 — OOB read. The oversized buffer is then passed to SetPriorityList which calls lstrlenW (also null-termination-dependent) and saves the extra data back to the registry.

Result: Information leak — registry key captures kernel/heap addresses. Can be used to leak GOLD object addresses.

Second bug in same patch: cbData + 2 integer overflow in HeapAlloc within GetPriorityList when cbData is DWORD_MAX. Patched by adding overflow check.

Code paths:

  • ClientAttach → ClientDetach with RequestMediaCall key
  • LSetAppPriority with RequestMakeCall key

Patch (Nov 12, 2024 / KB5046613): Explicit null termination added after RegQueryValueExW in GetPriorityList; overflow check on cbData.

Patch Analysis

The UAF fix (CVE-2024-26230) clears the context handle’s GOLD pointer in FreeDialogInstance before/after the free, so TUISPIDLLCallBack can no longer dereference a dangling pointer. Patched in November 2024 Patch Tuesday.

Detection

  • Monitor for normal users starting the Telephony service (sc start Tapisrv)
  • Alert on tapisrv.exe service crashes (crash during heap grooming is expected)
  • Monitor suspicious LoadLibraryW calls from svchost.exe processes hosting telephony

References

  • Đào Tuấn Linh, “CVE-2024-26230: Windows Telephony Service - It’s Got Some Call-ing Issues”, StarLabs, 2025-01-24
  • k0shl (Cyber Kunlun), “A trick, the story of CVE-2024-26230”, whereisk0shl.top — XFG bypass via VirtualAlloc + address prediction trick
  • Chen Le Qi — CVE-2024-43626 discovery (bonus bug in same function)
  • PrintSpoofer: https://github.com/itm4n/PrintSpoofer
  • PoC: https://github.com/star-sg/CVE/tree/master/CVE-2024-26230