0x01漏洞描述
Windows IPv6协议栈存在的一处拒绝服务漏洞,漏洞的根本原因是IPv6的嵌套分片机制中,当尝试递归重组嵌套的分片时会计算内部有效载荷中包含的所有扩展标头,当重组扩展头约为0xffff字节的数据包时,tcpip!IPv6ReassembleDatagram中发生的NULL指针解引用发生崩溃。
0x02 漏洞分析
漏洞是在tcpip!Ipv6pReassembleDatagram函数中,向rax中写入数据时导致崩溃。
tcpip!Ipv6pReassembleDatagram
NdisGetDataBuffer
NDIS_EXPORTED_ROUTINE PVOID NdisGetDataBuffer(
[in] NET_BUFFER *NetBuffer, //指向NET_BUFFER的指针
[in] ULONG BytesNeeded, //请求的数据的连续的字节数
[in, optional] PVOID Storage, //指向缓冲区的指针;如果调用方未提供任何缓冲区,则为 NULL 。 缓冲区的大小必须大于或等于 BytesNeeded 中指定的字节数。 如果此值不是 NULL,并且请求的数据不是连续的,则 NDIS 会将请求的数据复制到 存储 指示的区域。
[in] ULONG AlignMultiple, //幂对齐
[in] ULONG AlignOffset //对齐倍数的偏移量
);
NdisGetDataBuffer 返回指向连续数据的开头的指针,或者返回 NULL。
如果 NetBuffer 参数指向的 NET_BUFFER 结构中NET_BUFFER_DATA结构的 DataLength 成员小于 BytesNeeded 参数中的值,则返回值为 NULL。
如果缓冲区中请求的数据是连续的,则返回值是指向 NDIS 提供的位置的指针。 如果数据不连续,NDIS 使用 存储 参数,如下所示:
如果 Storage 参数为非 NULL,则 NDIS 会将数据复制到 存储中的缓冲区。 返回值是传递给 Storage 参数的指针。
如果 Storage 参数为 NULL,则返回值为 NULL。
由于无法映射数据缓冲区的低资源条件,返回值也可能为 NULL 。 即使数据连续或存储参数为非 NULL,也可能发生这种情况。
NdisGetDataBuffer函数返回数据指针或者NULL,rax是NdisGetDataBuffer函数的返回值,当赋值为0,之后赋值数据没有判断是否是空指针,就会导致异常。
ndis!NdisGetDataBuffer
NetBuffer->DataLength小于传进来的第二个参数BytesNeeded就会返回0。
tcpip!NetioRetreatNetBuffer
在调用ndis!NdisGetDataBuffer之前调用了tcpip!NetioRetreatNetBuffer函数
函数调用之前,NET_BUFFER->CurrentMdlOffset还没有设置,会调用NdisRetreatNetBufferDataStart函数设置NET_BUFFER结构。
NdisRetreatNetBufferDataStart会把NET_BUFFER->DataLength设置为0x10,函数返回0
a2 BytesNeeded是v3+0x28,v3是a2偏移0x68,a2是Ipv6pReceiveFragment函数中由IppCreateInReassemblySet生成,满足条件后才会调用tcpip!Ipv6pReassembleDatagram进行重组。
Ipv6pReceiveFragment
Ipv6pReceiveFragment函数作用是处理分片,当最后一个分片校验通过,就会调用tcpip!Ipv6pReassembleDatagram函数进行重组。
0x03 漏洞利用
攻击机:kali2022
靶机:win10专业版1709(16299.125)
NdisGetDataBuffer有返回NULL的情况,可以使NET_BUFFER为NULL,NdisGetDataBuffer会返回NULL,就会触发BSOD空指针引用。
补丁分析
Ipv6pReassembleDatagram增加了判断,当重组数据包options_data_length和fragment_sum_length和大于0xFFFF会调用IppDeleteFromReassmblySet函数删除这个包的信息,跳过重组。
在NetioRetreaNetBuffer函数调用处将第二个参数长度变成32位,避免后续调用NdisGetDataBuffer参数不一致。