作者:fanxiaocao(@TinySecEx) and @pjf_ of IceSword Lab , Qihoo 360

英文版地址:《Automatically Discovering Windows Kernel Information Leak Vulnerabilities》

前言

2017年6月補丁日,修復了我們之前報告的5個內核信息泄漏漏洞 , 文章末尾有細節。

前年我演示過如何用 JS 來 fuzz 內核,今天我們要給大家帶來的是不依賴 fuzz ,來自動化挖掘內核漏洞。從最近的幾個月工作里,選取了一個小點,說下內核信息泄漏類型漏洞的挖掘。

背景

windows vista 之后,微軟對內核默認啟用了了 ASLR ,簡稱 KASLR . KASLR 隨機化了模塊的加載基址 , 內核對象的地址等,緩解了漏洞的利用。

在 win8 之后,這項安全特性的得到了進一步的增強。 引入 nt!ExIsRestrictedCaller 來阻止 Low integrity 的程序調用某些可以泄漏出模塊基址,內核對象地址等關鍵信息的函數。 包括但不限于:

NtQuerySystemInformation

* SystemModuleInformation 
* SystemModuleInformationEx 
* SystemLocksInformation 
* SystemStackTraceInformation 
* SystemHandleInformation 
* SystemExtendedHandleInformation 
* SystemObjectInformation 
* SystemBigPoolInformation 
* SystemSessionBigPoolInformation 
* SystemProcessInformation
* SystemFullProcessInformation

NtQueryInfomationThread

NtQueryInfomationProcess

以上是傳統的可以獲取 內核模塊地址和內核對象地址的方法 , 作為內核正常的功能。但對于 integrity 在medium 以下的程序,在 win8 以后調用會失敗。

KASLR 作為一項漏洞利用緩解措施,其中的一個目的就是為了使得構建通用的ROP-CHAIN 更為困難。作為漏洞的利用者來說,挖掘出信息泄漏漏洞,來直接泄漏出所需要的模塊基址,就是直接對抗KASLR的辦法。

特點

作為內核漏洞的一種,在挖掘的過程中有特殊的地方。比如,對于傳統內存損壞類漏洞而言,漏洞本身就會影響系統的正常運行,使用 verifier 等工具,能較為方便的捕獲這種異常。

但是信息泄漏類型的漏洞,并不會觸發異常,也不會干擾系統的正常運行,這使得發現它們較為困難。 漏洞是客觀存在的,我們需要做的以盡可能小的成本去發現它們。

挖掘思路

泄漏發生時,內核必然會把關鍵的信息寫入用戶態的內存,如果我們監控所有內核態寫用戶態地址的寫操作,就能捕獲這個行為。

當然系統并沒有提供這個功能,這一過程由@pjf的一個專門的基于硬件虛擬化的挖掘框架進行捕獲。

為了不干擾目標系統本身的操作,我在虛擬機里執行監控,獲取必要的信息,在寫成log后,再在宿主機進行二次分析。

在物理機里,解碼日志并加載符號,做一些處理之后

就得到這樣的一批日志。

二次分析

現在我們有了一段實際運行過程中內核寫到用戶態內存的所有記錄。這里面絕大多數都是正常的功能,我們需要排除掉干擾,找出數據是關鍵信息的。

這里主要用到了兩個技巧。

污染內核棧

毒化或者說污染目標數據,是一種常見的思路。在網絡攻防里,也有 ARP 和 DNS 緩存的投毒。

這里所說的內核棧毒化,指的就是污染整個未使用的內核棧空間。如果某個內核棧上的變量沒有初始化,那么在這個變量被寫到到用戶態時,寫入的數據里就有我所標記的 magic value ,找出這個 magic value 所在的記錄,就是泄漏的發生點。

同時我注意到,j00ru 在他的 BochsPwn 項目里也曾使用了類似的技巧。

KiFastCallEntry Hook

為了有時機污染內核棧,我 Hook 了 KiFastCallEntry , 在每個系統調用發生時,污染當前棧以下剩余棧空間。

首先使用 IoGetStackLimits 獲取當前線程的范圍,然后從棧底部到當前棧位置的整個空間都被填充為 0xAA 。

這樣進入系統調用之后,凡是內核堆棧上的局部變量的內容,都會被污染成 0xAA 。

污染內核POOL

類似的,對于動態分配的內存,我采用 hook ExAllocatePoolWithTag 等,并污染其 POOL 內容的方式。

這樣,無論是棧上的,還是堆上的,只要是未初始化的,內容都被我們污染了。

如果這個內核堆棧變量沒有正確的初始化,就有可能將這個 magic value 寫入到用戶態的內存。結合我們捕獲的日志,就能馬上發現這個信息泄漏。

為了排除掉巧合,使用了多次變換 magic value 如 0xAAAAAAAA , 0xBBBBBBBB 的辦法來進行排除誤報。

排除干擾之后的一次典型的結果如下

可以看到,在某次短暫的監控過程中,就抓到了系統里 161 次泄漏。

當然這沒有排重,并不是有這么多個獨立的漏洞,而是某些漏洞在反復的泄漏。

此時我們就抓到了一個真正的信息泄漏漏洞,有堆棧信息,再輔以簡單的人工分析,就能知道細節, 這也是 CVE-2017-8482 背后的故事。

差異比對

對于未初始化堆棧所導致的內核信息泄漏,我們可以用污染然后查找標記的方式發現。

對于直接泄漏了關鍵信息的,比如直接寫入了模塊,對象,POOL 地址類型的,就不能用這種方法發現了。

在系統運行過程中,內核本身就會頻繁的向用戶態寫入數據,很多數據在內核地址范圍內,但實際上并不是有效的地址,只是一種噪音數據。

這種噪音數據有很多,像字符串,像素,位置信息等都有可能恰好是一個內核地址,我們需要排除掉這些噪音,發現真正的泄漏。

這里我們過濾出一部分有意義的地址,比如:

  1. 模塊地址,必須在內核模塊地址范圍內。
  2. object 地址
  3. POOL 地址

在環境改變,比如重啟系統之后 ,必須還能在相同的位置泄漏相同類型的數據。

在排除掉系統正常的功能如 NtQuerySystemInformation 之類的之后,得到的數據,可信度就非常高了。

泄漏模塊地址

CVE-2017-8485 為例,比對之后得到的結果

可以看到,此時的結果就非常直觀了,相同的堆棧來源在相同的位置下,都泄漏了 nt!ObpReferenceObjectByHandleWithTag+0x19f 這個地址。

泄漏 object 地址

由于泄漏 object 地址和 POOL 地址的本月微軟還沒來得及出補丁,不能描述細節。

可以看到其中的一個案例,某個函數泄漏一個相同 object 的地址。

值得一提的是,對于這種不是從堆棧上復制數據產生的泄漏,是無法用污染堆棧的方法發現的。

最后

可以看到,我們不需要專門的fuzz,僅僅依靠系統本身的運行產生的代碼覆蓋,就發現了這些漏洞。

任何程序的正常運行,都能提高這個覆蓋率。

事實上,在實際的挖掘過程中,我僅僅使用了運行游戲和瀏覽器的辦法就取得了良好的效果 , 一局游戲打完,十個內核洞也就挖到了。

本月案例

CVE-2017-8470

CVE-2017-8474

CVE-2017-8476

CVE-2017-8482

CVE-2017-8485


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/324/