king

CVE-2016-7290: 微软office word的整数下溢漏洞分析

king 安全防护 2022-12-17 404浏览 0

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文件的不同之处。

CVE-2016-7290: 微软office word的整数下溢漏洞分析

注意到只有一个字节的不同。接下来看下哪个结构块包含了这个不同。

CVE-2016-7290: 微软office word的整数下溢漏洞分析

可以看到不同之处在于OneTableDocumentStream数据域中。Sample文件的值为0x68,而poc文件使用0xfa来触发下溢。

二、触发漏洞

首先,为了调试开启页堆和用户态栈跟踪:

c:\ProgramFiles\DebuggingToolsforWindows(x86)>gflags.exe-iwinword.exe+hpa+ust
CurrentRegistrySettingsforwinword.exeexecutableare:02001000
ust-Createusermodestacktracedatabase
hpa-Enablepageheap
c:\ProgramFiles\DebuggingToolsforWindows(x86)>

然后运行poc.doc文件导致以下保护模式外异常访问:

(880.ac4):Accessviolation-codec0000005(firstchance)
Firstchanceexceptionsarereportedbeforeanyexceptionhandling.
Thisexceptionmaybeexpectedandhandled.
eax=00000000ebx=00000000ecx=00000033edx=00000002esi=22870ffdedi=002513c4
eip=744fb40cesp=0024c694ebp=0024c69ciopl=0nvupeiplnzacponc
cs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00210212
MSVCR90!memmove+0xfc:
744fb40cf3a5repmovsdwordptres:[edi],dwordptr[esi]
0:000>kvn
#ChildEBPRetAddrArgstoChild
000024c69c5e3f9b36002513bf22870ff8000000d3MSVCR90!memmove+0xfc
WARNING:Stackunwindinformationnotavailable.Followingframesmaybewrong.
010024c6b05e41384322870ff8002513bf000000d3wwlib!DllGetClassObject+0x455a
020024c7445e413223002513ac002513a000004ab8wwlib!GetAllocCounters+0xcadb
03002512305e4131c6002513ac002513a000004ab8wwlib!GetAllocCounters+0xc4bb
04002512645e45f414002513ac002513a000004ab8wwlib!GetAllocCounters+0xc45e
05002512805e8da8a7002513a022872fe400000000wwlib!GetAllocCounters+0x586ac
06002512b85e89fdcb04760520002513a0ffffffffwwlib!DllGetLCID+0x2d4521
07002567f45e66e9571b1329480476009800000000wwlib!DllGetLCID+0x299a45
08002580e05e671d5b047600980025892800000001wwlib!DllGetLCID+0x685d1
09002585845e67148904760098002589281b132948wwlib!DllGetLCID+0x6b9d5
0a0025894c5e675c10047600980000249000000000wwlib!DllGetLCID+0x6b103
0b002589985e4a6ad4047600981b1329480000056ewwlib!DllGetLCID+0x6f88a
0c002589d464270be622562f100000056e00000000wwlib!GetAllocCounters+0x9fd6c
0d002589f864270ebd18bea88018bea99800258aa8MSPTLS!FsTransformBbox+0x279b3
0e00258a4c64270f2c22798de800258d4000000000MSPTLS!FsTransformBbox+0x27c8a
0f00258aec6427119600258d400000000000000000MSPTLS!FsTransformBbox+0x27cf9
1000258ca06425736a22798de8227f0ca000000000MSPTLS!FsTransformBbox+0x27f63
1100258db46428aa6f22826fd00000000000000000MSPTLS!FsTransformBbox+0xe137
1200258eac6426fbb922798de8227f0ca000000000MSPTLS!FsTransformBbox+0x4183c
13002590006425684e22798de80000000000000000MSPTLS!FsTransformBbox+0x26986

三、调查访问的内存

第一步我们要检查在崩溃时访问的内存。

address22870ffdfoundin
_DPH_HEAP_ROOT@61000
inbusyallocation(DPH_HEAP_BLOCK:UserAddrUserSize-VirtAddrVirtSize)
227a13a8:22870fe019-228700002000
67be8e89verifier!AVrfDebugPageHeapAllocate+0x00000229
77126206ntdll!RtlDebugAllocateHeap+0x00000030
770ea127ntdll!RtlpAllocateHeap+0x000000c4
770b5950ntdll!RtlAllocateHeap+0x0000023a
5de2d804mso!Ordinal149+0x000074b0
5e6a754dwwlib!DllGetLCID+0x000a11c7
5e7debc2wwlib!DllGetLCID+0x001d883c
5e41f313wwlib!GetAllocCounters+0x000185ab
5e41ec32wwlib!GetAllocCounters+0x00017eca
5e41eb57wwlib!GetAllocCounters+0x00017def
5e41e72awwlib!GetAllocCounters+0x000179c2
5e423d89wwlib!GetAllocCounters+0x0001d021
5e6acca5wwlib!DllGetLCID+0x000a691f
5e422aa0wwlib!GetAllocCounters+0x0001bd38
5e43ed59wwlib!GetAllocCounters+0x00037ff1
5e43ec61wwlib!GetAllocCounters+0x00037ef9
5e48f0c3wwlib!GetAllocCounters+0x0008835b
5e48f050wwlib!GetAllocCounters+0x000882e8
5e4a6abawwlib!GetAllocCounters+0x0009fd52
64270be6MSPTLS!FsTransformBbox+0x000279b3
64270ebdMSPTLS!FsTransformBbox+0x00027c8a
64270f2cMSPTLS!FsTransformBbox+0x00027cf9
64271196MSPTLS!FsTransformBbox+0x00027f63
6425736aMSPTLS!FsTransformBbox+0x0000e137
6428aa6fMSPTLS!FsTransformBbox+0x0004183c
6426fbb9MSPTLS!FsTransformBbox+0x00026986
6425684eMSPTLS!FsTransformBbox+0x0000d61b
6426ad48MSPTLS!FsTransformBbox+0x00021b15
6428573eMSPTLS!FsTransformBbox+0x0003c50b
64285910MSPTLS!FsTransformBbox+0x0003c6dd
64285c7bMSPTLS!FsTransformBbox+0x0003ca48
6426b17aMSPTLS!FsTransformBbox+0x00021f47
0:000>!address@edi
ProcessParametrs00069738inrange000690000006a000
Environment02b233d8inrange02b2300002b24000
00160000:0023d000-00023000
Type00020000MEM_PRIVATE
Protect00000004PAGE_READWRITE
State00001000MEM_COMMIT
UsageRegionUsageStack
Pid.Tid880.ac4
0:000>dd@esi
22870ffd????????????????????????????????
2287100d????????????????????????????????
2287101d????????????????????????????????
2287102d????????????????????????????????
2287103d????????????????????????????????
2287104d????????????????????????????????
2287105d????????????????????????????????
2287106d????????????????????????????????
0:000>?@ecx*4
Evaluateexpression:204=000000cc

可以看到越界读取了一个0x19字节的堆内存,试着将另外的204个字节复制到edi中。

正如结果,栈变量在顶上6个栈桢传递,下面的是通过其他变量和偏移动态计算的。没有符号直接加大了跟踪的难度。

四、写内存

如果我们继续从esi读,那么可以假定继续写是安全的。我知道这是一个大的猜测,但是利用ole堆喷射或者得到使用eps的的堆,是有可能控制那个偏移的数据的。但是如何覆写?我们看下目标栈地址:

0:000>!pymonado-a002513c4-s0xcc
Holdon...
[+]Commandused:
!pymona.pydo-a002513c4-s0xcc
----------------------------------------------------
[+]Dumpingobjectat0x002513c4,0xccbytes
[+]Preparingoutputfile'dumpobj.txt'
-(Re)settinglogfiledumpobj.txt
[+]Generatingmoduleinfotable,hangon...
-Processingmodules
-Done.Let'srock'nroll.
>>Objectat0x002513c4(0xccbytes):
OffsetAddressContentsInfo
--------------------------
+000x002513c4|0x00000000
+040x002513c8|0x000bd62f
+080x002513cc|0x00002001
+0c0x002513d0|0x0000ff00
+100x002513d4|0xd63b0000
+140x002513d8|0x8001000c
+180x002513dc|0xff000000
+1c0x002513e0|0x0100ffff
+200x002513e4|0x00000000
+240x002513e8|0x00000000
+280x002513ec|0xffffffff
+2c0x002513f0|0x00000000
+300x002513f4|0x00000000
+340x002513f8|0x00000000
+380x002513fc|0x00000000
+3c0x00251400|0x00000000
+400x00251404|0xff000000
+440x00251408|0x00000000
+480x0025140c|0xff000000
+4c0x00251410|0x00000000
+500x00251414|0xff000000
+540x00251418|0x00000c48
+580x0025141c|0xffffffff
+5c0x00251420|0x00000000
+600x00251424|0xff000000
+640x00251428|0x00000000
+680x0025142c|0xff000000
+6c0x00251430|0x00000000
+700x00251434|0x1b132948ptrto0x5e52ee80:wwlib!GetAllocCounters+0x128118
+740x00251438|0xff000000
+780x0025143c|0x00000000
+7c0x00251440|0x00000000
+800x00251444|0x00000000
+840x00251448|0x00000000
+880x0025144c|0x00000000
+8c0x00251450|0xff000000
+900x00251454|0x00000000
+940x00251458|0x00000000
+980x0025145c|0x00000000
+9c0x00251460|0x00000000
+a00x00251464|0x00000000
+a40x00251468|0x00000000
+a80x0025146c|0x00000000
+ac0x00251470|0x00000000
+b00x00251474|0x00000000
+b40x00251478|0x00000000
+b80x0025147c|0x00000000
+bc0x00251480|0x00000000
+c00x00251484|0x00000000
+c40x00251488|0x00000000
+c80x0025148c|0x00000000

使用mona插件,能够将拷贝剩余大小的栈地址转储,可以看见有个指针指向.text (wwlib!GetAllocCounters+0x128118)。如果没猜错,我们不应该覆写这个值。

因此,我们可能溢出了一个栈缓冲区(可能性不大)。如果我们想命中返回地址,需要知道目的地址+0x1e8处才能出现。好奇之下能够定位到这里:

...
+cc0x00251490|0xff700000
+d00x00251494|0x00ffffff
+d40x00251498|0x00000000
+d80x0025149c|0x00000000
...
+1dc0x002515a0|0x1b132be0
+1e00x002515a4|0x0000005e
+1e40x002515a8|0x002515c4ptrtoself+0x00000200
+1e80x002515ac|0x5e415bc1wwlib!GetAllocCounters+0xee59
[+]Thismona.pyactiontook0:00:01.669000
0:000>ub0x5e415bc1
wwlib!GetAllocCounters+0xee41:
5e415ba95epopesi
5e415baa81fbffffff7fcmpebx,7FFFFFFFh
5e415bb00f873e393c00jawwlib!DllGetLCID+0x1d316e(5e7d94f4)
5e415bb68b5508movedx,dwordptr[ebp+8]
5e415bb953pushebx
5e415bba50pusheax
5e415bbb52pushedx
5e415bbce8b9e9fdffcallwwlib+0x457a(5e3f457a)

我们无法看见调用栈,因为栈向上伸展失败了:

0:000>?0x002515ac-@esp

Evaluateexpression:20248=00004f18

接下来的问题是,怎么模拟继续执行?

Bannedit编写了一个很好的插件counterfeit,可以在windbg中看到用VirtualAlloc分配的块并且用标记的数据填充它。我们能继续并替换esi的值,继续复制操作。

0:000>!pycf-a2000-f
_______.____
_________________/|_____________/____\____|__|/|_
_/___\/_\||\/\__\/__\___\__\/__\|\__\
\\__(<_>)|/|\|\___/||\/||\___/||||
\___>____/|____/|___|/__|\___>__||__|\___>__||__|
\/\/\/\/
version1.0-bannedit
Allocatedmemory@0x14130000withRWXpermissions.
Fillingmemory...
Finishedfillingmemory.
0:000>dd0x14130000
1413000041414141414141424141414341414144
1413001041414145414141464141414741414148
14130020414141494141414a4141414b4141414c
141300304141414d4141414e4141414f41414150
1413004041414151414141524141415341414154
1413005041414155414141564141415741414158
14130060414141594141415a4141415b4141415c
141300704141415d4141415e4141415f41414160

现在我们看到esi位于0x14130000:

0:000>g
(880.ac4):Accessviolation-codec0000005(!!!secondchance!!!)
eax=00000000ebx=00000000ecx=00000033edx=00000002esi=22870ffdedi=002513c4
eip=744fb40cesp=0024c694ebp=0024c69ciopl=0nvupeiplnzacponc
cs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00210212
MSVCR90!memmove+0xfc:
744fb40cf3a5repmovsdwordptres:[edi],dwordptr[esi]
0:000>r@esi=0x14130000
...
0:000>t
eax=00000000ebx=00000000ecx=00000017edx=00000002esi=14130070edi=00251434
eip=744fb40cesp=0024c694ebp=0024c69ciopl=0nvupeiplnzacponc
cs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00210212
MSVCR90!memmove+0xfc:
744fb40cf3a5repmovsdwordptres:[edi],dwordptr[esi]
0:000>dd@ediL1
002514341b132948
0:000>ddspoi(@edi)L1
1b1329485e52ee80wwlib!GetAllocCounters+0x128118
0:000>upoi(poi(@edi))
wwlib!GetAllocCounters+0x6e3b0:
5e47511855pushebp
5e4751198becmovebp,esp
5e47511b56pushesi
5e47511c8bf1movesi,ecx
5e47511ee814000000callwwlib!GetAllocCounters+0x6e3cf(5e475137)
5e475123f6450801testbyteptr[ebp+8],1
5e4751277407jewwlib!GetAllocCounters+0x6e3c8(5e475130)
5e47512956pushesi
0:000>t
eax=00000000ebx=00000000ecx=00000016edx=00000002esi=14130074edi=00251438
eip=744fb40cesp=0024c694ebp=0024c69ciopl=0nvupeiplnzacponc
cs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00210212
MSVCR90!memmove+0xfc:
744fb40cf3a5repmovsdwordptres:[edi],dwordptr[esi]
0:000>ddspoi(@edi-4)L1
4141415d????????

我们能看到我们覆写数据的指针,指向了来自esi中值对应的函数。因为esi中的数据是标记过的,我们能知道用来覆写指针的偏移。

五、曝光

再次观察调用栈,注意到memmove()的调用。

0:000>kvnL2
#ChildEBPRetAddrArgstoChild
000024c69c5e3f9b36002513bf22870ff8000000d3MSVCR90!memmove+0xfc
WARNING:Stackunwindinformationnotavailable.Followingframesmaybewrong.
010024c6b05e41384322870ff8002513bf000000d3wwlib!DllGetClassObject+0x455a

用Hex-Rays反编译器,可以看到这个函数只是memmove()的一个封装,并在wwlib库中调用。可以重命名sub_316d9b16函数为memmove_wrapper_1。

int__stdcallmemmove_wrapper_1(void*Src,void*Dst,size_tSize)
{
intresult;//eax@2
if(Size>0x7FFFFFFF)
result=MSO_1511(1647603307,0);
else
result=(int)memmove(Dst,Src,Size);
returnresult;
}

如果大小大于MAX_INT,一个整形溢出异常被触发。另外也没有合理的校验小雨目的缓冲区的情况。

为了利用,我们得知道memmove()如何访问和被调用。

所以设置一个断点bp wwlib!DllGetClassObject+0x4554 ".printf \"calling memmove(%x, %x, %x);\\n\", poi(@esp), poi(@esp+4), poi(@esp+8); gc"并重新运行PoC。

callingmemmove(271164,26fb3c,e);
callingmemmove(271172,26fb4a,f);
callingmemmove(271148,2266efe0,3);
callingmemmove(27114b,2266efe3,3);
callingmemmove(27114e,2266efe6,3);
callingmemmove(271151,2266efe9,3);
callingmemmove(271154,2266efec,3);
callingmemmove(271157,2266efef,4);
callingmemmove(27115b,2266eff3,5);
callingmemmove(27122e,27115b,5);
callingmemmove(27115b,2266eff8,d3);
(5f0.59c):Accessviolation-codec0000005(firstchance)
Firstchanceexceptionsarereportedbeforeanyexceptionhandling.
Thisexceptionmaybeexpectedandhandled.
eax=00000000ebx=00000000ecx=00000033edx=00000002esi=2266effdedi=00271160
eip=744fb40cesp=0026c430ebp=0026c438iopl=0nvupeiplnzacponc
cs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00210212
MSVCR90!memmove+0xfc:
744fb40cf3a5repmovsdwordptres:[edi],dwordptr[esi]

有一系列的源缓冲区是0x2266efXX开头的,并且目的缓冲区是0x002711YY。怀疑这是在一个错误的循环中多次调用memmove()。

我决定分析每个调用来判断是否是独特的。在windbg中执行‘k’命令不能继续分割它,我们已经准备在上述断点减缓执行。我决定用一个轻量的windbg插件来收集返回地址:

frompykdimport*
mashed=0
forframeingetStack():
mashed+=frame.returnOffset
print"stackhash:0x%x"%mashed
0:000>!pysh
stackhash:0x199a6804c9

现在将它添加到我们的断点,换一行并在末尾增加空格,最后重新运行:

0:010>buwwlib!DllGetClassObject+0x4554".printf\"callingmemmove(%x,%x,%x);\",poi(@esp),poi(@esp+4),poi(@esp+8);!pysh;gc"
0:010>g
...
callingmemmove(190fa4,18f97c,e);stackhash:0x18a96a3a98
callingmemmove(190fb2,18f98a,f);stackhash:0x18a96a3a98
callingmemmove(190f88,49d7fe0,3);stackhash:0x1847ab6993
callingmemmove(190f8b,49d7fe3,3);stackhash:0x1847ab6993
callingmemmove(190f8e,49d7fe6,3);stackhash:0x1847ab6993
callingmemmove(190f91,49d7fe9,3);stackhash:0x1847ab6993
callingmemmove(190f94,49d7fec,3);stackhash:0x1847ab6993
callingmemmove(190f97,49d7fef,4);stackhash:0x1847ab6993
callingmemmove(190f9b,49d7ff3,5);stackhash:0x1847ab6993
callingmemmove(19106e,190f9b,5);stackhash:0x1847ad8b4c
callingmemmove(190f9b,49d7ff8,d3);stackhash:0x1847ab6993
(7dc.71c):Accessviolation-codec0000005(firstchance)
Firstchanceexceptionsarereportedbeforeanyexceptionhandling.
Thisexceptionmaybeexpectedandhandled.
eax=00000000ebx=00000000ecx=00000033edx=00000002esi=049d7ffdedi=00190fa0
eip=744fb40cesp=0018c270ebp=0018c278iopl=0nvupeiplnzacponc
cs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00210212
MSVCR90!memmove+0xfc:
744fb40cf3a5repmovsdwordptres:[edi],dwordptr[esi]

现在可以判断memmove() 在一个循环中被调用,因为在同一个栈哈希值0x1847ab6993。

六、影响

因为不能溢出返回地址和之后在写或复制操作中会访问和用到的一些值,这个漏洞的影响非常小。

Microsoft将此漏洞修补为“Microsoft Office信息泄露漏洞”,这在本文中介绍的上下文中说的通。然而,如果我们能够在溢出覆盖栈中.text中的一个指针,这个漏洞将影响更大。

在sub_316f3232函数中,有525处调用memmove_wrapper_1(),意味着有有多种途径可以触发这个漏洞。

另外在栈中没有一个函数使用了/GS保护,意味着如果返回地址被覆盖,没有系统级别的缓解措施能够缓解它。

七、总结

许多复杂的漏洞在office代码中一直存在,只是难以被发现。甚至更难去调查根因并开发利用,如果微软开放了符号表,将能更好的发现漏洞。

继续浏览有关 安全 的文章
发表评论