CVE-2016-7290: 微軟office word的整數下溢漏洞分析
一、前言
這個下溢是當word處理特殊的二進制文檔文件時,在復制操作期間越界讀取時觸發,能夠導致winword.exe保護模式下棧的緩沖區溢出問題。
一切聽起來是戲劇性的,但是PoC觸發只需要越界讀取,然而本文將深入分析漏洞細節。
這個漏洞影響Microsoft Word 2007 Service Pack 3, Microsoft Office 2010 Service Pack 2 (32位版本), Microsoft Office 2010 Service Pack 2 (64位版本) 和 Microsoft Office Compatibility Pack Service Pack 3。更多的細節能從SRC-2016-0042獲取。本文所有的分析是基于Microsoft Office 2010 專業版的winword.exe(v14.0.4734.1000)。
首先,來看下sample和PoC文件的不同之處。
注意到只有一個字節的不同。接下來看下哪個結構塊包含了這個不同。
可以看到不同之處在于OneTableDocumentStream數據域中。Sample文件的值為0x68,而poc文件使用0xfa來觸發下溢。
二、觸發漏洞
首先,為了調試開啟頁堆和用戶態棧跟蹤:
- c:\Program Files\Debugging Tools for Windows (x86)>gflags.exe -i winword.exe +hpa +ust
- Current Registry Settings for winword.exe executable are: 02001000
- ust - Create user mode stack trace database
- hpa - Enable page heap
- c:\Program Files\Debugging Tools for Windows (x86)>
然后運行poc.doc文件導致以下保護模式外異常訪問:
- (880.ac4): Access violation - code c0000005 (first chance)
- First chance exceptions are reported before any exception handling.
- This exception may be expected and handled.
- eax=00000000 ebx=00000000 ecx=00000033 edx=00000002 esi=22870ffd edi=002513c4
- eip=744fb40c esp=0024c694 ebp=0024c69c iopl=0 nv up ei pl nz ac po nc
- cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210212
- MSVCR90!memmove+0xfc:
- 744fb40c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
- 0:000> kvn
- # ChildEBP RetAddr Args to Child
- 00 0024c69c 5e3f9b36 002513bf 22870ff8 000000d3 MSVCR90!memmove+0xfc
- WARNING: Stack unwind information not available. Following frames may be wrong.
- 01 0024c6b0 5e413843 22870ff8 002513bf 000000d3 wwlib!DllGetClassObject+0x455a
- 02 0024c744 5e413223 002513ac 002513a0 00004ab8 wwlib!GetAllocCounters+0xcadb
- 03 00251230 5e4131c6 002513ac 002513a0 00004ab8 wwlib!GetAllocCounters+0xc4bb
- 04 00251264 5e45f414 002513ac 002513a0 00004ab8 wwlib!GetAllocCounters+0xc45e
- 05 00251280 5e8da8a7 002513a0 22872fe4 00000000 wwlib!GetAllocCounters+0x586ac
- 06 002512b8 5e89fdcb 04760520 002513a0 ffffffff wwlib!DllGetLCID+0x2d4521
- 07 002567f4 5e66e957 1b132948 04760098 00000000 wwlib!DllGetLCID+0x299a45
- 08 002580e0 5e671d5b 04760098 00258928 00000001 wwlib!DllGetLCID+0x685d1
- 09 00258584 5e671489 04760098 00258928 1b132948 wwlib!DllGetLCID+0x6b9d5
- 0a 0025894c 5e675c10 04760098 00002490 00000000 wwlib!DllGetLCID+0x6b103
- 0b 00258998 5e4a6ad4 04760098 1b132948 0000056e wwlib!DllGetLCID+0x6f88a
- 0c 002589d4 64270be6 22562f10 0000056e 00000000 wwlib!GetAllocCounters+0x9fd6c
- 0d 002589f8 64270ebd 18bea880 18bea998 00258aa8 MSPTLS!FsTransformBbox+0x279b3
- 0e 00258a4c 64270f2c 22798de8 00258d40 00000000 MSPTLS!FsTransformBbox+0x27c8a
- 0f 00258aec 64271196 00258d40 00000000 00000000 MSPTLS!FsTransformBbox+0x27cf9
- 10 00258ca0 6425736a 22798de8 227f0ca0 00000000 MSPTLS!FsTransformBbox+0x27f63
- 11 00258db4 6428aa6f 22826fd0 00000000 00000000 MSPTLS!FsTransformBbox+0xe137
- 12 00258eac 6426fbb9 22798de8 227f0ca0 00000000 MSPTLS!FsTransformBbox+0x4183c
- 13 00259000 6425684e 22798de8 00000000 00000000 MSPTLS!FsTransformBbox+0x26986
三、調查訪問的內存
第一步我們要檢查在崩潰時訪問的內存。
- address 22870ffd found in
- _DPH_HEAP_ROOT @ 61000
- in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
- 227a13a8: 22870fe0 19 - 22870000 2000
- 67be8e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
- 77126206 ntdll!RtlDebugAllocateHeap+0x00000030
- 770ea127 ntdll!RtlpAllocateHeap+0x000000c4
- 770b5950 ntdll!RtlAllocateHeap+0x0000023a
- 5de2d804 mso!Ordinal149+0x000074b0
- 5e6a754d wwlib!DllGetLCID+0x000a11c7
- 5e7debc2 wwlib!DllGetLCID+0x001d883c
- 5e41f313 wwlib!GetAllocCounters+0x000185ab
- 5e41ec32 wwlib!GetAllocCounters+0x00017eca
- 5e41eb57 wwlib!GetAllocCounters+0x00017def
- 5e41e72a wwlib!GetAllocCounters+0x000179c2
- 5e423d89 wwlib!GetAllocCounters+0x0001d021
- 5e6acca5 wwlib!DllGetLCID+0x000a691f
- 5e422aa0 wwlib!GetAllocCounters+0x0001bd38
- 5e43ed59 wwlib!GetAllocCounters+0x00037ff1
- 5e43ec61 wwlib!GetAllocCounters+0x00037ef9
- 5e48f0c3 wwlib!GetAllocCounters+0x0008835b
- 5e48f050 wwlib!GetAllocCounters+0x000882e8
- 5e4a6aba wwlib!GetAllocCounters+0x0009fd52
- 64270be6 MSPTLS!FsTransformBbox+0x000279b3
- 64270ebd MSPTLS!FsTransformBbox+0x00027c8a
- 64270f2c MSPTLS!FsTransformBbox+0x00027cf9
- 64271196 MSPTLS!FsTransformBbox+0x00027f63
- 6425736a MSPTLS!FsTransformBbox+0x0000e137
- 6428aa6f MSPTLS!FsTransformBbox+0x0004183c
- 6426fbb9 MSPTLS!FsTransformBbox+0x00026986
- 6425684e MSPTLS!FsTransformBbox+0x0000d61b
- 6426ad48 MSPTLS!FsTransformBbox+0x00021b15
- 6428573e MSPTLS!FsTransformBbox+0x0003c50b
- 64285910 MSPTLS!FsTransformBbox+0x0003c6dd
- 64285c7b MSPTLS!FsTransformBbox+0x0003ca48
- 6426b17a MSPTLS!FsTransformBbox+0x00021f47
- 0:000> !address @edi
- ProcessParametrs 00069738 in range 00069000 0006a000
- Environment 02b233d8 in range 02b23000 02b24000
- 00160000 : 0023d000 - 00023000
- Type 00020000 MEM_PRIVATE
- Protect 00000004 PAGE_READWRITE
- State 00001000 MEM_COMMIT
- Usage RegionUsageStack
- Pid.Tid 880.ac4
- 0:000> dd @esi
- 22870ffd ???????? ???????? ???????? ????????
- 2287100d ???????? ???????? ???????? ????????
- 2287101d ???????? ???????? ???????? ????????
- 2287102d ???????? ???????? ???????? ????????
- 2287103d ???????? ???????? ???????? ????????
- 2287104d ???????? ???????? ???????? ????????
- 2287105d ???????? ???????? ???????? ????????
- 2287106d ???????? ???????? ???????? ????????
- 0:000> ?@ecx*4
- Evaluate expression: 204 = 000000cc
可以看到越界讀取了一個0x19字節的堆內存,試著將另外的204個字節復制到edi中。
正如結果,棧變量在頂上6個棧楨傳遞,下面的是通過其他變量和偏移動態計算的。沒有符號直接加大了跟蹤的難度。
四、寫內存
如果我們繼續從esi讀,那么可以假定繼續寫是安全的。我知道這是一個大的猜測,但是利用ole堆噴射或者得到使用eps的的堆,是有可能控制那個偏移的數據的。但是如何覆寫?我們看下目標棧地址:
- 0:000> !py mona do -a 002513c4 -s 0xcc
- Hold on...
- [+] Command used:
- !py mona.py do -a 002513c4 -s 0xcc
- ----------------------------------------------------
- [+] Dumping object at 0x002513c4, 0xcc bytes
- [+] Preparing output file 'dumpobj.txt'
- - (Re)setting logfile dumpobj.txt
- [+] Generating module info table, hang on...
- - Processing modules
- - Done. Let's rock 'n roll.
- >> Object at 0x002513c4 (0xcc bytes):
- Offset Address Contents Info
- ------ ------- -------- -----
- +00 0x002513c4 | 0x00000000
- +04 0x002513c8 | 0x000bd62f
- +08 0x002513cc | 0x00002001
- +0c 0x002513d0 | 0x0000ff00
- +10 0x002513d4 | 0xd63b0000
- +14 0x002513d8 | 0x8001000c
- +18 0x002513dc | 0xff000000
- +1c 0x002513e0 | 0x0100ffff
- +20 0x002513e4 | 0x00000000
- +24 0x002513e8 | 0x00000000
- +28 0x002513ec | 0xffffffff
- +2c 0x002513f0 | 0x00000000
- +30 0x002513f4 | 0x00000000
- +34 0x002513f8 | 0x00000000
- +38 0x002513fc | 0x00000000
- +3c 0x00251400 | 0x00000000
- +40 0x00251404 | 0xff000000
- +44 0x00251408 | 0x00000000
- +48 0x0025140c | 0xff000000
- +4c 0x00251410 | 0x00000000
- +50 0x00251414 | 0xff000000
- +54 0x00251418 | 0x00000c48
- +58 0x0025141c | 0xffffffff
- +5c 0x00251420 | 0x00000000
- +60 0x00251424 | 0xff000000
- +64 0x00251428 | 0x00000000
- +68 0x0025142c | 0xff000000
- +6c 0x00251430 | 0x00000000
- +70 0x00251434 | 0x1b132948 ptr to 0x5e52ee80 : wwlib!GetAllocCounters+0x128118
- +74 0x00251438 | 0xff000000
- +78 0x0025143c | 0x00000000
- +7c 0x00251440 | 0x00000000
- +80 0x00251444 | 0x00000000
- +84 0x00251448 | 0x00000000
- +88 0x0025144c | 0x00000000
- +8c 0x00251450 | 0xff000000
- +90 0x00251454 | 0x00000000
- +94 0x00251458 | 0x00000000
- +98 0x0025145c | 0x00000000
- +9c 0x00251460 | 0x00000000
- +a0 0x00251464 | 0x00000000
- +a4 0x00251468 | 0x00000000
- +a8 0x0025146c | 0x00000000
- +ac 0x00251470 | 0x00000000
- +b0 0x00251474 | 0x00000000
- +b4 0x00251478 | 0x00000000
- +b8 0x0025147c | 0x00000000
- +bc 0x00251480 | 0x00000000
- +c0 0x00251484 | 0x00000000
- +c4 0x00251488 | 0x00000000
- +c8 0x0025148c | 0x00000000
使用mona插件,能夠將拷貝剩余大小的棧地址轉儲,可以看見有個指針指向.text (wwlib!GetAllocCounters+0x128118)。如果沒猜錯,我們不應該覆寫這個值。
因此,我們可能溢出了一個棧緩沖區(可能性不大)。如果我們想命中返回地址,需要知道目的地址+0x1e8處才能出現。好奇之下能夠定位到這里:
- ...
- +cc 0x00251490 | 0xff700000
- +d0 0x00251494 | 0x00ffffff
- +d4 0x00251498 | 0x00000000
- +d8 0x0025149c | 0x00000000
- ...
- +1dc 0x002515a0 | 0x1b132be0
- +1e0 0x002515a4 | 0x0000005e
- +1e4 0x002515a8 | 0x002515c4 ptr to self+0x00000200
- +1e8 0x002515ac | 0x5e415bc1 wwlib!GetAllocCounters+0xee59
- [+] This mona.py action took 0:00:01.669000
- 0:000> ub 0x5e415bc1
- wwlib!GetAllocCounters+0xee41:
- 5e415ba9 5e pop esi
- 5e415baa 81fbffffff7f cmp ebx,7FFFFFFFh
- 5e415bb0 0f873e393c00 ja wwlib!DllGetLCID+0x1d316e (5e7d94f4)
- 5e415bb6 8b5508 mov edx,dword ptr [ebp+8]
- 5e415bb9 53 push ebx
- 5e415bba 50 push eax
- 5e415bbb 52 push edx
- 5e415bbc e8b9e9fdff call wwlib+0x457a (5e3f457a)
我們無法看見調用棧,因為棧向上伸展失敗了:
- 0:000> ?0x002515ac-@esp
- Evaluate expression: 20248 = 00004f18
接下來的問題是,怎么模擬繼續執行?
Bannedit編寫了一個很好的插件counterfeit,可以在windbg中看到用VirtualAlloc分配的塊并且用標記的數據填充它。我們能繼續并替換esi的值,繼續復制操作。
- 0:000> !py cf -a 2000 -f
- __ _____ .__ __
- ____ ____ __ __ _____/ |_ ____________/ ____\____ |__|/ |_
- _/ ___\/ _ \| | \/ \ __\/ __ \_ __ \ __\/ __ \| \ __\
- \ \__( <_> ) | / | \ | \ ___/| | \/| | \ ___/| || |
- \___ >____/|____/|___| /__| \___ >__| |__| \___ >__||__|
- \/ \/ \/ \/
- version 1.0 - bannedit
- Allocated memory @ 0x14130000 with RWX permissions.
- Filling memory...
- Finished filling memory.
- 0:000> dd 0x14130000
- 14130000 41414141 41414142 41414143 41414144
- 14130010 41414145 41414146 41414147 41414148
- 14130020 41414149 4141414a 4141414b 4141414c
- 14130030 4141414d 4141414e 4141414f 41414150
- 14130040 41414151 41414152 41414153 41414154
- 14130050 41414155 41414156 41414157 41414158
- 14130060 41414159 4141415a 4141415b 4141415c
- 14130070 4141415d 4141415e 4141415f 41414160
現在我們看到esi位于0x14130000:
- 0:000> g
- (880.ac4): Access violation - code c0000005 (!!! second chance !!!)
- eax=00000000 ebx=00000000 ecx=00000033 edx=00000002 esi=22870ffd edi=002513c4
- eip=744fb40c esp=0024c694 ebp=0024c69c iopl=0 nv up ei pl nz ac po nc
- cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210212
- MSVCR90!memmove+0xfc:
- 744fb40c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
- 0:000> r @esi=0x14130000
- ...
- 0:000> t
- eax=00000000 ebx=00000000 ecx=00000017 edx=00000002 esi=14130070 edi=00251434
- eip=744fb40c esp=0024c694 ebp=0024c69c iopl=0 nv up ei pl nz ac po nc
- cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210212
- MSVCR90!memmove+0xfc:
- 744fb40c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
- 0:000> dd @edi L1
- 00251434 1b132948
- 0:000> dds poi(@edi) L1
- 1b132948 5e52ee80 wwlib!GetAllocCounters+0x128118
- 0:000> u poi(poi(@edi))
- wwlib!GetAllocCounters+0x6e3b0:
- 5e475118 55 push ebp
- 5e475119 8bec mov ebp,esp
- 5e47511b 56 push esi
- 5e47511c 8bf1 mov esi,ecx
- 5e47511e e814000000 call wwlib!GetAllocCounters+0x6e3cf (5e475137)
- 5e475123 f6450801 test byte ptr [ebp+8],1
- 5e475127 7407 je wwlib!GetAllocCounters+0x6e3c8 (5e475130)
- 5e475129 56 push esi
- 0:000> t
- eax=00000000 ebx=00000000 ecx=00000016 edx=00000002 esi=14130074 edi=00251438
- eip=744fb40c esp=0024c694 ebp=0024c69c iopl=0 nv up ei pl nz ac po nc
- cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210212
- MSVCR90!memmove+0xfc:
- 744fb40c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
- 0:000> dds poi(@edi-4) L1
- 4141415d ????????
我們能看到我們覆寫數據的指針,指向了來自esi中值對應的函數。因為esi中的數據是標記過的,我們能知道用來覆寫指針的偏移。
五、曝光
再次觀察調用棧,注意到memmove()的調用。
- 0:000> kvn L2
- # ChildEBP RetAddr Args to Child
- 00 0024c69c 5e3f9b36 002513bf 22870ff8 000000d3 MSVCR90!memmove+0xfc
- WARNING: Stack unwind information not available. Following frames may be wrong.
- 01 0024c6b0 5e413843 22870ff8 002513bf 000000d3 wwlib!DllGetClassObject+0x455a
用Hex-Rays反編譯器,可以看到這個函數只是memmove()的一個封裝,并在wwlib庫中調用。可以重命名sub_316d9b16函數為memmove_wrapper_1。
- int __stdcall memmove_wrapper_1(void *Src, void *Dst, size_t Size)
- {
- int result; // eax@2
- if ( Size > 0x7FFFFFFF )
- result = MSO_1511(1647603307, 0);
- else
- result = (int)memmove(Dst, Src, Size);
- return result;
- }
如果大小大于MAX_INT,一個整形溢出異常被觸發。另外也沒有合理的校驗小雨目的緩沖區的情況。
為了利用,我們得知道memmove()如何訪問和被調用。
所以設置一個斷點bp wwlib!DllGetClassObject+0x4554 ".printf \"calling memmove(%x, %x, %x);\\n\", poi(@esp), poi(@esp+4), poi(@esp+8); gc"并重新運行PoC。
- calling memmove(271164, 26fb3c, e);
- calling memmove(271172, 26fb4a, f);
- calling memmove(271148, 2266efe0, 3);
- calling memmove(27114b, 2266efe3, 3);
- calling memmove(27114e, 2266efe6, 3);
- calling memmove(271151, 2266efe9, 3);
- calling memmove(271154, 2266efec, 3);
- calling memmove(271157, 2266efef, 4);
- calling memmove(27115b, 2266eff3, 5);
- calling memmove(27122e, 27115b, 5);
- calling memmove(27115b, 2266eff8, d3);
- (5f0.59c): Access violation - code c0000005 (first chance)
- First chance exceptions are reported before any exception handling.
- This exception may be expected and handled.
- eax=00000000 ebx=00000000 ecx=00000033 edx=00000002 esi=2266effd edi=00271160
- eip=744fb40c esp=0026c430 ebp=0026c438 iopl=0 nv up ei pl nz ac po nc
- cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210212
- MSVCR90!memmove+0xfc:
- 744fb40c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
有一系列的源緩沖區是0x2266efXX開頭的,并且目的緩沖區是0x002711YY。懷疑這是在一個錯誤的循環中多次調用memmove()。
我決定分析每個調用來判斷是否是獨特的。在windbg中執行‘k’命令不能繼續分割它,我們已經準備在上述斷點減緩執行。我決定用一個輕量的windbg插件來收集返回地址:
- from pykd import *
- mashed = 0
- for frame in getStack():
- mashed += frame.returnOffset
- print "stack hash: 0x%x" % mashed
- 0:000> !py sh
- stack hash: 0x199a6804c9
現在將它添加到我們的斷點,換一行并在末尾增加空格,最后重新運行:
- 0:010> bu wwlib!DllGetClassObject+0x4554 ".printf \"calling memmove(%x, %x, %x); \", poi(@esp), poi(@esp+4), poi(@esp+8); !py sh; gc"
- 0:010> g
- ...
- calling memmove(190fa4, 18f97c, e); stack hash: 0x18a96a3a98
- calling memmove(190fb2, 18f98a, f); stack hash: 0x18a96a3a98
- calling memmove(190f88, 49d7fe0, 3); stack hash: 0x1847ab6993
- calling memmove(190f8b, 49d7fe3, 3); stack hash: 0x1847ab6993
- calling memmove(190f8e, 49d7fe6, 3); stack hash: 0x1847ab6993
- calling memmove(190f91, 49d7fe9, 3); stack hash: 0x1847ab6993
- calling memmove(190f94, 49d7fec, 3); stack hash: 0x1847ab6993
- calling memmove(190f97, 49d7fef, 4); stack hash: 0x1847ab6993
- calling memmove(190f9b, 49d7ff3, 5); stack hash: 0x1847ab6993
- calling memmove(19106e, 190f9b, 5); stack hash: 0x1847ad8b4c
- calling memmove(190f9b, 49d7ff8, d3); stack hash: 0x1847ab6993
- (7dc.71c): Access violation - code c0000005 (first chance)
- First chance exceptions are reported before any exception handling.
- This exception may be expected and handled.
- eax=00000000 ebx=00000000 ecx=00000033 edx=00000002 esi=049d7ffd edi=00190fa0
- eip=744fb40c esp=0018c270 ebp=0018c278 iopl=0 nv up ei pl nz ac po nc
- cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210212
- MSVCR90!memmove+0xfc:
- 744fb40c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
現在可以判斷memmove() 在一個循環中被調用,因為在同一個棧哈希值0x1847ab6993。
六、影響
因為不能溢出返回地址和之后在寫或復制操作中會訪問和用到的一些值,這個漏洞的影響非常小。
Microsoft將此漏洞修補為“Microsoft Office信息泄露漏洞”,這在本文中介紹的上下文中說的通。然而,如果我們能夠在溢出覆蓋棧中.text中的一個指針,這個漏洞將影響更大。
在sub_316f3232函數中,有525處調用memmove_wrapper_1(),意味著有有多種途徑可以觸發這個漏洞。
另外在棧中沒有一個函數使用了/GS保護,意味著如果返回地址被覆蓋,沒有系統級別的緩解措施能夠緩解它。
七、總結
許多復雜的漏洞在office代碼中一直存在,只是難以被發現。甚至更難去調查根因并開發利用,如果微軟開放了符號表,將能更好的發現漏洞。