(CVE-2024-43626) Windows Telephony Service Heap Out-of-Bounds Read/Write Leading to Elevation of Privilege
CVE: CVE-2024-43626
Affected Versions: Windows 10 (1507, 1607, 1809, 21H2, 22H2); Windows 11 (22H2, 23H2, 24H2); Windows Server 2008 and later
CVSS3.1: 7.8 (High) — CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Summary
| Product | Windows Telephony Service (tapisrv.dll) |
|---|---|
| Vendor | Microsoft |
| Severity | High — a local unprivileged attacker may exploit this to elevate privileges to SYSTEM |
| Affected Versions | Windows 10 (1507, 1607, 1809, 21H2, 22H2); Windows 11 (22H2, 23H2, 24H2); Windows Server 2008 and later |
| Tested Versions | Windows 11 23H2 (Build 22631.3593) |
| CVE Identifier | CVE-2024-43626 |
| CVE Description | Improper input validation in Windows Telephony Server in Microsoft Windows may allow an unprivileged attacker to execute code as SYSTEM |
| CWE Classification(s) | CWE-122: Heap-based Buffer Overflow |
CVSS3.1 Scoring System
Base Score: 7.8 (High)
Vector String: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
| Metric | Value |
|---|---|
| Attack Vector (AV) | Local |
| Attack Complexity (AC) | Low |
| Privileges Required (PR) | Low |
| User Interaction (UI) | None |
| Scope (S) | Unchanged |
| Confidentiality (C) | High |
| Integrity (I) | High |
| Availability (A) | High |
Product Background
The Windows Telephony Service is a local service running on Windows by default, with its main logic implemented in tapisrv.dll inside a svchost.exe process. It exposes three RPC interfaces accessible to unprivileged clients:
[
uuid(2f5f6520-ca46-1067-b319-00dd010662da),
version(1.0),
]
interface DefaultIfName
{
long Proc0_ClientAttach(
handle_t hBindingHandle,
[out][context_handle] void **pCtxHandle,
[in]long lProcessID,
[out]long *phAsyncEventsEvent,
[in][string]wchar_t *pszDomainUser,
[in][string]wchar_t *pszMachine);
void Proc1_ClientRequest(
handle_t hBindingHandle,
[in][context_handle] void *CtxHandle,
[in][out][size_is(BufferSize)][length_is(*InputOutputSize)]char *Buffer,
[in] long BufferSize,
[in][out] long *InputOutputSize);
void Proc2_ClientDetach(
handle_t hBindingHandle,
[in][out][context_handle] void **pCtxHandle);
}
ClientAttach() retrieves a context handle, which is passed to ClientRequest() to perform operations. ClientRequest() is a dispatcher that invokes sub-functions based on an opnum.
Technical Details
Opnum 69 corresponds to LSetAppPriority(), which has a path into GetPriorityListTReqCall(). This function opens the HandOffPriorities registry key in the current user hive and passes it to GetPriorityList():
void __fastcall GetPriorityListTReqCall(wchar_t **pOutput)
{
if ( !RegOpenCurrentUser(0xF003Fu, &hkCU) )
{
if ( !RegOpenKeyExW(hkCU,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Telephony\\HandoffPriorities",
0, 0x20019u, &hkHandOffPriority) )
{
GetPriorityList(hkHandOffPriority, L"RequestMakeCall", pOutput);
RegCloseKey(hkHandOffPriority);
}
RegCloseKey(hkCU);
}
}
Inside GetPriorityList(), the registry value is read via RegQueryValueExW() — first with a null output buffer to obtain the data size, then into a heap buffer of cbData + 2 bytes. The data is passed directly to _wcsupr(), an unsafe string function that depends on a null terminator to determine the string’s length:
void __fastcall GetPriorityList(HKEY hKey, LPCWSTR RequestMediaCallName, wchar_t **pOutput)
{
if ( RegQueryValueExW(hKey, RequestMediaCallName, 0, &Type, 0, &cbData) || !cbData )
{
*pOutput = 0;
}
else
{
v6 = HeapAlloc(ghTapisrvHeap, HEAP_ZERO_MEMORY, cbData + 2);
v7 = (wchar_t *)v6;
if ( v6 )
{
*(_WORD *)v6 = '"';
if ( !RegQueryValueExW(hKey, RequestMediaCallName, 0, &Type, (LPBYTE)v6 + 2, &cbData) )
{
_wcsupr(v7); /* unsafe — no null terminator validation */
*pOutput = v7;
}
}
}
}
Since an unprivileged user controls the contents of the RequestMakeCall registry value under their own hive, they can write a value without a null terminator. When _wcsupr() processes this value it continues past the end of the allocated heap chunk into the adjacent chunk, corrupting data belonging to it. By shaping the heap so that a useful structure borders the allocation, an attacker can corrupt fields such as a “buffer size used” counter or a pointer to hijack execution.
Additionally, LSetAppPriority() calls SetPriorityList() to write the processed value back to the registry:
LSTATUS __fastcall SetPriorityList(HKEY hKey, LPCWSTR lpValueName, LPCWSTR lpString)
{
if ( !lpString )
return RegDeleteValueW(hKey, lpValueName);
v7 = lstrlenW(lpString);
return RegSetValueExW(hKey, lpValueName, 0, 1u, (const BYTE *)lpString + 2, 2 * v7);
}
lstrlenW() similarly depends on the null terminator, so it reads past the end of the allocation into the adjacent chunk and writes that data — including heap pointers — into the registry value. By querying this value, an attacker can leak heap addresses from the svchost.exe process, breaking ASLR and heap randomisation.
The _wcsupr() out-of-bounds write can be avoided during the information leak phase by starting the payload with a non-ASCII character, since _wcsupr() stops when it encounters a character outside the ASCII range.
The recommended fix is to replace RegQueryValueExW() with RegGetValueW(), which enforces the data type and automatically appends null terminators for string values.
The following screenshots demonstrate the heap pointer leak in action:


Credit
Chen Le Qi and Nguyễn Đăng Nguyễn of STAR Labs SG Pte. Ltd.
Timeline
- 2024-11-12 — CVE-2024-43626 published; patch released via Microsoft Security Response Center