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