CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析

  • A+
所属分类:安全文章

本文作者  Strawberry @ QAX A-TEAM

CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析

2020年10月14日,微软修复了一个紧急漏洞:Windows TCP/IP 远程代码执行漏洞,漏洞编号为 CVE-2020-16898。Windows TCP/IP 堆栈在处理 ICMPv6 路由器广告数据包时,存在一个远程执行代码漏洞。攻击者可通过向受影响主机发送特制 ICMPv6 路由广告包来利用此漏洞,成功利用此漏洞的攻击者可在目标服务器或客户端上执行任意代码。2020年10月16日,奇安信 CERT 监测到 Windows TCP/IP 远程代码执行漏洞细节及 POC 在网上发布,经验证,公开的 POC 可稳定触发 BSOD。本文对此漏洞进行分析,如有不足之处,欢迎批评指正。

CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析


声明:本篇文章由 [email protected] QAX A-TEAM原创,仅用于技术研究,不恰当使用会造成危害,严禁违法使用 ,否则后果自负。


QAX A-TEAM





漏洞简介




2020年10月14日,微软修复了一个紧急漏洞:Windows TCP/IP 远程代码执行漏洞,漏洞编号为 CVE-2020-16898。国外安全厂商发布了关于此漏洞的验证视频,证明此漏洞可造成BSOD(蓝屏死机),并指出有可能被恶意攻击者利用来进行蠕虫攻击。当Windows TCP / IP堆栈不正确地处理使用选项类型 25(递归DNS服务器(RDNSS)选项)且长度字段值为偶数的 ICMPv6 路由器广告数据包时,存在一个远程执行代码漏洞。攻击者可通过向受影响主机发送特制 ICMPv6 路由广告包来利用此漏洞,成功利用此漏洞的攻击者可在目标服务器或客户端上执行任意代码。


2020年10月16日,奇安信 CERT 监测到 Windows TCP/IP 远程代码执行漏洞细节及 POC 在网上发布,经验证,公开的 POC 可稳定触发BSOD。





漏洞复现




奇安信CERT第一时间复现了CVE-2020-16898漏洞,复现截图如下:


CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析


以下为测试时的抓包数据,向目标系统发送了分片的 ICMPv6 路由器广告数据包,数据包中包含了 Length 字段为 4(偶数)的递归 DNS 服务器(RDNSS)选项(类型为0x19,25) ,满足之前已知的漏洞触发条件:


CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析


仔细观察该数据包可以发现,除了第一个 RDNSS 选项中的 Length 长度为 4 之外,后面的 RDNSS 选项中的 Length 长度都为 5,从 Wireshark 解析的数据分块中可发现 Length 和 RDNSS 选项长度大致相关,但为什么 Length 不能用偶数呢,那还得继续了解一下协议。


CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析

CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析





协议分析



IPv6 路由器广告选项允许 IPv6 路由器向 DNS6 主机广告 DNS 递归服务器地址列表和 DNS 搜索列表。RDNSS 选项通常包含一个或多个递归 DNS 服务器的 IPv6 地址列表。所有地址共享相同的生命周期值。如果希望具有不同的“生存时间”值,则可以使用多个 RDNSS 选项。以下为 RDNSS 选项的格式:


CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析


  • Type(1个字节):RDNSS选项类型的类型为 25(0x19)


  • Length(1个字节):如果该选项中包含一个 IPv6 地址,则长度取最小值3 。每增加一个 RDNSS 地址,长度就会增加2。接收器使用“长度”字段来确定选项中IPv6地址的数量


  • LifeTime(4个字节):该RDNSS地址可以用于名称解析的最长时间(相对于发送包的时间,以秒为单位)


  • Addresses of IPv6 Recursive DNS Servers(可变长度,由“Length”字段确定):一个或多个递归DNS服务器的 128 位 IPv6 地址 。地址个数为(Length - 1)/ 2


可以将 Length 字段理解为 RDNSS 选项总长度/8,IPv6 Recursive DNS Servers 地址前的字段占 8 字节,每个 IPv6 Recursive DNS Servers 地址长度为 16 个字节,所以正常的 RDNSS 选项总长度应满足 16x+8(x>=1),将其除以 8 就是 2x+1(x>=1) ,也就是 Length 字段应该满足的条件。由于 IPv6 RDNSS 地址为 16 个字节,所以 RDNSS 选项总长度会以 16 字节递增,一个最小的长度为 24(8+16),即 Length 为 3 时,表示 Addresses of IPv6 Recursive DNS Servers 字段中只有一个 RDNSS 地址,因而协议中规定了标准的 Length 字段必须为大于等于 3 的奇数。如果这个字段真的取了偶数,又会发生什么呢,那我们调试一下 POC。





漏洞调试




当目标系统接收到 ICMPv6 路由器广告数据包后会调用 Ipv6pHandleRouterAdvertisement 函数进行处理,在该函数中会使用 NdisGetDataBuffer 函数从 NET_BUFFER 结构中访问数据(连续或不连续),该函数原型如下:


PVOID NdisGetDataBuffer(  PNET_BUFFER NetBuffer,  ULONG       BytesNeeded,  PVOID       Storage,  UINT        AlignMultiple,  UINT        AlignOffset);


其第一个参数 NetBuffer 为一个指向 NET_BUFFER 结构的指针;第二个参数 BytesNeeded 为请求数据的长度;第三个参数 Storage 为指向缓冲区的指针,如果调用者不提供缓冲区,则为 NULL。如果此值非 NULL 且请求的数据不连续,则 NDIS 会将请求的数据复制到 Storage 指向的缓冲区。


NdisGetDataBuffer 函数返回指向连续数据的指针,或者NULL。如果缓冲区中请求的数据是连续的,则返回值是指向 NDIS 提供的位置的指针。如果数据不连续,则根据 Storage参数来判断:


  • 如果 Storage 参数为非 NULL,即指定缓冲区指针,则 NDIS 将数据复制到Storage 指向的缓冲区中,返回值为 Storage参数指针 。

  • 如果 Storage 参数为 NULL,则返回值为 NULL。


NdisGetDataBuffer 函数借助 NET_BUFFER 结构中的 CurrentMdlOffset 字段定位要访问数据起始地址相对于第一个 MDL 指向内存数据的偏移。如下所示,使用 NdisGetDataBuffer 函数获取第一个 RDNSS 选项数据的指针。


0: kd> gBreakpoint 0 hittcpip!Ipv6pHandleRouterAdvertisement+0x984:fffff806`4c1dad2c e89f33d7ff      call    ndis!NdisGetDataBuffer (fffff806`4bf4e0d0)
0: kd> dt _net_buffer @rcxndis!_NET_BUFFER +0x000 Next : (null) +0x008 CurrentMdl : 0xffffdd06`68748d60 _MDL +0x010 CurrentMdlOffset : 0x10 +0x018 DataLength : 0x188 +0x018 stDataLength : 0x188 +0x020 MdlChain : 0xffffdd06`6a9c8180 _MDL +0x028 DataOffset : 0x70 +0x000 Link : _SLIST_HEADER +0x000 NetBufferHeader : _NET_BUFFER_HEADER +0x030 ChecksumBias : 0 +0x032 Reserved : 0 +0x038 NdisPoolHandle : 0xffffdd06`68531040 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)
0: kd> dx -id 0,0,ffffdd0666892300 -r1 ((ndis!_MDL *)0xffffdd0668748d60)((ndis!_MDL *)0xffffdd0668748d60) : 0xffffdd0668748d60 [Type: _MDL *] [+0x000] Next : 0xffffdd066e8ea9b0 [Type: _MDL *] [+0x008] Size : 56 [Type: short] [+0x00a] MdlFlags : 4 [Type: short] [+0x00c] AllocationProcessorNumber : 0x0 [Type: unsigned short] [+0x00e] Reserved : 0x0 [Type: unsigned short] [+0x010] Process : 0x0 [Type: _EPROCESS *] [+0x018] MappedSystemVa : 0xffffdd0668748da0 [Type: void *] [+0x020] StartVa : 0xffffdd0668748000 [Type: void *] [+0x028] ByteCount : 0x30 [Type: unsigned long] [+0x02c] ByteOffset : 0xda0 [Type: unsigned long]
0: kd> db 0xffffdd0668748da0 + 10ffffdd06`68748db0 19 04 00 00 00 00 03 84-30 30 30 30 30 30 30 30 ........00000000ffffdd06`68748dc0 30 30 30 30 30 30 30 30-18 22 fd 81 00 00 03 84 00000000."......ffffdd06`68748dd0 00 f9 09 02 55 48 33 77-9e f9 09 f1 00 00 00 00 ....UH3w........ffffdd06`68748de0 e0 8d 74 68 06 dd ff ff-90 26 3c 68 06 dd ff ff ..th.....&<h....ffffdd06`68748df0 10 86 74 68 06 dd ff ff-80 85 74 68 06 dd ff ff ..th......th....ffffdd06`68748e00 02 00 00 00 00 00 00 00-48 8e 74 68 06 dd ff ff ........H.th....ffffdd06`68748e10 00 00 00 00 00 00 00 00-18 8e 74 68 06 dd ff ff ..........th....ffffdd06`68748e20 18 8e 74 68 06 dd ff ff-00 00 00 00 00 00 00 00 ..th............
0: kd> ptcpip!Ipv6pHandleRouterAdvertisement+0x989:fffff806`4c1dad31 0fb64801 movzx ecx,byte ptr [rax+1]
0: kd> db raxffffdd06`68748db0 19 04 00 00 00 00 03 84-30 30 30 30 30 30 30 30 ........00000000ffffdd06`68748dc0 30 30 30 30 30 30 30 30-18 22 fd 81 00 00 03 84 00000000."......ffffdd06`68748dd0 00 f9 09 02 55 48 33 77-9e f9 09 f1 00 00 00 00 ....UH3w........ffffdd06`68748de0 e0 8d 74 68 06 dd ff ff-90 26 3c 68 06 dd ff ff ..th.....&<h....ffffdd06`68748df0 10 86 74 68 06 dd ff ff-80 85 74 68 06 dd ff ff ..th......th....ffffdd06`68748e00 02 00 00 00 00 00 00 00-48 8e 74 68 06 dd ff ff ........H.th....ffffdd06`68748e10 00 00 00 00 00 00 00 00-18 8e 74 68 06 dd ff ff ..........th....ffffdd06`68748e20  18 874 68 06 dd ff ff-00 00 00 00 00 00 00 00  ..th............


quarkslab 博客中提到,在对 RDNSS 选项的处理过程中会调用 Ipv6pUpdateRDNSS 函数计算选项中的 IPv6 地址数:(option.Length - 1)/ 2,那么计算 POC 中第一个选项的地址个数就是(4 - 1)/ 2 = 1,因而会将 NET_BUFFER 前进 24 个字节(3*8),以下为在第一个 RDNSS 选项的 Length 字段下断点的情况:


0: kd> gBreakpoint 1 hittcpip!Ipv6pUpdateRDNSS+0x9d:fffff806`4c34a661 8d4e01          lea     ecx,[rsi+1]
0: kd> ub rip l1tcpip!Ipv6pUpdateRDNSS+0x99:fffff806`4c34a65d 0fb64301 movzx eax,byte ptr [rbx+1]
0: kd> db rbxffffdd06`68748db0 19 04 00 00 00 00 03 84-30 30 30 30 30 30 30 30 ........00000000ffffdd06`68748dc0 30 30 30 30 30 30 30 30-18 22 fd 81 00 00 03 84 00000000."......ffffdd06`68748dd0 00 f9 09 02 55 48 33 77-9e f9 09 f1 00 00 00 00 ....UH3w........ffffdd06`68748de0 e0 8d 74 68 06 dd ff ff-90 26 3c 68 06 dd ff ff ..th.....&<h....ffffdd06`68748df0 10 86 74 68 06 dd ff ff-80 85 74 68 06 dd ff ff ..th......th....ffffdd06`68748e00 02 00 00 00 00 00 00 00-48 8e 74 68 06 dd ff ff ........H.th....ffffdd06`68748e10 00 00 00 00 00 00 00 00-18 8e 74 68 06 dd ff ff ..........th....ffffdd06`68748e20 18 8e 74 68 06 dd ff ff-00 00 00 00 00 00 00 00 ..th............
0: kd> u rip //计算(length - 1)/ 2tcpip!Ipv6pUpdateRDNSS+0x9d:fffff806`4c34a661 8d4e01 lea ecx,[rsi+1] // rsi =1,ecx=2fffff806`4c34a664 2bc6 sub eax,esi // 4-1=3fffff806`4c34a666 4183cfff or r15d,0FFFFFFFFhfffff806`4c34a66a 99 cdqfffff806`4c34a66b f7f9 idiv eax,ecx // 3/2 =1fffff806`4c34a66d 8b5304 mov edx,dword ptr [rbx+4]fffff806`4c34a670 8945b7 mov dword ptr [rbp-49h],eaxfffff806`4c34a673 8bf0 mov esi,eax
0: kd> tcpip!Ipv6pUpdateRDNSS+0xa7:fffff806`4c34a66b f7f9 idiv eax,ecx0: kd> tcpip!Ipv6pUpdateRDNSS+0xa9:fffff806`4c34a66d 8b5304 mov edx,dword ptr [rbx+4]0: kd> r eaxeax=1


在 Ipv6pUpdateRDNSS 函数执行完之后,CurrentMdlOffset 变成 0x28(原来是 0x10),正好指向了 18 22 ... 那块数据(Route Information Option,其类型为 0x18)。


0: kdgutcpip!Ipv6pHandleRouterAdvertisement+0xb89a7:fffff806`4c292d4f 440fb77c2462    movzx   r15d,word ptr [rsp+62h]
0: kd> dt _net_buffer ffffdd06`6e29dea0ndis!_NET_BUFFER +0x000 Next : (null) +0x008 CurrentMdl : 0xffffdd06`68748d60 _MDL +0x010 CurrentMdlOffset : 0x28 +0x018 DataLength : 0x170 +0x018 stDataLength : 0x170 +0x020 MdlChain : 0xffffdd06`6a9c8180 _MDL +0x028 DataOffset : 0x88 +0x000 Link : _SLIST_HEADER +0x000 NetBufferHeader : _NET_BUFFER_HEADER +0x030 ChecksumBias : 0 +0x032 Reserved : 0 +0x038 NdisPoolHandle : 0xffffdd06`68531040 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)
0: kd> dx -id 0,0,ffffdd0666892300 -r1 ((ndis!_MDL *)0xffffdd0668748d60)((ndis!_MDL *)0xffffdd0668748d60) : 0xffffdd0668748d60 [Type: _MDL *] [+0x000] Next : 0xffffdd066e8ea9b0 [Type: _MDL *] [+0x008] Size : 56 [Type: short] [+0x00a] MdlFlags : 4 [Type: short] [+0x00c] AllocationProcessorNumber : 0x0 [Type: unsigned short] [+0x00e] Reserved : 0x0 [Type: unsigned short] [+0x010] Process : 0x0 [Type: _EPROCESS *] [+0x018] MappedSystemVa : 0xffffdd0668748da0 [Type: void *] [+0x020] StartVa : 0xffffdd0668748000 [Type: void *] [+0x028] ByteCount : 0x30 [Type: unsigned long] [+0x02c] ByteOffset : 0xda0 [Type: unsigned long]
0: kd> db 0xffffdd0668748da0+28ffffdd06`68748dc8 18 22 fd 81 00 00 03 84-00 f9 09 02 55 48 33 77 ."..........UH3wffffdd06`68748dd8 9e f9 09 f1 00 00 00 00-e0 8d 74 68 06 dd ff ff ..........th....ffffdd06`68748de8 90 26 3c 68 06 dd ff ff-10 86 74 68 06 dd ff ff .&<h......th....ffffdd06`68748df8 80 85 74 68 06 dd ff ff-02 00 00 00 00 00 00 00 ..th............ffffdd06`68748e08 48 8e 74 68 06 dd ff ff-00 00 00 00 00 00 00 00 H.th............ffffdd06`68748e18 18 8e 74 68 06 dd ff ff-18 8e 74 68 06 dd ff ff ..th......th....ffffdd06`68748e28 00 00 00 00 00 00 00 00-b0 18 cd 68 06 dd ff ff ...........h....ffffdd06`68748e38  f0 c4 d1 66 06 dd ff ff-01 00 00 00 00 00 00 00  ...f............


在下一次循环时,会对该选项进行解析,并按照 Route Information Option(其类型为 0x18)的流程进行处理。


0: kd> Breakpoint 0 hittcpip!Ipv6pHandleRouterAdvertisement+0x984:fffff806`4c1dad2c e89f33d7ff      call    ndis!NdisGetDataBuffer (fffff806`4bf4e0d0)
0: kd> ptcpip!Ipv6pHandleRouterAdvertisement+0x989:fffff806`4c1dad31 0fb64801 movzx ecx,byte ptr [rax+1]
0: kd> db raxffffdd06`68748dc8 18 22 fd 81 00 00 03 84-00 f9 09 02 55 48 33 77 ."..........UH3wffffdd06`68748dd8 9e f9 09 f1 00 00 00 00-e0 8d 74 68 06 dd ff ff ..........th....ffffdd06`68748de8 90 26 3c 68 06 dd ff ff-10 86 74 68 06 dd ff ff .&<h......th....ffffdd06`68748df8 80 85 74 68 06 dd ff ff-02 00 00 00 00 00 00 00 ..th............ffffdd06`68748e08 48 8e 74 68 06 dd ff ff-00 00 00 00 00 00 00 00 H.th............ffffdd06`68748e18 18 8e 74 68 06 dd ff ff-18 8e 74 68 06 dd ff ff ..th......th....ffffdd06`68748e28 00 00 00 00 00 00 00 00-b0 18 cd 68 06 dd ff ff ...........h....ffffdd06`68748e38  f0 c4 d1 66 06 dd ff ff-01 00 00 00 00 00 00 00  ...f............


在 case 0x18 中,会调用 NdisGetDataBuffer 函数向栈上复制 Length*8 字节数据(由于数据不连续并且指定了 Storage 参数为栈上的地址),由于 Length 为 0x22,因而函数执行完之后栈被破坏,如下所示:


0: kd> tcpip!Ipv6pHandleRouterAdvertisement+0xb8a08:fffff806`4c292db0 e81bb3cbff      call    ndis!NdisGetDataBuffer (fffff806`4bf4e0d0)
0: kd> r rdxrdx=0000000000000110 // 0x22*8 = 0x110
0: kd> r r8 // Storage 参数r8=fffff8064ad23718
0: kd> r rsprsp=fffff8064ad23460
0: kd> ptcpip!Ipv6pHandleRouterAdvertisement+0xb8a0d:fffff806`4c292db5 660f6f0553e50d00 movdqa xmm0,xmmword ptr [tcpip!_xmm (fffff806`4c371310)]
0: kd> k # Child-SP RetAddr Call Site00 fffff806`4ad23460 42424242`42424242 tcpip!Ipv6pHandleRouterAdvertisement+0xb8a0d01 fffff806`4ad23810 84030000`00000519 0x42424242`4242424202 fffff806`4ad23818 41414141`41414141 0x84030000`0000051903 fffff806`4ad23820 41414141`41414141 0x41414141`4141414104 fffff806`4ad23828 00000000`00000000     0x41414141`41414141


最终在 Ipv6pHandleRouterAdvertisement 函数返回时由于 cookie 检查不通过,触发蓝屏。至此,我们已经清楚的看到漏洞触发的过程,但心中是否还有些许疑问,为什么会走到 Route Information Option 的处理流程呢,Wireshark 的解析的数据中并没有看到 0x18 类型的 Route Information 选项呢,直接发送 18 22 ... 这样子的选项又会怎样呢。


0: kd> tcpip!Ipv6pHandleRouterAdvertisement+0x10ea:fffff806`4c1db492 e869f70800      call    tcpip!_security_check_cookie (fffff806`4c26ac00)
0: kd> pKDTARGET: Refreshing KD connection
*** Fatal System Error: 0x00000139 (0x0000000000000002,0xFFFFF8064AD232C0,0xFFFFF8064AD23218,0x0000000000000000)
WARNING: This break is not a step/trace completion.The last command has been cleared to preventaccidental continuation of this unrelated event.Check the event, location and thread before resuming.Break instruction exception - code 80000003 (first chance)
A fatal system error has occurred.Debugger entered on first try; Bugcheck callbacks have not been invoked.
A fatal system error has occurred.
For analysis of this file, run !analyze -vnt!DbgBreakPointWithStatus:fffff806`497cf7e0 cc int 3
0: kd> k # Child-SP RetAddr Call Site00 fffff806`4ad227f8 fffff806`498add92 nt!DbgBreakPointWithStatus01 fffff806`4ad22800 fffff806`498ad487 nt!KiBugCheckDebugBreak+0x1202 fffff806`4ad22860 fffff806`497c7a97 nt!KeBugCheck2+0x94703 fffff806`4ad22f60 fffff806`497d9829 nt!KeBugCheckEx+0x10704 fffff806`4ad22fa0 fffff806`497d9c50 nt!KiBugCheckDispatch+0x6905 fffff806`4ad230e0 fffff806`497d7fe3 nt!KiFastFailDispatch+0xd006 fffff806`4ad232c0 fffff806`4c26ac35 nt!KiRaiseSecurityCheckFailure+0x32307 fffff806`4ad23458 fffff806`4c1db497 tcpip!_report_gsfailure+0x508 fffff806`4ad23460 42424242`42424242 tcpip!Ipv6pHandleRouterAdvertisement+0x10ef09 fffff806`4ad23810 84030000`00000519 0x42424242`424242420a fffff806`4ad23818 41414141`41414141 0x84030000`000005190b fffff806`4ad23820 41414141`41414141 0x41414141`414141410c fffff806`4ad23828 00000000`00000000     0x41414141`41414141



漏洞分析




来来来,看一下 Route Information 选项(类型为 0x18)的格式:


CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析


然后就看到了重点,这个 Length 字段只能设置为 1、2 或者 3,所以像调试时候处理的 18 22 ... 那样的数据至少在协议上是不允许的。


然而,在 Ipv6pHandleRouterAdvertisement 函数中也是不允许的,在该函数中首先会对每个选项进行检查,很明显的,会检查 Route Information 选项中的 Length 是否大于 3 ,如果大于 3 就会进入错误流程,然后忽略这个包之类的。

// Ipv6pHandleRouterAdvertisement检测流程    IPv6_options = (KIRQL *)NdisGetDataBuffer(pNET_BUFFER, 2i64, &v183, 1i64, 0);    DataLength = *(_DWORD *)(pNET_BUFFER + 0x18);    v29 = 8 * IPv6_options[1];                 // Length * 8    if ( v29 && v29 <= DataLength )     {      v25 = *IPv6_options;                      // Type      v30 = 1;    }    ......    switch ( v25 )     {      ......      case 0x18u:        v244 = 0i64;        v245 = 0i64;        v246 = 0i64;        if ( v29 > 0x18u     //这里判断 v29 是否大于 0x18,即 Length 是否大于 3          || (v145 = *(_BYTE *)(NdisGetDataBuffer(pNET_BUFFER, v29, &v244, 1i64, 0) + 2), v145 > 0x80u)          || v145 > 0x40u && v29 < 0x18u          || v145 && v29 < 0x10u )        {          *v7 = 0x18;          goto LABEL_273;        }        break;      case 0x19u:        if ( *(_BYTE *)(v11 + 0x194) & 0x40 && v29 < 0x18u )    //判断 Length 是否小于 3          *v7 = 0x19;        break;


再看一下攻击数据, Route Information 选项的前 8 个字节被嵌到了第一个 Recursive DNS Server 选项的末尾。由于在 Case 0x19 的检查流程中(详见上面的代码),只判断了 Length 是否小于 3 ,而没有判断该字段是否是偶数值,可导致在对数据包选项进行检查的时候将第一个 Recursive DNS Server 选项长度误当成 0x20,因此检查是通过的。而在真正处理的过程中,又将其长度解析为 0x18,因而待处理的下一个选项变成了 Route Information 选项,如上面调试时所看到的,该漏洞使 Route Information 选项成功绕过前面的检查,向栈上复制大量数据,造成栈溢出!


CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析


[ 调试证明 ] 每当选项数据通过检测后,使用 Length*8 更新 NET_BUFFER 结构。在 POC 调试中,第一个选项 Length 为 4,于是 NET_BUFFER 前进 0x20,其当前 MDL 正好指向原来的下一个 MDL 指向内存的首地址(分片长度相关),如下所示,可结合上面抓包数据来看:


// Ipv6pHandleRouterAdvertisement检测流程    if ( *v7 != 0x1C )        goto LABEL_273;    //有错误    if ( v29 )    // Length*8    {      v31 = v29 + *(_DWORD *)(t828_NET_BUFFER + 0x10);// CurrentMdlOffset + Length*8      if ( v31 >= *(_DWORD *)(*(_QWORD *)(t828_NET_BUFFER + 8) + 0x28i64) )      {        NdisAdvanceNetBufferDataStart(t828_NET_BUFFER, v29, 0i64, 0i64);      }      else      {        *(_DWORD *)(t828_NET_BUFFER + 0x28) += v29;    // DataOffset更新        *(_DWORD *)(t828_NET_BUFFER + 0x18) -= v29;    // DataLength更新        *(_DWORD *)(t828_NET_BUFFER + 0x10) = v31;     // CurrentMdlOffset更新      }    }
0: kd> gBreakpoint 0 hittcpip!Ipv6pHandleRouterAdvertisement+0x22b:fffff806`4c1da5d3 e8f83ad7ff call ndis!NdisGetDataBuffer (fffff806`4bf4e0d0)
0: kd> dt _net_buffer @rcxndis!_NET_BUFFER +0x000 Next : (null) +0x008 CurrentMdl : 0xffffdd06`6e805210 _MDL +0x010 CurrentMdlOffset : 0 +0x018 DataLength : 0x168 +0x018 stDataLength : 0x168 +0x020 MdlChain : 0xffffdd06`6a9c8040 _MDL +0x028 DataOffset : 0x90 +0x000 Link : _SLIST_HEADER +0x000 NetBufferHeader : _NET_BUFFER_HEADER +0x030 ChecksumBias : 0 +0x032 Reserved : 0 +0x038 NdisPoolHandle : 0xffffdd06`68531040 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
0: kd> dx -id 0,0,ffffdd0666892300 -r1 ((ndis!_MDL *)0xffffdd066e805210)((ndis!_MDL *)0xffffdd066e805210) : 0xffffdd066e805210 [Type: _MDL *] [+0x000] Next : 0xffffdd066e804760 [Type: _MDL *] [+0x008] Size : 56 [Type: short] [+0x00a] MdlFlags : 4 [Type: short] [+0x00c] AllocationProcessorNumber : 0x80 [Type: unsigned short] [+0x00e] Reserved : 0x6 [Type: unsigned short] [+0x010] Process : 0x0 [Type: _EPROCESS *] [+0x018] MappedSystemVa : 0xffffdd066e805250 [Type: void *] [+0x020] StartVa : 0xffffdd066e805000 [Type: void *] [+0x028] ByteCount : 0x30 [Type: unsigned long]    [+0x02c] ByteOffset       : 0x250 [Type: unsigned long]
0: kd> db 0xffffdd066e805250 l30ffffdd06`6e805250 19 05 00 00 00 00 03 84-41 41 41 41 41 41 41 41 ........AAAAAAAAffffdd06`6e805260 41 41 41 41 41 41 41 41-42 42 42 42 42 42 42 42 AAAAAAAABBBBBBBBffffdd06`6e805270  42 42 42 42 42 42 42 42-19 05 00 00 00 00 03 84  BBBBBBBB........





补丁比对



以下为补丁前后对比,其中,v29 和 v32 来自于 Length << 3,补丁前只判断了 Length 长度是不是大于等于 3,补丁后加入了对 Length 值奇偶的校验,如果 Length 为偶数,则会转入错误流程。


CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析

补丁前


CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析

补丁后





漏洞小结





Windows tcpip.sys 在检查 ICMPv6 路由器广告数据包中的 RDNSS 选项时,没有对长度字段进行严格的判断,并且在检查和处理选项数据的过程中也没有采取一致的策略,导致原本不能通过检查的选项数据绕过检查进入处理流程,从而引发安全问题。





参考链接



  • https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-16898

  • https://blog.quarkslab.com/beware-the-bad-neighbor-analysis-and-poc-of-the-windows-ipv6-router-advertisement-vulnerability-cve-2020-16898.html

  • https://tools.ietf.org/html/rfc6106#section-5

  • https://tools.ietf.org/html/rfc4191

  • https://mp.weixin.qq.com/s/8CCtxffVTBE-uzLlkMIxfw

  • https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ndis/nf-ndis-ndisgetdatabuffer

本文始发于微信公众号(奇安信 CERT):CVE-2020-16898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: