导语:最近,微软发布了MS16-148用于修补CVE-2016-7290,其中涉及一个整数溢出问题。溢出后触发一个超出范围的复制操作,在非保护模式下处理特制的二进制文档文件,Winword.exe进程可能导致基于堆栈的缓冲区溢出。

最近,微软发布了MS16-148用于修补CVE-2016-7290,其中涉及一个整数溢出问题。溢出后触发一个超出范围的复制操作,在非保护模式下处理特制的二进制文档文件,Winword.exe进程可能导致基于堆栈的缓冲区溢出。在这篇博客文章中,我会详细的描述该漏洞。

这个安全漏洞影响微软Word 2007的Service Pack 3,Microsoft Office 2010的Service Pack 2(32位版本)、Microsoft Office 2010的Service Pack 2(64位版本)、Microsoft Office兼容包Service Pack 3。在微软Office 2010专业的WinWord.exe进行所有的分析v14.0.4734.1000,当时的最新补丁。

首先,让我们来看看样本和poc的比较:

图片1.png

通过比较你可能会注意到,只存在一个单一的字节修改。使用Offviz,我们可以看一下,对哪些块修改。

1482382575714441.png

修改发生在OneTableDocumentStream块的数据字段内。该示例包含字节值为0x68,但是POC使用0xFA,造成了向下溢出。

0x0触发漏洞

首先,设置页堆和用户模式堆栈跟踪调试:

图片3.png

然后在非护模式下运行导致crash的poc.doc文件:

图片4.png

0x1检查溢出内存

第一件事就是开始检查crash处的访问内存。

0:000> !heap -p -a @esi
    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
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  ???????? ???????? ???????? ????????
0:000> [email protected]*4
Evaluate expression: 204 = 000000cc

我们已经可以看到,这是一个越界读取堆缓冲区的漏洞,堆的空间是0x19个字节,却将204字节复制到@edi地址读处,造成越界。有人可能会问,堆栈变量的大小是多少?

事实证明,堆栈变量似乎动态地从其他一些变量计算偏移。在没有符号的情况下很难追踪其地址。

0x2内存写入

如果我们能够继续从@esi读取数据,那么我们可以继续进行写入。我知道这是一个巨大的假设,但与OLE堆喷射相比,我们可以控制偏移数据是很有可能的。是否可以重写呢?让我们来看看目的堆栈地址:

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  
…
+64     0x00251428 | 0x00000000  
+68     0x0025142c | 0xff000000  
+6c     0x00251430 | 0x00000000  
+70     0x00251434 | 0x1b132948  ptr to 0x5e52ee80 : wwlib!GetAllocCounters+0x128118
+74     0x00251438 | 0xff000000  
+78     0x0025143c | 0x00000000

使用mona插件,我们可以dump目的堆栈地址,可以看到,有一个指针指向.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> [email protected]
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包含标记的数据,我们可以知道在具体的偏移地址重写指针。

0:000> ?0x5d-0x41
Evaluate expression: 28 = 0000001c
0:000> !py mona do -a 002513c4 -s 0x78
Hold on...
[+] Command used:
!py mona.py do -a 002513c4 -s 0x78
----------------------------------------------------
[+] Dumping object at 0x002513c4, 0x78 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 (0x78 bytes):
Offset  Address      Contents    Info
------  -------      --------    -----
+00     0x002513c4 | 0x41414141  = ASCII 'AAAA'
+04     0x002513c8 | 0x41414142  = ASCII 'AAAB'
…
+54     0x00251418 | 0x41414156  = ASCII 'AAAV'
+58     0x0025141c | 0x41414157  = ASCII 'AAAW'

0x03深度分析

再次调用堆栈来看, 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

使用反编译器,我们可以看到这个函数存在于wwwlib中。此外,为了简洁,将其改名为sub_316d9b16为memmove_wrapper_1

int __stdcall memmove_wrapper_1(void *Src, void *Dst, size_t Size)
{
  int result; // [email protected]
  if ( Size > 0x7FFFFFFF )
    result = MSO_1511(1647603307, 0);
  else
    result = (int)memmove(Dst, Src, Size);
  return result;
}

如果大小比MAX_INT大,将会造成一个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是一个循环中!

源链接

Hacking more

...