報告編號:B6-2020-101901
報告來源:360-CERT
報告作者:360-CERT
更新日期:2020-10-19
0x01 前言
1.1 環境搭建
(1)攻擊機環境:Ubuntu 20.04
安裝scapy
sudo apt install Python-pip3
sudo pip3 install scapy
(2)受害機環境:windows 10 1909 x64
tcpip.sys 驅動版本:10.0.18362.476
(3)雙機調試Windows 驅動:
https://blog.csdn.net/qq_21000273/article/details/52027708
斷點:
bp tcpip!Ipv6pUpdateRDNSS
bp tcpip!Ipv6pHandleRouterAdvertisement
bp tcpip!Ipv6pHandleRouterAdvertisement+0xae4cc
bp tcpip!Ipv6pHandleRouterAdvertisement+0xae4db
bp tcpip!Ipv6pUpdateRDNSS+0x99
bp tcpip!Ipv6pUpdateRDNSS+0xca
1.2 背景知識
涉及的包類型:
type:24 Route Information Option
type:25 Recursive DNS Server Option
type:134 IMCPv6 Router Advertisement
涉及的結構:
_MDL結構
typedef __struct_bcount (Size ) struct _MDL {
struct _MDL *Next ;
CSHORT Size ;
CSHORT MdlFlags ;
struct _EPROCESS *Process ;
PVOID MAppedSystemVa ;
PVOID StartVa ;
ULONG ByteCount ;
ULONG ByteOffset ;
} MDL , *PMDL ;
_NET_BUFFER 結構
typedef struct _NET_BUFFER {
union {
struct {
PNET_BUFFER Next;
PMDL CurrentMdl;
ULONG CurrentMdlOffset;
union {
ULONG DataLength;
SIZE_T stDataLength;
};
PMDL MdlChain;
ULONG DataOffset;
};
SLIST_HEADER Link;
NET_BUFFER_HEADER NetBufferHeader;
};
USHORT ChecksumBias;
USHORT Reserved;
NDIS_HANDLE NdisPoolHandle;
PVOID NdisReserved[2];
PVOID ProtocolReserved[6];
PVOID MiniportReserved[4];
NDIS_PHYSICAL_ADDRESS DataPhysicalAddress;
union {
PNET_BUFFER_SHARED_MEMORY SharedMemoryInfo;
PSCATTER_GATHER_LIST ScatterGatherList;
};
} NET_BUFFER, *PNET_BUFFER;
相關函數:
NdisGetDataBuffer 函數
PVOID NdisGetDataBuffer(
PNET_BUFFER NetBuffer,
ULONG BytesNeeded,
PVOID Storage,
UINT AlignMultiple,
UINT AlignOffset
);
NetBuffer:指向NET_BUFFER 結構的指針
BytesNeeded:請求的連續數據的字節數
Storage:指向緩沖區的指針,如果調用者未提供緩沖區,則為NULL。緩沖區的大小必須大于或等于BytesNeeded中指定的字節數。如果此值為非NULL,并且請求的數據不連續,則NDIS將請求的數據將復制到Storage指向的地址。
Windows通過Ipv6pHandleRouterAdvertisement 函數處理 IPv6 路由器通告數據,在該函數中調用 NdisGetDataBuffer 函數從 NET_BUFFER 結構中訪問連續或不連續的數據,通過 NET_BUFFER ->CurrentMdlOffset 字段來記錄要訪問數據起始地址相對于_MDL->MappedSystemVa 的偏移。
0x02 漏洞分析
2.1 漏洞背景
2020年10月14日,360CERT監測發現 Microsoft 發布了 TCP/IP遠程代碼執行漏洞 的風險通告,該漏洞是由于Windows TCP/IP堆棧 在處理IMCPv6 Router Advertisement(路由通告)數據包時存在漏洞,遠程攻擊者通過構造特制的ICMPv6 Router Advertisement(路由通告)數據包 ,并將其發送到遠程Windows主機上,可造成遠程BSOD,漏洞編號為CVE-2020-16898。
2.2 漏洞成因
根據rfc5006 描述,RDNSS包的length應為奇數,而當攻擊者構造的RDNSS包的Length為偶數時,Windows TCP/IP 在檢查包過程中會根據Length來獲取每個包的偏移,遍歷解析,導致對 Addresses of IPv6 Recursive DNS Servers 和下一個 RDNSS 選項的邊界解析錯誤,從而繞過驗證,將攻擊者偽造的option包進行解析,造成棧溢出,從而導致系統崩潰。
RDNSS Option 數據包格式如下:
Type: 占8-bit,RDNSS 的類型為25
Length:8-bit無符號整數,單位長度為8個字節,所以Type, Length, Reserved, Lifetime一共占8個字節,一個單位長度,而一個IPv6地址占16個字節,兩個單位長度,所以Length的最小值為3,且為奇數。
Reserved:保留字段
Lifetime:32-bit無符號整數,存活周期。
Addresses of IPv6 Recursive DNS Servers:保存RNDSS的IPv6地址,每個占16個字節,地址的數量會影響Length字段,number=(Length - 1) / 2。每增加一個地址,Length加2。
漏洞點存在于tcpip.sys -> Ipv6pHandleRouterAdvertisement 函數
漏洞調用鏈為:Icmpv6ReceiveDatagrams -> Ipv6pHandleRouterAdvertisement -> Ipv6pUpdateRDNSS
Ipv6pHandleRouterAdvertisement 函數存在兩個循環,第一個循環遍歷所有headers,做一些基本的驗證,如length的大小,第二個循環用于處理包,并且該階段不再驗證,兩個循環的偽代碼如下:
// 循環1
while ( 1 )
{
……
v28 = (KIRQL *)NdisGetDataBuffer(v9, 2u, v182, 1u, 0);
v27 = v9->DataLength;
actual_length_bytes = 8 * v28[1];
……
switch ( v25 )
{
case 0x18u: // case 0x18 (ICMPv6NDOptRouteInfo)
……
if ( actual_length_bytes > 0x18u
|| (v144 = *((_BYTE *)NdisGetDataBuffer(v9, actual_length_bytes, v220, 1u, 0) + 2), v144 > 0x80u)
|| v144 > 0x40u && actual_length_bytes < 0x18u // <-----【1】驗證實際字節數,不能大于0x18
|| v144 && actual_length_bytes < 0x10u )
{
*a3 = 24;
goto LABEL_275;
}
break;
case 0x19u: // case 0x19 (ICMPv6NDOptRDNSS) // <-----【2】
if ( (*(_BYTE *)(v11 + 404) & 0x40) != 0 && actual_length_bytes < 0x18u )
*a3 = 25;
break;
}
……
if ( actual_length_bytes )
{
v31 = actual_length_bytes + v9->CurrentMdlOffset;
if ( v31 >= *(_Dword *)(v9->Link.Region + 0x28) )
{
NdisAdvanceNetBufferDataStart(v9, actual_length_bytes, 0, 0i64);// <---actual_length_bytes=4*8=0x20
}
else
{
v9->DataOffset += actual_length_bytes;
v9->DataLength -= actual_length_bytes;
v9->CurrentMdlOffset = v31; // 更新CurrentMdlOffset
}
}
v21 += actual_length_bytes;
}
……
// 循環2
while ( 1 )
{
……
if ( *v75 == 0x18 ) // case 0x18 (ICMPv6NDOptRouteInfo)
{
……
v153 = (unsigned __int8 *)NdisGetDataBuffer(NetBuffer_1, actual_option, Storage_1, 1u, 0); // <--- 【3】
v225 = _mm_load_si128((const __m128i *)&_xmm);
v174 = v225.m128i_u32[((unsigned __int64)v153[3] >> 3) & 3];
……
}
if ( *v75 == 0x19 ) // case 0x19 (ICMPv6NDOptRDNSS)
{
if ( (*(_BYTE *)(v11 + 0x194) & 0x40) != 0 )
{
Ipv6pUpdateRDNSS(v11, NetBuffer_1, Buf2, v189, &v170); // <---- 【4】
goto LABEL_309;
}
}
else if ( *v75 == 31 && (*(_BYTE *)(v11 + 404) & 0x40) != 0 )
{
Ipv6pUpdateDNSSL(v11, NetBuffer_1, Buf2, (unsigned int)v189, &v170);
LABEL_309:
v77 = v166;
goto LABEL_118;
}
……
}
第一個循環用于驗證各個header的有效性,首先獲取第一個option包,length為0x4,實際字節數為length*8=0x20個字節,首先更新_net_buffer結構,根據實際字節數計算option的偏移,解析到后面的option2,后面依次根據option.length 解析到option3, option4 ……
所以并沒有處理'x18x22',因此繞過了【1】處case:0x18中對length的驗證。所以如果Option1.length設為3時,構造圖中的包由于長度0x22校驗不通過,會當成無效包被舍棄。
第二個循環處理各個option包,但在case:0x19 的Ipv6pUpdateRDNSS函數中,計算ipv6地址個數是通過(length-1)/2 ,這樣導致length=0x4時和length=0x3時計算的結果一樣,都是根據ipv6地址個數*8+8(Type/Length/ Reserved/Lifetime),所以跳過0x18個字節解析到'x18x22',將其當成了type=0x18的option包,并且沒有了長度的驗證。
綜上,漏洞是由于檢查和解析包時根據length計算的偏移不同,導致繞過檢查,解析到攻擊者偽造的option包,造成棧溢出。
0x03 漏洞利用
具體調試利用過程如下:
(1)首先進入循環1中的【2】處,判斷length實際字節數是否小于0x18,然后調用NdisAdvanceNetBufferDataStart,更新_NET_BUFFER結構,得到下一個Option的偏移,依次處理后面的Option:
調用NdisAdvanceNetBufferDataStart前:
kd> dt ndis!_NET_BUFFER @r14
+0x000 Next : (null)
+0x008 CurrentMdl : 0xffffe20b`aea77e70 _MDL
+0x010 CurrentMdlOffset : 0x10
+0x018 DataLength : 0x188
+0x018 stDataLength : 0x188
+0x020 MdlChain : 0xffffe20b`b0a9c220 _MDL
+0x028 DataOffset : 0x70
+0x000 Link : _SLIST_HEADER
+0x000 NetBufferHeader : _NET_BUFFER_HEADER
+0x030 ChecksumBias : 0
+0x032 Reserved : 0
+0x038 NdisPoolHandle : 0xffffe20b`ae45cb40 Void
+0x040 NdisReserved : [2] (null)
+0x050 ProtocolReserved : [6] 0x00000198`00000000 Void
+0x080 MiniportReserved : [4] (null)
+0x0a0 DataPhysicalAddress : _LARGE_INTEGER 0x0
+0x0a8 SharedMemoryInfo : (null)
+0x0a8 ScatterGatherList : (null)
kd> dt ndis!_MDL 0xffffe20b`aea77e70
+0x000 Next : 0xffffe20b`aea77b10 _MDL
+0x008 Size : 0n56
+0x00a MdlFlags : 0n4
+0x00c AllocationProcessorNumber : 0xffff
+0x00e Reserved : 0xffff
+0x010 Process : (null)
+0x018 MappedSystemVa : 0xffffe20b`aea77eb0 Void
+0x020 StartVa : 0xffffe20b`aea77000 Void
+0x028 ByteCount : 0x30
+0x02c ByteOffset : 0xeb0
kd> db 0xffffe20b`aea77eb0+0x10
ffffe20b`aea77ec0 19 04 00 00 00 00 03 84-30 30 30 30 30 30 30 30 ........00000000 // <--- Option1
ffffe20b`aea77ed0 30 30 30 30 30 30 30 30-18 22 fd 81 00 00 03 84 00000000."......
ffffe20b`aea77ee0 00 bf 09 02 73 6d 41 72-00 00 03 00 dd bf 04 04 ....smAr........
調用NdisAdvanceNetBufferDataStart后:
kd> dt ndis!_NET_BUFFER @r14
+0x000 Next : (null)
+0x008 CurrentMdl : 0xffffe20b`aea77b10 _MDL
+0x010 CurrentMdlOffset : 0
+0x018 DataLength : 0x168
+0x018 stDataLength : 0x168
+0x020 MdlChain : 0xffffe20b`b0a9c220 _MDL
+0x028 DataOffset : 0x90
+0x000 Link : _SLIST_HEADER
+0x000 NetBufferHeader : _NET_BUFFER_HEADER
+0x030 ChecksumBias : 0
+0x032 Reserved : 0
+0x038 NdisPoolHandle : 0xffffe20b`ae45cb40 Void
+0x040 NdisReserved : [2] (null)
+0x050 ProtocolReserved : [6] 0x00000198`00000000 Void
+0x080 MiniportReserved : [4] (null)
+0x0a0 DataPhysicalAddress : _LARGE_INTEGER 0x0
+0x0a8 SharedMemoryInfo : (null)
+0x0a8 ScatterGatherList : (null)
kd> dt ndis!_MDL 0xffffe20b`aea77b10
+0x000 Next : 0xffffe20b`aea78890 _MDL
+0x008 Size : 0n56
+0x00a MdlFlags : 0n4
+0x00c AllocationProcessorNumber : 0xe20b
+0x00e Reserved : 0xffff
+0x010 Process : (null)
+0x018 MappedSystemVa : 0xffffe20b`aea77b50 Void
+0x020 StartVa : 0xffffe20b`aea77000 Void
+0x028 ByteCount : 0x30
+0x02c ByteOffset : 0xb50
kd> db 0xffffe20b`aea77b50
ffffe20b`aea77b50 19 05 00 00 00 00 03 84-41 41 41 41 41 41 41 41 ........AAAAAAAA // <--- Option2
ffffe20b`aea77b60 41 41 41 41 41 41 41 41-42 42 42 42 42 42 42 42 AAAAAAAABBBBBBBB
ffffe20b`aea77b70 42 42 42 42 42 42 42 42-19 05 00 00 00 00 03 84 BBBBBBBB........
(2)進入循環2中的【4】處:
進入Ipv6UpdateRDNSS,處理第一個type為0x19,length為4的option,Ipv6pUpdateRDNSS 中計算IPv6 地址數量是通過下面代碼實現的:
調試結果如下:
rbx 保存option 包的起始位置,[rbx+1] 取的是length字段的值,此時為4,esi的值為1,ecx的值為2,所以這段匯編的計算的ipv6地址個數為(length-1)/2 =1 .
所以length設置為4,其實和length=3計算結果是相同的:
(4-1)/2 = 1
(3-1)/2 = 1
因此會按照0x18(一個ipv6地址加上Type/Length/ Reserved/Lifetime) 的偏移進行解析下一個Option,即解析到偽造的Option。
調用Ipv6UpdateRDNSS前:
kd> dt ndis!_NET_BUFFER @r14
+0x000 Next : (null)
+0x008 CurrentMdl : 0xffffe20b`aea77e70 _MDL
+0x010 CurrentMdlOffset : 0x10
+0x018 DataLength : 0x188
+0x018 stDataLength : 0x188
+0x020 MdlChain : 0xffffe20b`b0a9c220 _MDL
+0x028 DataOffset : 0x70
+0x000 Link : _SLIST_HEADER
+0x000 NetBufferHeader : _NET_BUFFER_HEADER
+0x030 ChecksumBias : 0
+0x032 Reserved : 0
+0x038 NdisPoolHandle : 0xffffe20b`ae45cb40 Void
+0x040 NdisReserved : [2] (null)
+0x050 ProtocolReserved : [6] 0x00000198`00000000 Void
+0x080 MiniportReserved : [4] (null)
+0x0a0 DataPhysicalAddress : _LARGE_INTEGER 0x0
+0x0a8 SharedMemoryInfo : (null)
+0x0a8 ScatterGatherList : (null)
kd> dt ndis!_MDL 0xffffe20b`aea77e70
+0x000 Next : 0xffffe20b`aea77b10 _MDL
+0x008 Size : 0n56
+0x00a MdlFlags : 0n4
+0x00c AllocationProcessorNumber : 0xffff
+0x00e Reserved : 0xffff
+0x010 Process : (null)
+0x018 MappedSystemVa : 0xffffe20b`aea77eb0 Void
+0x020 StartVa : 0xffffe20b`aea77000 Void
+0x028 ByteCount : 0x30
+0x02c ByteOffset : 0xeb0
kd> db 0xffffe20b`aea77eb0+0x10
ffffe20b`aea77ec0 19 04 00 00 00 00 03 84-30 30 30 30 30 30 30 30 ........00000000 // <--- Option1
ffffe20b`aea77ed0 30 30 30 30 30 30 30 30-18 22 fd 81 00 00 03 84 00000000."......
ffffe20b`aea77ee0 00 bf 09 02 73 6d 41 72-00 00 03 00 dd bf 04 04 ....smAr........
調用Ipv6UpdateRDNSS后:
kd> dt ndis!_NET_BUFFER @r14
+0x000 Next : (null)
+0x008 CurrentMdl : 0xffffe20b`aea77e70 _MDL
+0x010 CurrentMdlOffset : 0x28
+0x018 DataLength : 0x170
+0x018 stDataLength : 0x170
+0x020 MdlChain : 0xffffe20b`b0a9c220 _MDL
+0x028 DataOffset : 0x88
+0x000 Link : _SLIST_HEADER
+0x000 NetBufferHeader : _NET_BUFFER_HEADER
+0x030 ChecksumBias : 0
+0x032 Reserved : 0
+0x038 NdisPoolHandle : 0xffffe20b`ae45cb40 Void
+0x040 NdisReserved : [2] (null)
+0x050 ProtocolReserved : [6] 0x00000198`00000000 Void
+0x080 MiniportReserved : [4] (null)
+0x0a0 DataPhysicalAddress : _LARGE_INTEGER 0x0
+0x0a8 SharedMemoryInfo : (null)
+0x0a8 ScatterGatherList : (null)
kd> dt ndis!_MDL 0xffffe20b`aea77e70
+0x000 Next : 0xffffe20b`aea77b10 _MDL
+0x008 Size : 0n56
+0x00a MdlFlags : 0n4
+0x00c AllocationProcessorNumber : 0xffff
+0x00e Reserved : 0xffff
+0x010 Process : (null)
+0x018 MappedSystemVa : 0xffffe20b`aea77eb0 Void
+0x020 StartVa : 0xffffe20b`aea77000 Void
+0x028 ByteCount : 0x30
+0x02c ByteOffset : 0xeb0
kd> db 0xffffe20b`aea77eb0+0x28
ffffe20b`aea77ed8 18 22 fd 81 00 00 03 84-00 bf 09 02 73 6d 41 72 ."..........smAr // <--- 偽造的option
ffffe20b`aea77ee8 00 00 03 00 dd bf 04 04-00 60 6d b0 0b e2 ff ff .........`m.....
(3)進入循環2中的【3】處case:0x18,處理偽造的type為0x18,length為0x22的option。
對于type為0x18會進入下面的流程處理,調用NdisGetDataBuffer函數,其中第二個參數為長度的實際字節大小,等于length8,所以此時傳入的actual_length_bytes = 0x22 8 = 0x110:
而Storage_1 為棧上的數組變量,將0x110個字節賦值過去,就會造成棧上的溢出,實際的崩潰是溢出覆蓋了stack cookie,觸發tcpip!_security_check_cookie,造成藍屏(BSOD):
調用NdisGetDataBuffer函數前:
kd> r rdx // actual_length_bytes_1
rdx=0000000000000110
kd> dd r8 // Storage_1
fffff806`6ce9a348 00000000 00000000 00000000 00000000
fffff806`6ce9a358 00000000 00000000 00000000 00000000
fffff806`6ce9a368 00000000 00000000 b3b18770 ffffe20b
fffff806`6ce9a378 aea77eb0 ffffe20b b021bce0 ffffe20b
fffff806`6ce9a388 00000000 00000000 aea77eb0 ffffe20b
fffff806`6ce9a398 00000000 00000000 b0210040 00000000
調用NdisGetDataBuffer函數后:
kd> dd fffff806`6ce9a348
fffff806`6ce9a348 81fd2218 84030000 00000519 84030000
fffff806`6ce9a358 41414141 41414141 41414141 41414141
fffff806`6ce9a368 42424242 42424242 42424242 42424242
fffff806`6ce9a378 00000519 84030000 41414141 41414141
fffff806`6ce9a388 41414141 41414141 42424242 42424242
kd> k
Child-SP RetAddr Call Site
fffff806`6ce9a090 42424242`42424242 tcpip!Ipv6pHandleRouterAdvertisement+0xae522
fffff806`6ce9a440 84030000`00000519 0x42424242`42424242
fffff806`6ce9a448 41414141`41414141 0x84030000`00000519
fffff806`6ce9a450 41414141`41414141 0x41414141`41414141
fffff806`6ce9a458 00000000`00000000 0x41414141`41414141
最后需要注意的是如果從NetBuffer_1請求的數據是連續的,則會將數據存放在NDIS提供的地址,這樣無法造成溢出。所以需要從NetBuffer_1請求的數據不是連續的,才會將數據存放在Storage_1上。數據非連續的實現是通過 fragmentation(碎片化),將Router Advertisement包通過scapy的fragment6函數拆分成多個IPv6 fragments進行發送。
藍屏崩潰現場:
0x04 補丁分析
補丁前第一個循環case:0x19的偽代碼:
補丁后:
補丁加入了針對length的奇偶驗證,v32為length *8的結果, 如果 length 為偶數,(v32-8)&0xf 將不等于0,則轉入錯誤處理流程。
0x05 時間線
2020-10-13 微軟發布漏洞通告
2020-10-14 360CERT發布通告
2020-10-16 360CERT監測到網上公開相關Poc
2020-10-16 360CERT更新通告
2020-10-19 360CERT發布漏洞分析報告
0x06 參考鏈接
- https://tools.ietf.org/html/rfc8106
- https://www.mcafee.com/blogs/other-blogs/mcafee-labs/cve-2020-16898-bad-neighbor
- http://blog.pi3.com.pl/?p=780
- https://blog.quarkslab.com/beware-the-bad-neighbor-analysis-and-poc-of-the-windows-ipv6-router-advertisement-vulnerability-cve-2020-16898.html