ip分片代码_CVE202016898 “坏邻居”Windows TCP/IP 远程代码执行漏洞分析

本文详细分析了Windows TCP/IP堆栈在处理ICMPv6路由器广告数据包时存在的远程代码执行漏洞(CVE-2020-16898)。攻击者可以通过发送特制的ICMPv6路由广告包,利用Length字段的偶数值,绕过检查并触发栈溢出,执行任意代码。微软已修复此漏洞,但公开的POC可以稳定触发BSOD。漏洞触发的关键在于对Length字段的不严格校验,导致原本不应通过的数据进入处理流程。
摘要由CSDN通过智能技术生成

本文作者  Strawberry @ QAX A-TEAM

7bfeb09e0875ae912b2fec6bc7f7dd8b.gif

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

7bfeb09e0875ae912b2fec6bc7f7dd8b.gif

声明:本篇文章由 Strawberry@ 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漏洞,复现截图如下:

02873cfcec13a033445cb954abdca408.png

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

08c43675d28a5beb852513c828abdb22.png

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

2c5ef737bb2ed2ed80b77fbcad42103a.png

697a068436053ea0b2b88fe15da64db7.png

协议分析

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

b553dcdcabf45df3128f27d2656139df.png

  • 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.....&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.....&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............

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.....&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,eax0: 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: kd> gutcpip!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  .&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 = 0x1100: kd> r r8    // Storage 参数r8=fffff8064ad237180: kd> r rsprsp=fffff8064ad234600: 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     30: 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)的格式:

23389b08025449dd5f0dc85613eab269.png

然后就看到了重点,这个 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 选项成功绕过前面的检查,向栈上复制大量数据,造成栈溢出!

9f60236ca81afed8031fd9e6709f0e19.png

[ 调试证明 ] 每当选项数据通过检测后,使用 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 为偶数,则会转入错误流程。

fa3878cf3679fc1637646a1dc3757181.png

补丁前

213ea661d675ff9634c39b1f6900a7a8.png

补丁后

漏洞小结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值