【漏洞剖析】CVE-2024-38063(Windows IPV6 远程执行代码漏洞)

目录

一、搭建环境

1.内核调试环境

2.获取pdb符号文件

二、静态分析

三、动态调试

四、触发漏洞

五、【强运】的回响


一、搭建环境

1.内核调试环境

VMware 17 以上请下载https://github.com/4d61726b/VirtualKD-Redux

主机环境:

Windbg(尽量默认路径安装,后边省的改设置)

Virtualkd:VirtualKD - Windows Kernel Debugger Booster for Virtual Machines

Vmware虚拟机环境:

Windows10(有漏洞版本)            Wireshark(监测数据包用)

Virtualkd也下载一份,双击启动target/vminstall.exe,点击install,全部默认选是,自动重启虚拟机,进入debug模式

主机上启动 vmmon64.exe

虚拟机打开target64目录下的vminstall.exe,默认设置确定,重启后在这个界面按F8

选择“禁用驱动程序签名强制”(关键嗷,不然主机这边挂不上调试)

主机上OS和Debugger都是yes,证明成功挂上调试

主机windbg出现int 3,并且虚拟机卡住,这时在windbg命令行输入g继续运行

2.获取pdb符号文件

在使用windbg输入命令调试时,如果一直busy,可以先break一下,输入命令后再g继续运行

在windbg设置符号路径,确保WinDbg能够访问微软的符号服务器

.sympath srv*C:\Symbols*https://msdl.microsoft.com/download/symbols

其中C:\Symbols是本地符号缓存目录,可以自己更换,请在没有中文目录下创建

接下来强制重新加载tcpip.sys

.reload /f tcpip.sys

最后重新下载符号

.reload

在重新加载后,可以使用lm t n指令查看所有已加载的模块以及地址范围

此时查看上边设置的符号缓存目录,发现已经下载了对应的pdb符号文件,用ida反编译时就能看到符号啦!(这里的pdb文件是虚拟机中tcpip.sys的符号文件,不是主机的)

二、静态分析

First of all,先给虚拟机做个快照,现在虚拟机是存在漏洞且可调试的完美状态,为了方便后续验证漏洞,所以务必先做快照(挖洞好习惯,多打快照)。

其次,尽量漏洞版本系统是2024年6月之后8月14日之前的win10,因为这个期间微软也会调整或修复其他问题,会出现一些干扰函数,所以尽量选择漏洞前后的两个版本进行diff。

1.给主机和虚拟机系统更新Windows10.0-KB5041773-x64补丁,这个补丁主要修复了CVE-2024-38063,可以在C:\Windows\WinSxS目录下找到修复后的tcpip.sys(用右上角搜索搜tcpip.sys,查看签名时间为8月10日的文件)

2.使用ida中的BinDiff插件diff修复前后的tcpip.sys(注意i64文件目录不能有中文),主要看第一列相似度,发现新加了一个函数Feature_Servicing_tcpip_autoportreuse__private_IsEnabledDeviceUsage(Feature_xxx__private_IsEnabledDeviceUsage大多为补丁函数,但不确定是针对ipv6漏洞的补丁函数,下边还要继续分析)。

3.由于有函数符号,直接在diff列表中搜索ipv6,发现一个ipv6函数相似度不是100%,双击进去看一下

4.在该函数的最下边,发现了Feature_2365398330__private_IsEnabledDeviceUsage()函数,通过比对漏洞版本的tcpip.sys,差的0.06确实在if判断这里

5.进入该Feature函数,一个标志位&0x10,判断了第五位是不是0;如果第五位不是0,那就执行else语句,进行回滚(这里简单分析,一会还要动调)。

6.回到Ipv6pProcessOptions函数继续分析,相较于修复前多了一个IsEnabledDeviceUsage判断,推断产生漏洞的原因就在于IppSendErrorList()。在修补之前,Ipv6pProcessOptions函数调用IppSendErrorList()来处理处理失败的数据包或请求丢弃的数据包,这会遍历整个net_buffer_list(传入的第三个参数),处理其中的每一个数据包;而修复后,在某种条件下只会处理当前的数据包。这也证明之前引发溢出的漏洞并不是单一数据包产生的,我们需要发送多个会被抛弃或或待被处理的ipv6数据包,才能组合触发漏洞。

三、动态调试

PS:在动调前先测试主机和虚拟机能不能ping通,使用ping ipv6地址相互ping,开始可能会失败一两次,但如果持续失败,请直接关闭虚拟机内所有防火墙和主机的域防火墙。

1.通过windbg对补丁函数下断点bp tcpip!Feature_2365398330__private_IsEnabledDeviceUsage

2.为了减小工作量,这里对Ipv6pProcessOptions进行下断,使用python简单fuzz发送ipv6数据包(对头部多个字段同时进行fuzz),等待几秒钟,发现有ipv6数据包触发了Ipv6pProcessOptions函数,复制该数据包的hexstream备用。

3.查看函数逻辑,发现能不能进LABEL_36全靠这个if判断,这里是个||运算,所以两个条件满足一个即可,我们先查看*(_DWORD *)(v6 + 24)的内容,如果该值可控,那很方便就能进入LABEL_36了。

4.通过在cmp [rdi+18h], eax下断,发现*(_DWORD *)(v6 + 24)的值为0x28是IPV6载荷字节数(payload length),可以尝试减小payload数据量或伪造有效载荷长度从而进入补丁函数。

5.跟进查看,主要目的是获取Feature_2365398330__private_featureState的内容,这是本次漏洞修复检测的核心。看到Feature_2365398330__private_featureState的内容是0x157,这条路径可以暂放,回到Ipv6pProcessOptions尝试满足另一个条件。

6.回到另一个条件,取到的DataBuffer值为0x10,DataBuffer[1]的值是0x00,所以v9(r14d)的值为0x08,也就是说payload length要小于8才能进入这个分支到达LABEL_36。

7.搞清了如何触发Ipv6pProcessOptions中的IppSendErrorList ,接下来分析在上一章中我们分析出IppSendErrorList第三个参数是net_buffer_list的指针,这里我们下个条件断点,当第一万次击中断点时再断。使用python多线程发送一万个plen小于8的ipv6数据包,分析查看net_buffer_list链表内容。

8.通过ida得知IppSendErrorList第三个参数是r8,在触发IppSendErrorList断点时查看r8寄存器的值为ffffb6816a27c3c0,使用dt查看该单链表结构内容。这里第一次让我感到奇怪,明明使用多线程发送了一万个数据包,但list中却只有一个数据包,单链表的next指针指向了null。对这种现象有两种解释:其一是由于我给虚拟机分配了8个cpu内核,导致虚拟机处理这些数据包太快,同时python发包速率有上限,所以list只有最新的数据包;其二是发送的数据包没有被放在一个列表中。经过降低虚拟机核心数,终于在list链表中看到其他数据包。但这还不够,接下来需要分析IppSendError函数进行了哪些操作,导致产生了“数据下溢”。

四、触发漏洞

1.进入IppSendError函数进行分析,发现这个函数主要做了两件事:一是检查数据包的有效性,根据传入的数据包信息,更新数据包的状态,调整数据包各个字段大小;二是生成发送ICMP 错误消息。最可疑的代码就是将net_buffer_list中的DataOffsetDelta置为0,这是IppSendError函数中除了将list的status字段设置为0xC000021B以外出现的修改list内容的代码,而且还是很关键的偏移量字段。当net_buffer_list->DataOffsetDelta字段&0x40等于0时,会进入else中执行把DataOffsetDelta置零操作。也许问题就出现在这里,IppSendError函数会经过IppSendErrorList循环调用,对当前net_buffer_list上的所有数据包的DataOffsetDelta都进行置零操作,单个数据包当然不会受到影响,但如果链上的数据包需要合并或其它操作,那可能会导致偏移错位,读写到错误的数据。

2.有师傅说DataOffsetDelta应该是packet_size,但经过动调调试以及NetioRetreatNetBufferList函数参数定义,被置零的字段应该就是DataOffsetDelta,这个字段对应了当前数据包待被处理的buffer起始地址,每个阶段偏移会被赋予不同值,最大为整个数据包长度(包含头部)。同时发现DataOffsetDelta在Ipv6pReceiveRoutingHeader函数和Ipv6pReceiveFragment函数中都有被调用。

Ipv6pReceiveRoutingHeader函数中v7被赋值为DataOffsetDelta-0x28(ipv6头长),只要保证Next Header字段为43(有路由头部)就会进入该代码块,虽然极其容易触发,但由于Ipv6pReceiveRoutingHeader函数被调用在IppSendError函数之前,赋值为零在减0x28之后,所以基本无法利用。

Ipv6pReceiveFragment函数在下边使用了(a1->DataOffsetDelta[0]-0x30),也就是说如果可以触发这里,v41会被赋值为0xFFD0(无符号int16),ExAllocatePoolWithTagPriority函数会为重组数据包分配0xFFD0大小的内存。说明一下,代码中减去0x30是因为IPv6头长0x28 + IPv6 分片头长0x8。这里看起来是个可以利用的点,而且成色很好,由于ExAllocatePoolWithTagPriority函数分配的内存是未初始化的,上边我们提到IppSendError函数会发送ICMP 错误消息,所以返回的ICMP数据包中很有可能包含内核地址(多次申请释放0xFFD0和0x28大小的内存,甚至可能泄露出net_buffer_list上的地址和数据)。

3.还是分析的太慢了,边hw边分析了一周,ynwarcs发布了POC(https://github.com/ynwarcs/CVE-2024-38063),Marcus发布了分析文章https://www.malwaretech.com/2024/08/exploiting-CVE-2024-38063.html。ynwarcs发布的POC选择了使用Ipv6pReassemblyTimeout函数来触发这个漏洞,我将结合该POC阐述利用思路。

4.有点乱?我们捋捋思路。已经确定漏洞产生原因是由于IppSendError对list上不该把DataOffsetDelta置零的数据包进行了置零操作,而要想将数据包放在一个list上且有合并操作,那就一定涉及处理分片数据包。Ipv6pReceiveFragment函数既处理和合并分片数据包,还使用了被IppSendError函数置零的DataOffsetDelta进行申请内存操作,组装分片数据包产生0xffd0的重组数据包。

虽然0xffd0的重组数据包很大,但目前还没有造成堆栈溢出,Ipv6pReceiveFragment很小心的使用了十六位无符号数,所以还要想办法把这个0xffd0大小的数据包copy到另一段比较小的堆栈中。

5.ynwarcs找到了用于触发堆溢出的函数Ipv6pReassemblyTimeout。该函数在ipv6数据包重组失败或异常一段时间后被调用,他的代码很少,但在中间部分使用了memmove函数复制缓冲区,顺着这里往上看,它使用IppNetAllocate申请了一块大小为(0xFFD0+8+0x28)再加上一个不超过0x40的值,由于这里是十六位无符号数,所以相加后一定是一个处于0x00和0x50之间的数,远小于0xFFD0,所以下边使用memmove复制内存时会产生溢出。

五、【强运】的回响

至于为什么这么晚才写完分析,因为我一直在尝试找一条不同的触发路径,也fuzz了很多ipv6数据包及其组合,虽然没能触发该漏洞,但幸运的是其中一个组合触发了另一个漏洞。虽然这么多年过去了,但这些经典的网络驱动上依然存在着很多问题(蓝海嗷),后续如果有分析文章我会第一时间发布文章,感谢看到这里~。

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值