天天看點

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,是以很容易就能造成棧溢出。