
poc for CVE-2024-38063 (RCE in tcpip.sys)
这是一个(相当不稳定的)针对 CVE-2024-38063 的 PoC,该漏洞是 tcpip.sys 中的远程代码执行漏洞,于 2024 年 8 月 13 日被修补。该漏洞并非由我发现并报告的,发现者应是 Wei。
pip3 install scapy
修改脚本中的字段:
iface <- 如果你有多个适配器,需要选择用于发送数据包的适配器。例如,Linux 上为 "eth0",Windows 上为 "Hyper-V Virtual Ethernet Adapter"。如果要使用默认接口,请留空。ip_addr <- 目标系统的 IPv6 地址num_tries & num_batches <- 发送多少批不同的数据包。数量越多,造成的堆损坏越多,触发漏洞的几率也越高。mac_addr <- 留空,除非 scapy 报告找不到 MAC 地址。参见下面的故障排除部分。运行脚本:
python3 cve-2024-38063.py
复现该漏洞最简单的方法是在目标系统上使用 bcdedit /set debug on 并重启机器/虚拟机。这会使默认网络适配器驱动变为 kdnic.sys,它非常乐意合并数据包。如果你想在不同的设置上复现该漏洞,你需要让系统能够合并你发送的数据包。你可以阅读下面的故障排除部分以获取更多细节。
如果你对技术细节感兴趣,可以阅读 Marcus 撰写的 这篇精彩分析。我下面写的细节旨在作为摘要,而非严谨的技术分析。
NET_BUFFER 对象,其中包含缓冲的数据包数据。在偏移量 0x30 处,我们还有一个当前偏移字段,指示数据包已被解析到何处。在此阶段,偏移量通常为 0x28,表示 IPv6 头已被解析,但其他内容尚未处理。tcpip!Ipv6pReceiveDestinationOptions 中处理“目标选项”扩展头时,解析错误会导致调用 tcpip!IppSendErrorList。此函数会对链表中(从当前数据包开始)的每个数据包对象调用 tcpip!IppSendError。tcpip!IppSendError 会产生副作用。它会将缓冲的数据包数据“回退”到开头,并将当前偏移字段重置为零。0x8C)。这意味着驱动程序将继续解析链表中其他数据包的扩展头,即使它们已在 IppSendError 中被“回退”。0x28。Ipv6pReceiveFragment。该函数解析分片扩展头,并假设数据包的偏移字段至少为 0x28,然后通过从当前偏移值中减去 0x30 来计算数据包中非头数据的长度。该值随后被存储到重组对象中,该对象用于重组分片数据包。IppSendError 回退的数据包上被调用。偏移值将为零,并在 Ipv6pReceiveFragment 的较早处增加到 8。在计算非头数据的大小时,该值将下溢并等于 0xffd8(该减法以 16 位进行)。Ipv6pReassembleDatagram:用于计算重组后数据包输出缓冲区的长度。但所有计算都是以 32 位进行的,并且有一个健全性检查,确保总长度不超过 0xFFFF,但这种情况确实会发生。Ipv6pReassemblyTimeout:同样用于计算长度。但这里的计算是以 16 位进行的,会导致整数溢出。这随后会导致在将数据复制到缓冲区时发生缓冲区溢出。为了触发 Ipv6pReassemblyTimeout,分片的发送者需要保持非活动状态 1 分钟。我们的策略是:
IppSendError,随后发送一个分片数据包。Ipv6pReceiveFragment 中造成下溢,并创建一个新的重组对象,其分片数据长度为高的 16 位值。Ipv6pReassemblyTimeout。Ipv6pReassemblyTimeout 中造成缓冲区大小计算的整数溢出,并触发基于堆的缓冲区溢出。脚本中的数据包被大量发送,以提高它们被合并的几率。主要载荷非常简单:
我们还手动设置了 IPv6 头中的跳数和流标签字段。回顾一下,由于漏洞,缓冲的数据包数据被重置。这意味着,在处理分片数据包时,IPv6 头将被解释为分片头数据。IPv6 头中的跳数字段将被解释为分片头中 ID 字段的某一位。通过更改它,我们确保针对多个不同的分片触发漏洞,并造成多次不同的损坏,从而增加崩溃的几率(毕竟这是一个 PoC)。IP 头的流标签字段将被解释为分片头的偏移量和“更多分片”字段。通过将其设置为 1,我们表示后续还有更多头(因此稍后能够触发 Ipv6pReassemblyTimeout),并且偏移量为零(因为这是第一个到达的具有此 ID 的数据包)。
Ipv6pReassemblyTimeout 中使用的代码路径要求原始分片数据包作为单播发送。如果不起作用,可能的原因包括:
ping -6 {ipv6_address}Ether(raw(sr1(IPv6(dst={你的目标 IP})/ICMPv6EchoRequest()))).src,但有时这不起作用tcpip!Ipv6pReceiveDestinationOptions -> tcpip!Ipv6pProcessOptions -> tcpip!IppSendErrorList?tcpip!Ipv6pProcessOptions 上设置断点,检查 [rcx] 是否始终为零。如果是,则数据包可能由于某些原因未被合并。tcpip!Ipv6pReceiveFragment 上设置断点,检查 [rcx+0x30] 是否等于零。如果不是,则漏洞由于某些原因未能触发。