CVE-2024-26230: Windows Telephony Service - It's Got Some Call-ing Issues (Elevation of Privilege)
2025-1-24 00:0:0 Author: starlabs.sg(查看原文) 阅读量:0 收藏

Executive Summary

CVE-2024-26230 is a critical vulnerability found in the Windows Telephony Service (TapiSrv), which can lead to an elevation of privilege on affected systems. The exploit leverages a use-after-free in FreeDialogInstance. By manipulating the registry, an attacker controls memory allocation to create a fake object, triggering the UAF in TUISPIDLLCallback to gain code execution. This is further chained with techniques to bypass mitigations like CFG and ultimately load a malicious DLL, escalating privileges to SYSTEM via PrintSpoofer. In this blog post, we will take an in-depth look at how this vulnerability works, how it can be exploited, and the mitigation strategies that can help defend against it.

Vulnerability Overview

Affected Products

Product Windows Telephony Service (TapiSrv)
Vendor Microsoft
Security Impact Elevation of Privilege
CVE ID CVE-2024-26230
CWE(s) CWE-416 - Use After Free

CVSS3.1 Score

  • Base Score: 7.8
  • Vector String: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Affected Software

  • Version: Microsoft Windows 10 Version 22H2 Build 19045.3803 AMD64

Description of Vulnerability

The vulnerability exists within the FreeDialogInstance function in C:\Windows\System32\tapisrv.dll. Specifically, it occurs when the function deallocates the GOLD object while the original context handle retains a pointer to it, leading to a use-after-free condition.

Here are the primary reasons for the occurrence of this bug and how it can be exploited:

  1. The FreeDialogInstance function deallocates the GOLD object (identified by the magic number 0x474F4C44) without ensuring that the context handle object no longer references it. This issue is evident at both Line 1 and Line 2.

FreeDialogInstance frees GOLD object

  1. The TUISPIDLLCallBack function attempts to use the freed GOLD object, which is now a dangling pointer. It accesses a virtual function at v12 + 0x20 and subsequently invokes it, triggering the use-after-free vulnerability.

UAF in TUISPIDLLCallBack

In typical use-after-free exploitation scenarios, we seek a suitable object to serve as an exploit primitive. In kernel mode, a variety of objects can be leveraged for this purpose. However, in user mode—where TAPISrv operates as an RPC service—the vulnerability can only be exploited with objects within the same process. As such, without an appropriate object already present in the target process, direct exploitation of the vulnerability is not possible.

In this case, we need to think outside the box. Instead of searching for a suitable object to serve as an exploit primitive, the goal is to find a primitive that allows us to create a memory allocation where both the size and content can be fully controlled. With this approach, we can craft a fake object that can be used to exploit the vulnerability.

One such primitive is a dispatch function named TRequestMakeCall, which opens the registry key Software\\Microsoft\\Windows\\CurrentVersion\\Telephony\\HandoffPriorities and allocates memory to store the key values.

TRequestMakeCall

By inspecting the registry with Registry Editor, we can confirm that this registry key is configured with Full Control permissions for the user.

Since this key is fully user-controlled, it allows us to manipulate the value of the RequestMakeCall key. This gives us control over both the allocated heap size and the content of the memory.

RequestMakeCall registry key privilege

Windows Telephony Service

The Windows Telephony Service is a component of Windows RPC (Remote Procedure Call), allowing us to interact with it as we would with any standard RPC service.

To utilize RPC, some knowledge with Interface Definition Language (IDL) is required.

Interface Definition Language (IDL)

IDL stands for Interface Definition Language, which is used to define the interfaces of an RPC server. It is an attributed programming language and is similar to a C header file. Understanding IDL is important because it helps you know the interfaces that Telephony provides and understand how we can communicate with Telephony services. But don’t worry—it’s quite simple. Let’s take a deep dive together!

The telephony service provides three interfaces, which can be defined as follows:

interface tapsrv {
    typedef [context_handle] void* PCONTEXT_HANDLE_TYPE;

    long ClientAttach(
        [out][context_handle] PCONTEXT_HANDLE_TYPE* pphContext,
        [in] long lProcessID,
        [out]long* phAsyncEventsEvent,
        [in, string] const wchar_t* pszDomainUser,
        [in, string] wchar_t* pszMachine
    );

    void ClientRequest(
        [in] PCONTEXT_HANDLE_TYPE phContext,
        [in, out, length_is(*plUsedSize), size_is(lNeededSize)] unsigned char* pBuffer,
        [in] long lNeededSize,
        [in, out] long* plUsedSize
    );

    void ClientDetach(
        [in, out] PCONTEXT_HANDLE_TYPE* pphContext
    );

};

ClientRequest

The most important interface here, is the ClientRequest. It allows you to communicate, or in other terms, use various functions that Telephony offers in gaFuncs table.

Client Request

gaFuncs in IDA.

gaFuncs table

As you can see, the ClientRequest function takes a buffer, pBuffer, as its second argument. The pBuffer structure is defined as follows:

  • The first element (at index 0) is always the offset to the functions in the gaFuncs table. For example:

    • To invoke GetUIDllName, the offset is 0x1.
    • To invoke TUISPIDLLCallback, the offset is 0x2, and so on.
  • The second element in the buffer represents the first argument for the invoked function. The third element corresponds to the second argument, and so forth.

  • Each element in the buffer occupies 4 bytes, which is equivalent to the size of an integer.

pBuffer

To invoke the GetUIDllName, your pseudo-code would be:

    write_func_offset((void*)buffer, GaFuncs::GetUIDllName); // first one at 0th is always offset of the dispatch functions in gaFuncs table
    write_param((void*)buffer, 1, 1); // param1_GetUIDllName
    write_param((void*)buffer, 2, 3); // param2_GetUIDllName
    write_param((void*)buffer, 5, -1); // param5_GetUIDllName

    LONG byteWritten = (sizeof(buffer) / sizeof(*buffer));
    ClientRequest(context, buffer, 1024, &byteWritten);

Steps to Exploitation

Using the exploit primitive described above, the use-after-free vulnerability can be exploited with the following setup:

  1. Use GetUIDllName to create a GOLD object.
  2. Use FreeDialogInstance to free the GOLD object, thereby creating a dangling pointer.
  3. Manipulate the registry key value for TRequestMakeCall to craft a fake object for the use-after-free scenario.
  4. Finally, invoke TUISPIDLLCallback to trigger the use-after-free on the fake object, taking control of the program’s execution flow.

The visualization of these steps is shown below.

Use After Free visualization

At this stage, we are able to control the RIP (Return Instruction Pointer) to point to any desired location. However, the Control Flow Guard mitigation prevents the creation of a ROP (Return-Oriented Programming) chain in this exploitation attempt. Further information on Control Flow Guard can be found in the following references:

Nevertheless, it remains possible to invoke Win32 functions imported by the binary. For example, I could invoke VirtualAlloc as below.

UAF to invoke VirtualAlloc

The exploitation steps to achieve LoadLibraryW to load our controlled DLL file are as follows:

  • Step 1: Utilize the use-after-free exploit described above to invoke a call to the malloc function. If malloc is executed successfully, it will return a pointer to the allocated address. In TUISPIDLLCallback, the lower 32 bits of the returned value are written into the RPC input buffer and returned to the client. Consequently, this results in a useful information leak.

TUISPIDLLCallback write results to RPC Input Buffer

We successfully leaked lower 32 bit.

Leak lower 32bit

  • Step 2: Utilize the leaked lower 32 bits above to invoke a call to VirtualAlloc, enabling the creation of a Read-Write-Execute (RWX) memory region.

  • Step 3: Copy the DLL path to the previously created RWX memory region using memcpy_s. The copy operation is limited to three characters at a time so we have to repeat this step for a few times.

DLL Path in memory

  • Step 4: Invoke a call to LoadLibrary with the path to our hack.dll (a reverse shell) that was copied into the RWX memory region in Step 3. This will result a shell with NT Authority\Network Service privileges.

Reverse Shell

  • Step 5:: NT Authority/Network Service have SeImpersonate privilege. The SeImpersonate is a Windows privilege that grants a user or process the ability to impersonate the security context of another user or account. This privilege allows a process to assume the identity of a different user, enabling it to perform actions or access resources as if it were that user. However, if not properly managed or granted to unauthorized users or processes, the SeImpersonate can pose a significant security risk. In this case, we can achieve higher privilege by using PrintSpoofer exploit to leverage to NT Authority/System. For further details on PrintSpoofer exploit, see PrintSpoofer

NT Authority/System

CVE-2024-43626 - An Out-of-Bound Write bug in GetPriorityList leads to Information Leak in SetPriorityList

While conducting further research into this n-day vulnerability, my colleague Chen Le Qi discovered an interesting bug in one of the functions used during the exploitation process. Special thanks to him for his invaluable help and for sharing insights during the writing of this blog post.

This bug is located in the GetPriorityList function within tapisrv.dll and allows for an Out-of-Bound Write primitives, and later on, an information leak in SetPriorityLeak. It allows an attacker to leak the address of GOLD objects.

The GetPriorityList function was used to spray the heap during earlier exploitation steps. Below are more details about what it does:

  1. The function invokes RegQueryValueExW to retrieve length of the value from a registry key.

  2. Upon success, it allocates memory on the heap based on the length of the registry key’s value.

  3. It invokes RegQueryValueEx again, gets the value and converts the registry key’s value to uppercase using _wcsupr, and stores the result ins *a3 (the third argument).

The issue occurs in the final step when the function invokes _wcsupr to convert the registry key’s value to uppercase. The _wcsupr function and the GetPriorityList does not verify whether the input string is null-terminated. Consequently, if the registry key’s value is not null-terminated, _wcsupr will read beyond the buffer until it encounters a null byte (\0). This can result in an information leak later on, as the extra data read is stored in *a3 and returned to the caller.

Out-of-Bound Write Bug In GetPriorityList

After the buffer *a3 is returned, it is subsequently used in the SetPriorityList function.

Bug in SetPriorityList

As shown in the image above, the lstrlenW function is called to calculate the length of the lpString that is saved to the registry key. Unfortunately, lstrlenW assumes that the input string is null-terminated. If the string becomes corrupted in GetPriorityList and is then passed to SetPriorityList, it will be saved to the registry key. This behavior can result in an information leak.

There are two code paths that could be used to exploit this vulnerability:

  • ClientAttach -> ClientDetach with RequestMediaCall key.
  • LSetAppPriority with RequestMakeCall key.

Visualization of 02 code path

In recent updates, Microsoft addressed this bug by explicitly null terminating the string, preventing _wcsupr from reading beyond the allocated buffer. This single patch should also address two bugs in both GetPriorityList and SetPriorityList, as it is GetPriorityList that accepts a string without null-termination.

1st Patch In GetPriorityList

This bug has been assigned the name CVE-2024-43626 and got patched in November 12, 2024 Updates. Interestingly, this is not the only issue Microsoft addressed in the patch. If we take a closer look, another patch can be observed at Line 20, where an integer overflow check is implemented on cbData. The cbData variable represents the length of the registry key’s value, as obtained during the initial invocation of RegQueryValueExW within GetPriorityList.

After the cbData value is retrieved, it is used to allocate memory for the registry key’s value by invoking HeapAlloc with a size of cbData + 2. If cbData reach the maximum value of a DWORD, this could result in an integer overflow.

2nd Patch In GetPriorityList

However, I was unable to create a registry key value of such a large size during testing. This limitation exists because the length of a registry key’s value is limited. If anyone has insights into creating large registry key values, please comment down below, I would greatly appreciate your knowledge. Thank you in advance!

Exploit Reproduction (Optional)

  • Start the Telephony service with command sc start Tapisrv. Telephony service is not running by default, but you could start it with normal user privilege. After started, it will run under NT Authority/Network Services privilege.

Start telephony service

  • Verify if Telephony service is running with command sc query Tapisrv

Verify telephony service is running

  • Copy the hack.dll to Desktop under path C:\Users\user01\Desktop\hack.dll. It’s a simple reverse shell, source code could be found in attached Proof of Concept.

  • Run Set_Null_DACL.exe to set DACL of hack.dll to NULL, which means everyone could access. Source code for this program could be found in attached Proof of Concept.

Set DACL of hack.dll to NULL

  • Run the exploit

Run the exploit

  • On attacker machine, run netcat to listen for reverse shell at port 13338 with command nc -nlvp 13338.

Reverse shell with NT/Authority privilege

  • Run whoami /priv to verify that NT Authority/Network Service have SeImpersonate privilege.

SeImpersonate Privilege

  • Using PrintSpoofer exploit to achieve NT Authority/System. For further details, see PrintSpoofer

NT Authority/System

Detection Guidance

Due to the nature of the vulnerability, an attacker needs to start the Telephony service as a normal user and the service could crash during exploitation. Consequently, users should implement measures to prevent normal users from starting the Telephony service or to monitor and log any instances when the service crashes.

Suggested Mitigations

  • Implement restrictions to prevent the normal user to start Telephony Service.

Proof-of-Concept

PoC_CVE_2024_26230.exe (exploit)

CVE-2024-26230 highlights a significant vulnerability within the Windows Telephony Service, presenting a risk for privilege escalation on affected systems. While this vulnerability can be exploited through a series of carefully orchestrated steps, the key to mitigating such risks lies in proactive system hardening. By restricting access to the Telephony Service, monitoring unusual service activity, and ensuring proper security configurations, administrators can significantly reduce the attack surface. As with any critical vulnerability, staying updated with the latest patches and applying security best practices are essential in protecting systems from potential exploits.

Thanks for reading!

Demo

References


文章来源: https://starlabs.sg/blog/2025/cve-2024-26230-windows-telephony-service-its-got-some-call-ing-issues/
如有侵权请联系:admin#unsafe.sh