-
-
[原创]CVE-2022-34718 TCP/IP 远程命令执行漏洞分析
-
2022-10-11 17:57 14818
-
原文链接:https://mp.weixin.qq.com/s/5f8GqQ892ZwbWLdowXXVOg
1. 漏洞概述
项目 | 详情 |
---|---|
名称 | Windows TCP/IP Remote Code Execution Vulnerability |
简介 | tcpip.sys在对ipv6数据包进行重组时,NextHeaderOffset可能超出缓冲区范围,导致越界写的发生,利用该漏洞可以实现远程命令执行 |
影响版本 | 该漏洞影响所有受支持的 Windows 操作系统 |
编号 | CVE-2022-34718 |
2. 基础知识
2.1 IPv6 fragment
如果发送的数据包大小超过了最大传输单元(MTU),IPv6 源节点会将数据包分割成多个分片,在前面添加 fragment header ,作为单独的数据包进行传送,然后由目的节点对数据包进行重组。
Fragment header 结构如下:
1 2 3 4 5 6 7 | 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | Next Header | Reserved | Fragment Offset |Res|M| + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | Identification | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + |
- Next header: 1 字节,未分割之前原始数据包的包头类型;
- Reserved: 1 字节,初始化为 0;
- Fragment Offset: 13 比特,头部后方数据相对于原始可分割单位的偏移,以 8 字节为单位;
- Res: 2 比特,初始化为 0;
- M flag: 1 = 后面还有fragments; 0 = 这是最后一个fragment;
- Identification: 4 字节,由发送源点生成,用于和最近发送的,且源地址和目的地址相同的分片数据包做区分。
在分割时,数据包会被分为 不可分片部分 和 可分片部分:
- 不可分片部分:数据包中需要被中间节点处理的部分,例如IPv6头、Hop-by-Hop Options头、中间节点的Destination Options头、Routing头等
- 可分片部分:数据包中只需要被目的节点处理的部分,例如目的节点的Destination Options头、上层(ICMPv6、TCP、UDP)头以及上层的数据
假设原始数据包组成如下:
1 2 3 | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | IPv6 header | Hop - by - Hop options | ICMPv6 header | ICMPv6 paylaod | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + |
根据数据包的大小,它会被源节点分成多个分片:
1 2 3 4 5 6 7 8 9 10 11 | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | IPv6 header | Hop - by - Hop options | Fragment header | First fragment | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | IPv6 header | Hop - by - Hop options | Fragment header | Second fragment | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - | IPv6 header | Hop - by - Hop options | Fragment header | Last fragment | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - |
当上面的数据包都到达目的节点后,会重组成为原始数据包:
- 从第一个分片数据包获取不可分片部分;
- 将每个数据包中 Fragment header 后面的数据提取出来,组成可分片部分。
除此之外需要修改两个位置的数值:
- 不可分片部分最后一个头部中的 next header 字段,数值从第一个分片的 fragment header 中获取;
- 重组后数据包的 Payload length 字段需要重新进行计算。
2.2 IPSec
Internet Protocal Security(IPsec) 是一个安全网络协议组,可以对数据包进行验证和数据加密,从而为位于 IP 网络中的两台计算机提供安全的加密通信。IPsec 用于 virtual private networks(VPNs)。
IPsec 使用以下的协议实现不同的功能:
- Authentication Header(AH): 为IP 数据包提供无连接数据完整性和数据源认证,为重放攻击提供保护;
- Encapsulating Security Payloads(ESP): 提供机密性、无连接数据完整性、数据源认证、反重放服务(通过部分序列完整性的方式)以及有效的通信流机密性;
- Internet Security Association and Key Management Protocol(ISAKMP): 提供了一个用于身份认证和密钥交换的框架,通过手动配置预共享密钥、Internet Key Exchange(IKE和IKEv2)、Kerverized Internet Negotiation of Keys(KINK) 或者 IPSECKEY DNS 记录来提供真正经过验证的密钥材料。该协议的目的是使用 AH 或 ESP 操作所需的各种算法和参数生成 security associations(SA)。
其中 ESP 结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | Security Parameters Index (SPI) | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | Sequence Number | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | Payload data | + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | | Padding | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | | Pad Length | Next Header | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | Integrity Check Value (ICV) | + ...... + | ...... | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + |
- Security Parameters Index (32 bits)
随机值,和目标 IP 一起用于识别接收方的 security association - Sequence Number (32 bits)
递增计数器,防止重放攻击 - Payload data (variable)
保护的原始 IP 包内容,还包括用于保护数据的其他参数(例如用于加密算法的初始化变量)。保护的数据类型由下面的 Next Header 字段说明。 - Padding (0-255 octets)
填充数据,用于和加密算法的 cipher block size 以及下一个字段对齐 - Pad Length (8 bits)
填充数据的长度,以八位字节为单位 - Next Header (8 bits)
下一个头部的类型,数值选自 List of IP protocol numbers - Integrity Check Value (multiple of 32 bits)
可变长度的检查值,可能包含填充数据,用于和 IPv6 的 8 字节边界或者 IPv4 的 4 字节边界进行对齐
3. 漏洞分析
漏洞发生时的函数调用栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 0 : kd> kb # RetAddr : Args to Child : Call Site 00 fffff80a` 1f1842ea : ffffb00e` 0cbef000 ffffb00e` 0d333270 ffffb00e` 0f5b4890 ffffb00e` 00000050 : tcpip!Ipv6pReassembleDatagram + 0x1e3 01 fffff80a` 1f184442 : ffffb00e` 00000000 fffff80a` 1f1c61e8 00000000 ` 00000000 ffffb00e` 0d3138f0 : tcpip!Ipv6pReceiveFragment + 0xae2 02 fffff80a` 1f03bfdf : ffffb00e` 0cbef000 ffffb00e` 0cbef000 00000000 ` 00000001 00000000 ` 0000002b : tcpip!Ipv6pReceiveFragmentList + 0x42 03 fffff80a` 1f03de45 : fffff80a` 1f1c1240 ffffb00e` 0c8ec940 00000000 ` 00000001 ffffb00e` 0cbef000 : tcpip!IppReceiveHeaderBatch + 0x3ef 04 fffff80a` 1f044131 : ffffb00e` 0d31eb90 ffffb00e` 0d23e9c0 fffff803` 94d7b001 00000000 ` 00000000 : tcpip!IppFlcReceivePacketsCore + 0x315 05 fffff80a` 1f043e12 : 00000000 ` 00000017 fffff803` 00000001 fffff80a` 1f001bf0 ffffb00e` 0d23e901 : tcpip!FlpReceiveNonPreValidatedNetBufferListChain + 0x271 06 fffff803` 92ebce75 : 00000000 ` 00000002 fffff803` 931d1980 fffff80a` 1f043d50 fffff803` 94d7b1f0 : tcpip!FlReceiveNetBufferListChainCalloutRoutine + 0xc2 07 fffff80a` 1f0023a6 : ffffb00e` 0cc1b7b0 00000000 ` 00000000 ffffb00e` 0c7ff870 ffffb00e` 0d23e900 : nt!KeExpandKernelStackAndCalloutInternal + 0x85 08 fffff80a` 1e10392e : 00000000 ` 00000000 fffff803` 94d7b2c0 00000000 ` 00000001 fffff80a` 1de5f20d : tcpip!FlReceiveNetBufferListChain + 0xb6 09 fffff80a` 1e1028cc : fffff80a` 1f522801 fffff803` 94d7dd86 0000000e ` 00000000 fffff80a` 00000001 : NDIS!ndisMIndicateNetBufferListsToOpen + 0x11e 0a fffff80a` 1eca6156 : 00000000 ` 00000000 00000000 ` 00000000 ffffb00e` 0d0f4e40 ffffb00e` 0d23e930 : NDIS!NdisMIndicateReceiveNetBufferLists + 0x31c 0b fffff80a` 1eca73e3 : ffffb00e` 0d23e900 00000000 ` 00000001 ffffb00e` 0d0f4e40 fffff803` 94d7b588 : e1i63x64!RECEIVE::RxIndicateNBLs + 0x132 0c fffff80a` 1ecae315 : ffffb00e` 0c483830 ffffb00e` 0d0f4000 ffffb00e` 0d0f4001 fffff803` 00000000 : e1i63x64!RECEIVE::RxProcessInterrupts + 0x253 0d fffff80a` 1ecae623 : ffffb00e` 0c4226b0 00000001 ` 00000000 00000001 ` 00000000 00000000 ` 00000000 : e1i63x64!INTERRUPT::MsgIntDpcTxRxProcessing + 0x121 0e fffff80a` 1ecaddb8 : ffffb00e` 0e66f7d8 00000000 ` 00000000 40200342 ` 00000000 00000000 ` 00000000 : e1i63x64!INTERRUPT::MsgIntMessageInterruptDPC + 0x10f 0f fffff80a` 1e104e69 : ffffda01` 9d7c9010 ffffda01` 9d7c9320 00000000 ` 00000000 00000000 ` 00000000 : e1i63x64!INTERRUPT::MiniportMessageInterruptDPC + 0x28 10 fffff803` 92e7f785 : ffffb00e` 0d200000 fffff803` 93155180 ffffb00e` 0cc8e5d0 ffffb00e` 0cc8e5d0 : NDIS!ndisInterruptDpc + 0x1c9 11 fffff803` 92e7ed10 : fffff80a` 20dc79a8 00000000 ` 002e593c 00000000 ` 00140001 00000000 ` 00000000 : nt!KiExecuteAllDpcs + 0x335 12 fffff803` 92f7500a : 00000000 ` 00000000 fffff803` 93155180 fffff803` 931d1980 ffffb00e` 0f6cc080 : nt!KiRetireDpcList + 0x910 13 00000000 ` 00000000 : fffff803` 94d7c000 fffff803` 94d76000 00000000 ` 00000000 00000000 ` 00000000 : nt!KiIdleLoop + 0x5a |
根据以上信息可以确定,tcpip.sys 使用 IppReceiveHeaderBatch
函数处理接收到的数据包,对 next header 的数值进行判断并调用相应函数处理对应头部,漏洞就发生在处理 fragment header 的时候。
tcpip.sys 会调用 Ipv6pReceiveFragmentList
函数对 fragment header 进行处理,在对分片进行重组的时候:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void Ipv6pReassembleDatagram(__int64 Packet, Reassembly_t * Reassembly, char OldIrql) { ExtensionHeaderLength = Reassembly - >ExtensionHeaderLength; TotalLength = ExtensionHeaderLength + Reassembly - >DataLength; HeaderAndOptionsLength = ExtensionHeaderLength + 0x28 ; NetBufferList = NetioAllocateAndReferenceNetBufferAndNetBufferList( IppReassemblyNetBufferListsComplete, Reassembly, 0i64 , 0i64 , 0 , 0 ); NetBuffer = NetBufferList - >FirstNetBuffer; data = NdisGetDataBuffer(NetBuffer, HeaderAndOptionsLength, 0i64 , 1u , 0 ) / / 请求访问 NET_BUFFER 中的一段连续空间,请求的大小为 HeaderAndOptionsLength Reassembly - >IPv6.Payload_length = __ROR2__(TotalLength, 8 ); * data = Reassembly - >IPv6; / / 复制 IPv6 header memmove(data + 0x28 , Reassembly - >ExtensionHeader, Reassembly - >ExtensionHeaderLength); / / 复制 ExtensionHeader * (data + Reassembly - >nh_offset) = Reassembly - >next_header; / / 复制 next header ... } |
缓冲区 data 的大小为 HeaderAndOptionsLength
,next header 的偏移值为 Reassembly->nh_offset
。发生越界写的原因就是 Reassembly->nh_offset
的数值大于 HeaderAndOptionsLength
。
接下来通过调试确定这两个数值的来源,测试用数据包组成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 0 1 2 3 4 5 6 7 8 9 a b c d e f + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + 0000 | Ethernet II, 0x0e B | | + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + + 0010 | | + IPv6 header, 0x28 B + 0020 | | + + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + 0030 | | Routing header, 8 B | 93 59 - > ESP header, 8 B + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + 0040 99 40 00 00 00 00 | 55 55 55 55 55 55 55 55 55 55 - > IV, 0x10 B + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + 0050 55 55 55 55 55 55 |eb 42 e4 0a 95 da 11 1d 21 a1| - > payload + padding + padding_length + next_header + - - + - - + - - + - - + - - + - - + + 0x30 B,已被加密 0060 |ea c7 31 c9 e1 24 48 8d f9 98 d4 98 82 b7 eb 5d | + + 0070 |b7 8f bf c6 3a 00 eb 22 7d 26 55 b7 43 9f 60 62 | + + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + 0080 |e3 ea fe 8c 23 87 | + - - + - - + - - + - - + - - + - - + |
由于 ESP header 在 Routing header 之后,系统会先处理 ESP header。tcpip.sys 会调用 IppReceiveEspList
函数处理该头部,具体功能位于 IppReceiveEspNbl
函数中,该函数会对 ESP 中的加密数据进行解密,解密结果为:
1 2 3 4 | 0x00 - - - - - - - - - - - - 3a 00 - 00 01 56 56 56 56 55 55 0x10 55 55 55 55 55 55 55 55 - 55 55 55 55 55 55 55 55 0x20 55 55 55 55 55 55 01 02 - 03 04 05 06 07 08 09 0a 0x30 0b 0c 0d 0e 0e 2c |
可以看到 next header 字段的数值为 0x2c,即 Fragment Header for IPv6,系统处理完 ESP 后会继续处理 fragment header。
整理调试过程中,Reassembly->nh_offset
和 HeaderAndOptionsLength
的数据来源变化情况:
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 | / / IppReceiveHeadersHelper Packet - >prefix_size = header_size; / / 0x28 这里是ipv6 header的长度 / / Ipv6pReceiveRoutingHeader / / / / 0x11a 保存的是 nh_offset ,因为此时在处理 routing header, next header 位于起始位置 / / / / 因此大小和 header_size 相同 Packet - >nh_offset = Packet - >prefix_size; / / 0x28 / / / / 继续向下读取 routing header 内容 NetBufferList = Packet - >net_buffer_list; routing_header = NdisGetDataBuffer(NetBufferList - >FirstNetBuffer, 8u , &Storage, 1u , 0 ); len = 8 * routing_header - >length + 8 ; Packet - >prefix_size + = len ; / / 加上 routing header 的 8 个字节, 0x30 / / IppReceiveEspNbl Packet - >nh_offset = Packet - >prefix_size + LOWORD(NetBuffer - >DataOffset) - data_offset + 9 ; / / 0x30 + 0x84 - 0x56 + 0x9 = 0x67 / / 即 Packet - >prefix_size + len (payload_data + padding) + 8 + 1 / / 其中 8 是SPI和Sequence Number占据字节数, 1 是Padding Length占据字节数 Packet - >prefix_size + = (iv_length + 8 ); / / 0x48 / / Ipv6pReceiveFragment Packet - >prefix_size + = 8 ; / / 加上Fragment header的 8 个字节, 0x50 ExtensionHeaderLength = Packet - >prefix_size - 0x30 ; / / 0x50 - 0x30 = 0x20 Reassembly - >ExtensionHeaderLength = ExtensionHeaderLength; / / 这里赋值 0x20 Reassembly - >nh_offset = Packet - >nh_offset; / / 这里赋值 0x67 |
由此可以看出,系统在处理 ESP header 时,因为 next header 位于数据包的尾部, 因此 nh_offset 的计算考虑了 payload 的长度;但是在处理 fragment header 的时候,系统认为 next_header 位于 ExtensionHeader 中。即:
1 2 3 4 5 6 7 | |< - data buffer size - >| |< - 0x28 B - >|< - Extension Header 0x20 B - >| + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + | IPv6 header | Routing header | ESP header | IV | Payload |PL |NH | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + ↑ 系统认为的next_header位置 ↑ 真实的next_header位置 |
实际上,根据 RFC8200 ,在进行分片的时候,ESP header 是不能放在 fragment header 的前面的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | original packet: + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - + - - - - - - - - + - / / - + - - - - - - - - + | Per - Fragment |Ext & Upper - Layer| first | second | | last | | Headers | Headers |fragment|fragment|....|fragment| + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - + - - - - - - - - + - / / - + - - - - - - - - + fragment packets: + - - - - - - - - - - - - - - - - - - + - - - - - - - - - + - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - + | Per - Fragment |Fragment | Ext & Upper - Layer | first | | Headers | Header | Headers | fragment | + - - - - - - - - - - - - - - - - - - + - - - - - - - - - + - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - + - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Per - Fragment |Fragment| second | | Headers | Header | fragment | + - - - - - - - - - - - - - - - - - - + - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + o o o + - - - - - - - - - - - - - - - - - - + - - - - - - - - + - - - - - - - - - - + | Per - Fragment |Fragment| last | | Headers | Header | fragment | + - - - - - - - - - - - - - - - - - - + - - - - - - - - + - - - - - - - - - - + |
其中的 Ext & Upper-Layer Headers 可能包括 TCP、UDP、IPv4、IPv6、ICMPv6、ESP 等。
4. 补丁对比
Ipv6pReassembleDatagram
新增代码段
1 2 3 4 5 6 7 8 9 10 11 12 | TotalLength = ExtensionHeaderLength + Reassembly - >DataLength;; HeaderAndOptionsLength = ExtensionHeaderLength + 0x28 ; if ( Reassembly - >nh_offset > HeaderAndOptionsLength ) { / / 这里比较了 next header 偏移值和 net buffer 的大小 LABEL_7: LOBYTE(v9) = OldIrql; IppDeleteFromReassemblySet(v8 + 20392 , Reassembly, v9); LABEL_39: + + * (v10 + 8 ); + + * (v11 + 140 ); return ; } |
IppReceiveEsp
新增代码段
1 2 3 4 5 6 7 8 | next_header = Packet - >next_header; IppReceiveEspNbl(...); if ( ! * next_header || (result = ( * next_header - 0x2B ), result < = 1 ) ) / / 也就是说ESP后面不能跟 next header < = 0x2c 的头部了 { result = IppDiscardReceivedPackets(v3, 6i64 , Packet); * (v2 + 140 ) = 0xC000021B ; goto LABEL_11; } |
可以看到经过补丁修复之后,系统对 ESP header 的 next header 字段进行了判断,不再允许小于等于 0x2c 的头部,同时在进行分片重组时,对 Reassembly->nh_offset
和 HeaderAndOptionsLength
的数值进行了判断。
5. 参考资料
- Analysis of a Windows IPv6 Fragmentation Vulnerability: CVE-2021-24086
- Reverse-engineering tcpip.sys: mechanics of a packet of the death (CVE-2021-24086)
- IPsec wiki
- IP Encapsulating Security Payload (ESP)
- Internet Protocol, Version 6 (IPv6) Specification
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。