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 資料包格式如下:
-
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,是以很容易就能造成棧溢出。