天天看点

CVE-2020-16898逆向分析

CVE-2020-16898漏洞是一个典型的栈溢出漏洞

受影响的版本:

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

NET_BUFFER结构指定通过网络发送或接收的数据。

NDIS驱动程序可以调用以下函数来分配和初始化NET_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 数据包格式如下:

CVE-2020-16898逆向分析
  • 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字段对包进行验证,第二个循环用于处理其余包内容的部分。首先找到第一个循环

CVE-2020-16898逆向分析

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。

CVE-2020-16898逆向分析

刚刚说道pbVar15为RDNSS包现在如果之前那个if判断包不为0的话就会进入截图里的else部分这部分流程大致可以概括为iVar12=_Size=pbVar15[0]所以iVar12便是字段Type

CVE-2020-16898逆向分析

这一步,为判断Type他会先判断Type是否不为1、3如果都满足在判断是否为5,如果不为5就进入else开始判断是否是RDNSS包,刚刚提到RDNSS类型表示为25即0x19。

CVE-2020-16898逆向分析

这里的uVar42是之前获取到的包总大小,也就是lenth×8后得到的结果,他会在第二个if里判断包总大小是否小于24,如果小于则这个if成立。

然后我们就需要转入第二个while(true)也就是处理包体部分的循环,在这当中会有许多我们不关心的部分,直接省略。

CVE-2020-16898逆向分析

第二个循环也会像第一个那样,先对length×8并将包总大小赋值给lVar28,然后将type赋值给cVar11,跟着cVar11往下查找

CVE-2020-16898逆向分析

他会先判断type是否为0x03如果不是,便进入一个else判断type等于0x18或者是0x19

CVE-2020-16898逆向分析

根据代码逻辑,他会转入FUN_1c01ab5b4函数,根据上面的代码可以判断出第二个参数lVar2是一个NET_BUFFER结构体知道这一点即可

CVE-2020-16898逆向分析

进入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个字节会被留到下一次循环去处理

CVE-2020-16898逆向分析
CVE-2020-16898逆向分析

ghidra对于这部分的伪代码有点错误,再用IDA来看看。

CVE-2020-16898逆向分析

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