作者:xd0ol1@知道創宇404實驗室

0 引子

在上一篇文章中,我們分析了 Office 文檔型漏洞 CVE-2015-1641 的利用,本文將繼續對此類漏洞中的另一常見案例 CVE-2015-2545(MS15-099)展開分析。相較而言,這些 Exp 的威脅性更大,例如可采用“Word EPS + Windows EoP”的組合,且很多地方借鑒了瀏覽器漏洞的利用思路,因此還是很值得我們學習研究的。

1 樣本信息

分析中用到的樣本信息如下:

SHA256:3a65d4b3bc18352675cd02154ffb388035463089d59aad36cadb1646f3a3b0fc
Size:420,577 bytes
Type:Office Open XML Document

我們將此文件的后綴名改為 zip,解壓后可得到如下目錄結構:

圖0  樣本通過 zip 解壓后的目錄結構

其中,image1.eps 是精心設計的漏洞利用文件,即由 PostScript 語言編寫的特殊圖形文件,這里 Word 和 PostScript 的關系一定層度上可類比為 IE 瀏覽器和 JavaScript 的關系,更多關于 PostScript 語言的說明可參考該手冊

此外,本文的分析環境為 Win7 x86+Office 2007 SP3,EPSIMP32 模塊的版本信息如下:

圖1  EPSIMP32 模塊的版本信息

2 漏洞原理分析

首先我們看下原理,簡單來說就是 Word 程序在解析 EPS(Encapsulated PostScript)圖形文件時存在一個 UAF(Use-After-Free)的漏洞,其錯誤代碼位于 EPSIMP32 模塊。為了便于理解,我們給出樣本中觸發此漏洞的那部分 PostScript 代碼,當然有經過一定的反混淆處理:

圖2  觸發漏洞的那部分 PostScript 代碼(PoC)

其中操作符 copy 和 forall 的定義如下:

圖3  dict 操作時 copy 和 forall 的定義

結合上述代碼,我們給出漏洞原理更為具體的描述:當通過 forall 操作 dict2 對象時,將對 dict2 中的 ‘key-value’ 進行迭代處理,且 pNext 指針指向下一對待處理的 ‘key-value’。然而,proc 中存在 dict1 dict2 copy 的操作,此過程會先釋放掉 dict2 原有的 ‘key-value’ 空間,之后再申請新空間進行接下來的拷貝,即原先 pNext 指向的 ‘key-value’ 空間被釋放了。而后在 putinterval 操作中將重新用到原先 pNext 指向的空間,并向其中寫入特定的字符串。因此,在下一次迭代時,pNext 指向的數據就變成了我們所構造的 ‘key-value’。

接著我們來完整分析下此過程,這里給出 PostScript 對象和 dict 下 ‘key-value’ 對象的定義,它們在后面會涉及到:

//PostScript對象的定義
struct PostScript_object {
    dword type;
    dword attr;
    dword value1;
    dword value2;
} ps_obj;

//字典‘key-value’對象的定義
struct Dictionary_key_value {
    dword *pNext;
    dword dwIndex;
    ps_obj key;
    ps_obj value;
} dict_kv;

就每個 PostScript 操作符而言,都有一個具體的處理函數與之對應,我們可以很方便的由 IDA 進行查看,之后通過相對偏移的計算就可以在 OllyDBG 中定位到關鍵點了:

圖4  操作符對應的處理函數

借助如下斷點我們將在進程加載 EPSIMP32 模塊時斷下來:

bp LoadLibraryW, UNICODE [dword ptr [esp + 0x04] + 0x6e] == “EPSIMP32.FLT”

圖5  WINWORD 進程加載 EPSIMP32 模塊

很自然的我們會想到在 forall 的對應函數上下斷,可以得到與 dict 操作迭代處理相關的代碼段如下,其中 EPSIMP32 的模塊基址為 0x73790000:

圖6  dict 在 forall 操作時的迭代處理

此過程包含4個 call 調用,其中第一個 call 用于獲取當前要處理的 ‘key-value’ 和指針 pNext,即指向下次處理的 ‘key-value’,而第二個和第三個 call 分別用于將 key 和 value 存儲到操作棧上,最后的第四個 call 則用于處理 proc 中的操作。

我們來跟一下,在第一個 call 調用時,ecx 寄存器指向的內容為 dict2 內部 hash-table的 指針、hash-table 的大小以及包含的 ‘key-value’ 個數:

圖7  ecx 寄存器指向的 hash-table

此調用執行完成后,我們會得到 keyZ1 和指向 keyZ2 的指針:

圖8  keyZ1 及指向 keyZ2 的指針

而當第二個和第三個 call 調用完成后,我們可以看到 keyZ1 的 key 和 value 被存儲到了操作棧上:

圖9  將 keyZ1 存儲到操作棧上

在第四個 call 調用中,對于 proc 的各操作符,首先會獲取對應處理函數的地址,而后以虛函數的方式進行調用,相關代碼片段如下:

圖10  調用操作符的處理函數

這里我們主要關注 copy 操作,由分析可知,在其處理過程中會將 dict2 內部 hash-table 上對應的所有 ‘key-value’ 空間都釋放掉,即上述 pNext 指向的 keyZ2 空間被釋放掉了,如下給出的是進行該 delete 操作的函數入口:

圖11  delete ‘key-value’ 的函數入口

同樣,此時入參 ecx 寄存器指向的內容中包含了 dict2 的 hash-table 指針,接下去的操作將逐次釋放 keyZ1~keyZ8 的空間,最后 hash-table 也會被釋放掉:

圖12  釋放 dict2 上的 ‘key-value’ 空間

而釋放的 keyZ2 空間,即 pNext 指向的空間,將在隨后的 putinterval 操作中被重新寫入特定的偽造數據:

圖13  由 putinterval 操作寫入偽造數據

因此,在 forall 的下一次迭代過程中,根據 pNext 指針獲取的 ‘key-value’ 就變成了我們所偽造的數據,并且之后同樣被存儲到了操作棧上:

圖14  偽造的 ‘key-value’

3 漏洞利用分析

這里我們接著上一節的內容來繼續跟下漏洞的利用,此時偽造的 ‘key-value’ 已經被存儲到了操作棧上,下述給出的是本次迭代中 forall 操作所處理的 proc 代碼:

圖15  第二次迭代時處理的 proc 代碼

也就是將操作棧上的 key 和 value 分別賦給 xx_19169 以及 xx_26500,操作完成后得到的 xx_19169 如下:

圖16  xx_19169 中的內容

可以看到,xx_19169 的 type 字段為 0x00000003,即表示的是整型,所以對于本文的分析環境來說,接下去的處理過程將會按照 “old version” 的分支來進行:

圖17  不同版本執行分支的選擇

xx_26500 則是實現漏洞利用的關鍵,由圖18可知它的 type 字段為 0x00000500,表明這是一個string類型,且 value2 字段為泄露出來的指針,在此基礎上經過一系列構造后,可得到 string 對象如下:

圖18  獲取 RW primitives

在 PostScript 中會為每個 string 對象分配專門的 buffer 用于存儲實際的字符串內容,其基址及大小就保存在該 string 對象中。就最終樣本偽造的 string 對象來說,其 buffer 基址為 0x00000000,且大小為 0x7fffffff,因此借助此對象可以實現任意內存的讀寫。之后代碼會通過獲取的 RW primitives 來查找 ROP gadgets,從而創建 ROP 鏈,同時由 putinterval 操作將 shellcode 和 payload 寫入內存:

圖19  創建 ROP 鏈并寫入 shellcode 和 payload

之后再通過修改操作符 bytesavailable 處理函數中的如下 call 指針跳轉到 ROP 鏈上:

圖20  控制 EIP 跳轉到 ROP 鏈

其中,ROP 鏈包含的指令如下,可以看到首先進行的是 stack pivot 操作,接著會將 shellcode 所在的頁屬性置為可執行,最后跳轉到 shellcode 的入口:

圖21  ROP 鏈中的操作指令

這里借助了一個小技巧來繞過保護程序對 ZwProtectVirtualMemory 調用的檢測,對于 ntdll 模塊中的 Nt/Zw 函數,除了賦給 eax 寄存器的 id 不同外,其余部分都是相同的。ROP 鏈在完成 eax 的賦值后,也就是將 ZwProtectVirtualMemory 函數中的 id 賦給 eax 后,會直接跳過 ZwCreateEvent 函數(該函數未被 hook)的前5字節并執行余下的那部分指令,通過這種方式能實現任意的系統調用而不會被檢測到:

圖22  繞過保護程序對 ZwProtectVirtualMemory 調用的檢測

下面我們再來簡單看下 shellcode,和大多數情況一樣,它的主要作用就是獲取相關的 API 函數,然后創建并執行 payload 文件。樣本中 shellcode 的部分數據經過了加密處理,因此會有一個解密的操作:

圖23  對 shellcode 中的數據進行解密

而后,代碼通過查找 LDR 鏈的方式來獲取 msvcrt 模塊的基址:

圖24  獲取 msvcrt 模塊的基址

之后從 msvcrt 模塊的導入表中得到函數 GetModuleHandleA 和 GetProcAddress 的入口地址,由 GetModuleHandleA 函數可以獲取到 kernel32 模塊的句柄,最后再借助 GetProcAddress 調用來逐個獲取下述的導出函數地址:

圖25  獲取相關的 API 函數

緊接著 payload 的內容,即圖19所示代碼中介于首尾字符串 “5555555566666666” 之間的那部分數據,會被寫入到臨時目錄下的 plugin.dll 文件中,分析可知這是一個惡意的程序:

圖26  樣本創建的惡意 dll 文件

通過 LoadLibraryA 函數加載該 plugin.dll 模塊后,將會在臨時目錄下另外再釋放一個名為 igfxe.exe 的程序,其作用是獲取遠程文件并執行之:

圖27  釋放的 igfxe.exe 程序

4 結語

本文基于樣本文檔分析了 CVE-2015-2545 的利用,然鑒于筆者就 PostScript 語言所知尚少,固有些點也是沒能給講透徹,希望能有更多這類漏洞的分析文章出現。另外,錯誤之處還望各位加以斧正,歡迎一起交流:P

5 參考

[1] The EPS Awakens
[2] Microsoft Office Encapsulated PostScript and Windows Privilege Escalation Zero-Days
[3] 警惕利用Microsoft Office EPS漏洞進行的攻擊
[4] 針對CVE-2015-2545漏洞研究分析
[5] 文檔型漏洞攻擊研究報告
[6] PostScript Language Reference Manual
[7] How the EPS File Exploit Works to Bypass EMET (CVE-2015-2545)
[8] CVE-2015-2545 ITW EMET Evasion


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