科普:MEDCIN引擎是一款服務于醫生護士的電子病歷系統。
幾個月前,我在MEDCIN引擎舊版本的安全評估中發現了一個漏洞。于是我向供應商報告了該漏洞然后修復了,之后在查看該程序的最新代碼時又發現了幾個漏洞。
這個程序之前的舊版本里漏洞是不需要經過身份驗證就可以處理來自遠程客戶端的郵件還可以在Windows上運行系統級權限的服務。而且,原來的二進制文件也缺少編譯時的內存保護。于是我首先檢查了開發商是否將編譯時的內存保護功能放入他們的最新版本??焖贋g覽了幾個任意函數,我看到棧cookies已經被添加了而且下圖中二進制文件也被SafeSEH編譯。有了這些新的保護措施,任何基于堆棧的緩沖器溢出漏洞將更難被利用。
于是我使用各種曾在原始的可執行文件中調查出來的信息解析例程進行跟蹤。配合之前在二進制文件上的操作,我快速編寫了一個簡單的模糊器向應用程序服務提供隨機數據。幾個星期的模糊測試和靜態分析后我找到了8個代碼執行漏洞。這些bug中包括棧、堆、數據段緩沖器溢出、內存泄露以及控制部分任意內存的寫入。
CVE-2015-2898
調查了一堆漏洞后,我決定開始著手于一開始發現的基于堆棧的緩沖區溢出。可是棧cookies與SafeSEH編譯的二進制文件的結合并不能讓我們從應用程序獲得可執行文件的控制權。那么如果不能泄漏棧的cookie,我就沒辦法利用這個漏洞。
CVE-2015-2901
接下來的一個bug是字符串緩沖寄存器溢出到數據段再通過socket泄漏。這個漏洞很有用,因為不僅可以覆蓋到整個數據段還能從數據段返回給我內存地址。不過這個漏洞有一個明顯的限制范圍,我只能覆蓋數據段的空字節。下圖是漏洞的偽代碼。
花了很多時間和精力,還是沒有辦法僅僅通過這個漏洞來獲得執行的控制權。我發現只有當數據段被損壞或者程序崩潰的時候會有一個很短暫的時間讓我們通過。但是,程序崩潰的情況是隨機的而且常常沒有辦法進入。我也沒辦法讓棧cookie或棧地址泄漏。但是!能泄漏出一些非常有用的堆地址。
CVE-2015-2900
然后我將目光轉移到能控制部分任意內存寫入的漏洞。這個特殊的bug是在一次模糊器運行中發生程序崩潰時發現的。它似乎來自于添加到數據段中堆指針地址的用戶控制的索引(帶符號的整數)。里面的函數還不能驗證傳入的整數是否大于零。不過我認為該漏洞是部分控制的原因是因為添加索引的堆地址是動態的。下面是bug的一些偽代碼。
經過一番調查,我發現存放我們控制的索引的堆地址在CVE-2015-2901的字符串連接緩沖寄存器的下方。這意味著,我們可以泄漏這個地址并完全控制發生寫入的位置。正如代碼片段中看到的,被寫入的值是一個堆指針,在任意寫入之前就被分配好了。
現在既然我們可以在存儲器的任何地方寫一個指針,那么接下來要做的就是確定我們是否控制著指針指向的數據并找到一個有用的目標地址以重寫我們的指針。確定后,我們重寫一個函數指針觸發命令獲取代碼執行。不過前提是假設指針指向的數據是可以控制執行的。
通過搜索二進制文件,我只找到極少數位于靜態位置的函數指針。幸運的是這些函數指針中有一個可以直接訪問信息解析函數。哇!我們似乎已經在堆上實現了代碼執行。
這能運行的原因是因為該應用程序并沒有在數據執行保護下編譯。我就在Windows 7系統上將DEP設置為“接受”。大多數人只知道DEP禁用棧上的執行,但是它也保護著堆。
實現代碼執行后,現在必須弄清楚被我們重寫的函數指針指向的數據到底被控制了多少。我看了一下代碼,發現雖然我很好地控制住了緩存器,但在緩沖器里還是超過了0×100個字節。每次置換我都嘗試從不能控制的地方跳轉到緩沖器中我們的控制部分,但是都失敗了。那有其他的漏洞讓我們做到么?
CVE-2015-2899
這個漏洞是實現代碼執行如何控制重寫的函數指針所指向的數據的最后一個障礙。既然我們的數據在堆上,那么下一步就是看看是否能以某種方式直接溢出堆分配從而用可控的數據來填充它。所幸,我們正好有一個堆緩沖器溢出。
在上圖中,我們有一個經典的堆緩沖器溢出,因為目標緩沖器的大小是靜態的而且源字符串的長度不會被檢測。于是我決定嘗試以內存分配溢出到我的目標分配的方法來創建一個堆,這樣重寫的函數指針就會指向控制的數據。
在這里我需要讓內存持續分配不被釋放,最好還能控制大小。終于,我在一堆信息分析函數中找到了一個函數可以讓我隨便分配內存。在沒有對Windows 7的堆分配器進行真正研究的情況下,我直接測試哪些大小和分配數量是能達到我的需求。下面是我觀點驗證程序的一般結構。
正如預期的那樣,由于不清楚windows 7的堆分配器的內部工作原理,我的成果相當不可靠。另外,我還不清楚是否有其他內存的分配和釋放在我的控制之外。于是我需要一個能監視分配器分配和釋放的可視化工具。
正當我在為網上找到一個堆監視工具而高興時。我發現這些竟然都不是我想要的!他們要么就是拿內存快照然后讓你分析,要么就被嵌入在一個調試器里。我本還希望和庫hooking起來的函數不僅能共享還能運行64位。
不過我在網上找到一個叫Heapy的工具的源代碼。它使用了一個名叫MinHook的開源hooking庫來支持鉤子函數,還支持x86和x64體系結構。它也有hooking動態內存分配的示例代碼。于是我決定用這個MinHook來作為我工具的一部分。
堆監視器
有了新的堆分配可視化工具和代碼,我開始使用HeapMonitor(自己起的名兒)。我首先在DLL注入器寫入代碼連接一個服務級別的進程。然后我改變策略,將鉤子細節寫到一個文件里去而不是通過socket傳送。這樣就可以讓我的可視化GUI界面在所有系統上都適用。我還附加一個棧跟蹤方便追蹤負責調用的函數。
我決定用我最熟悉的Java寫GUI。我在界面的右側列表中實時顯示分配和釋放。主框架是顯示內存分頁的塊狀視圖,也會實時更新信息。主框架上的第二個選項卡列出了所有列表中被選的分配和釋放的棧追蹤。為了防止分配的內存頁出現劃分問題,我還在上端添加了內存導航欄通過內存快速導航。
為了更好地處理Windows 7堆分配器的內部工作,我查找了不少安全報告。其中對我工具最有用是Chris Valasek的“了解低碎片堆”, Steven Seeley的“分配器里的幽靈”,和Jeremy Fetiveau的“利用低碎片堆獲取利潤和樂趣”。通過我的堆可視化工具也證實了他們的研究。
CVE-2015-6006
在我操作里有一個很大的問題,每當我把分配設置為連續分配就總是會在達到0×1000個字節后自動關閉。這意味著,我的堆溢出必須相當大。在查看了關于Windows 7上堆分配器的最新研究后,我意識到這不正是因為啟用了低碎片堆么。之后通過堆監視器工具也證實了這一點。但是我依然沒辦法分配同樣的大小,因為CVE-2015-2899是靜態的,0XF0,和CVE-2015-2900必須至少0×100個字節(passed_alloc_size + 0×100)。
當我做到最后的時候,我承認要達到大致上100%的可靠性是不可能的。非常碰巧的是針對任意寫入漏洞(CVE-2015-2900)的調用函數有兩個bug。它在用戶提供的數據傳遞到任意寫入函數分配緩沖器之前就截斷成16位有符號的short型。該函數返回后,就將所有的用戶數據以字符串的形式拷貝到新創建的緩沖器。這意味著我們可以提供一個大于MAX_SHORT字符串導致之后的字符串拷貝發生堆溢出。這也意味著我們可以控制分配的大小。下圖是漏洞的代碼。
一旦低碎片堆被激活,我就能在按序分配中創建缺口并重新分配他們用于我的堆溢出。調整了一些額外的內存分配我的攻擊幾乎100%可行!
有興趣的話你可以在這里可以獲得堆監視器工具和源代碼。
在這里獲得完整的概念和Metasploit模塊。