CVE-2020-16898逆向分析

CVE-2020-16898逆向分析CVE 2020 16898 漏洞是一个典型的栈溢出漏洞 此漏洞由于出现在内核层所以最有可能造成的便是系统蓝屏宕机 也不排除利用栈溢出造成任意代码执行 但由于系统自身带有的 security c

大家好,欢迎来到IT知识分享网。

  • windows_10 : 1709/1803/1903/1909/2004
  • window_server_2019 : *
  • window_server : 1903/1909/2004

官方安全更新:Microsoft

360cert使用IDA Pro已经对此漏洞有过分析:
我是用ghidra进行分析所以在伪代码上有些许不同
在进行分析前先要对几个结构体和函数还有RSDNSS报文进行一下了解:

typedef struct _NET_BUFFER { 
    union { 
    struct { 
    PNET_BUFFER Next; PMDL CurrentMdl; ULONG CurrentMdlOffset; union { 
    ULONG DataLength; SIZE_T stDataLength; }; PMDL 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]; NDIS_PHYSICAL_ADDRESS DataPhysicalAddress; union { 
    PNET_BUFFER_SHARED_MEMORY SharedMemoryInfo; PSCATTER_GATHER_LIST ScatterGatherList; }; } NET_BUFFER, *PNET_BUFFER; 
  • NdisAllocateNetBuffer
  • NdisAllocateNetBufferAndNetBufferList
    驱动程序可以调用 NdisAllocateNetBufferListPool函数,然后在分配NET_BUFFER_LIST结构池时 将NET_BUFFER_LIST_POOL_PARAMETERS结构的fAllocateNetBuffer成员 设置 为TRUE。在这种情况下,将为驱动程序从池中分配的每个NET_BUFFER_LIST结构预先分配NET_BUFFER结构。
    链接到每个NET_BUFFER结构的是一个或多个缓冲区描述符,这些描述符映射包含网络数据包数据的缓冲区。这些缓冲区描述符在NetBufferHeader成员中指定为MDL链 。此类网络数据包数据已被接收或将被发送。
    要访问MDL链中的其他数据空间,NDIS驱动程序可以调用以下函数:
  • NdisRetreatNetBufferDataStart
  • NdisRetreatNetBufferListDataStart
typedef struct _MDL { 
    struct _MDL *Next; CSHORT Size; CSHORT MdlFlags; struct _EPROCESS *Process; PVOID MappedSystemVa; PVOID StartVa; ULONG ByteCount; ULONG ByteOffset; } MDL, *PMDL; 

MDL描述了物理内存中虚拟内存缓冲区的布局,一个MDL结构是部分不透明的结构,它表示一个内存描述符列表(MDL),驱动程序应仅直接访问此结构的Next和MdlFlags成员。

PVOID NdisGetDataBuffer( PNET_BUFFER NetBuffer, ULONG BytesNeeded, PVOID Storage, UINT AlignMultiple, UINT AlignOffset ); 

调用 NdisGetDataBuffer函数以从NET_BUFFER结构访问连续的数据块 ,返回值为一个指向连续数据开始的指针,或者返回NULL,调用此函数以获取指向NET_BUFFER结构中包含的网络数据标头的指针 。您可以轻松地解析此函数返回的连续数据块中存储的标头。

NDIS_STATUS NdisRetreatNetBufferDataStart( PNET_BUFFER NetBuffer, ULONG DataOffsetDelta, ULONG DataBackFill, NET_BUFFER_ALLOCATE_MDL_HANDLER AllocateMdlHandler ); 

用 NdisRetreatNetBufferDataStart函数以访问NET_BUFFER结构的MDL链中 更多的已 用数据空间
RDNSS Option 数据包格式如下:
在这里插入图片描述

  • 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。

以上内容参考详情:
NET_BUFFER结构体
MDL结构体
MDL结构体的使用说明
NdisGetDataBuffer函数
NdisRetreatNetBufferDataStart函数
RDNSS rfc5006

出现漏洞的地方大致位于对RDNSS Option包解析的位置,上面提到过RDNSS option包的大致格式,如果只带有一个IPV6地址的话RDNSS包总大小为24(16+8)字节。

因为缺少代理,我的ida7与ghidra9.1都没法逆出WIN64下PE文件的导出函数名ida下全是“sub_地址”的格式ghidra下全是”FUN_地址“的格式,所以在查找处理RDNSS option包函数的时候废了不少时间。
在这里我已经找到了并且更改了函数名做了标记
我们首先要找到两个while(true)第一个循环用于遍历处理RDNSS包的头部分,也就是Type与Length字段对包进行验证,第二个循环用于处理其余包内容的部分。首先找到第一个循环
在这里插入图片描述
pbVar15获取到数据后,根据报文格式首字节为Type下标为1的字节自然就为Length在这里进行了位移运算左移3位相当与乘以8,假设现在只有一个地址Length为3,那么3×8=24(8+16=24)刚好是整个RDNSS包的大小,在根据rfc5066中对RDNSS的解释,增加一个地址Length就要+2来看3+2=5,而5×8=40(8+32=40)刚好也是RDNSS包的大小,所以我们可以看出我划线部分是在计算RDNSS包实际总大小。然后最后用一个if来判断包大小是否为0。
在这里插入图片描述
刚刚说道pbVar15为RDNSS包现在如果之前那个if判断包不为0的话就会进入截图里的else部分这部分流程大致可以概括为iVar12=_Size=pbVar15[0]所以iVar12便是字段Type
在这里插入图片描述
这一步,为判断Type他会先判断Type是否不为1、3如果都满足在判断是否为5,如果不为5就进入else开始判断是否是RDNSS包,刚刚提到RDNSS类型表示为25即0x19。
在这里插入图片描述
这里的uVar42是之前获取到的包总大小,也就是lenth×8后得到的结果,他会在第二个if里判断包总大小是否小于24,如果小于则这个if成立。
然后我们就需要转入第二个while(true)也就是处理包体部分的循环,在这当中会有许多我们不关心的部分,直接省略。
在这里插入图片描述
第二个循环也会像第一个那样,先对length×8并将包总大小赋值给lVar28,然后将type赋值给cVar11,跟着cVar11往下查找

在这里插入图片描述

他会先判断type是否为0x03如果不是,便进入一个else判断type等于0x18或者是0x19
在这里插入图片描述

根据代码逻辑,他会转入FUN_1c01ab5b4函数,根据上面的代码可以判断出第二个参数lVar2是一个NET_BUFFER结构体知道这一点即可
在这里插入图片描述
进入FUN_1c01ab5b4函数后,他会利用第二个参数获取到一个NET_BUFFER结构体变量,然后就是之前所提到的计算地址个数的算法(length-1)/2,这个运算式存在一个漏洞,那就是当length等于4的时候计算结果与length等于3时的计算结果完全一直

(4-1)/2=1 (3-1)/2=1 

现在就来假设一下,假如我们构造一个Type为25,length为4的数据包,也就是说我们构造的RDNSS包最大长度可以是32个字节(4×8=32),但是由于(4-1)/1与(3-1)/2的结果一样,那就会导致程序会将地址当作只有一个地址去解析,也就是说第二个循环只会处理24个字节的内容,剩下的8个字节会被留到下一次循环去处理
在这里插入图片描述
在这里插入图片描述
ghidra对于这部分的伪代码有点错误,再用IDA来看看。
在这里插入图片描述

假如我们那多出来的八个字节的第1,2个字节分别为0x18和任意一个很大的数来充当length那么根据之前对 NdisGetDataBuffer函数的了解,他的第三个参数为一块缓冲区,这块缓冲区的大小必须要大于第二个参数值,否则如果这块缓冲区位于局部变量的栈区那就会造成栈溢出,而在这里v257是一个局部变量,占5个字节(v251、v250、v259、v258、v257内存地址是连续的以此判断为一个数组v257为首字节)v77等于length*8,所以很容易就能造成栈溢出。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/155651.html

(0)
上一篇 2025-02-17 21:00
下一篇 2025-02-17 21:05

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信