手把手教你如何构造Office漏洞POC(以CVE-2012-0158为例)
下面小编为大家介绍office漏洞cve-2012-0158就凭借其经典、通用又稳定的漏洞利用经常出现在各种报告中,详细的讲解这个漏洞的原理,以及如何基于原理手动构造出可利用的poc样本。
漏洞原理关于本漏洞原理,网上有各种分析文章,一般都基于实例样本来定位漏洞的二进制代码并分析原理,这里不作详细的分析过程,直接给出漏洞成因,后面也有少部分的调试演示。这个漏洞发生在office的一个组件mscomctl.ocx,多个版本office的该模块都存在这个漏洞,本文的测试环境均以winxp+office2007为例。mscomctl.ocx这个漏洞模块是office解析activex控件用到的一个动态库,如果一个office文档中包含activex这类控件元素的话,比如按钮、列表、树形控件等,当文档通过office打开时mscomctl.ocx就会被自动载入office程序的进程空间被调用来解析和显示控件。
本漏洞属于经典缓冲区溢出漏洞里的栈内存拷贝溢出漏洞,当office解析到一个被构造好的控件(以listview列表控件为例),会发生栈内存越界拷贝。以下栈回溯示意图可以代表本漏洞的发生过程:
可以看出,excel在解析listview控件的时候,读取并加载了控件的数据流,加载数据流的过程中会调用到一个内部函数readbytesfromstreampadded,该函数的功能类似于memcpy内存拷贝函数,根据参数从指定内存拷贝指定大小数据到目标内存。但仔细往上跟踪就会发现,漏洞并不是出现在这个函数里,而是出现在cobj::load这个函数,下面分析一下这个函数如何出现的漏洞,先贴上ida关于这个函数的伪代码:
int __stdcall cobj__load( int a1, void *lpmem)
{
int result; // eax@1
void *v3; // ebx@1
int v4; // esi@4
int v5; // [sp+ch] [bp-14h]@1
size_t dwbytes; // [sp+14h] [bp-ch]@3
int v7; // [sp+18h] [bp-8h]@4
int v8; // [sp+1ch] [bp-4h]@8
v3 = lpmem;
result = readbytesfromstreampadded(&v5, lpmem, 0xcu ); //第一次正常拷贝,读取数据头
if ( result >= 0 )
{
if ( v5 == 'jboc' && dwbytes >= 8 ) //漏洞触发条件
{
v4 = readbytesfromstreampadded(& v7, v3, dwbytes); //第二次拷贝,此处调用必然越界
if ( v4 >= 0 )
{
if ( ! v7 )
goto label_8;
lpmem = 0;
v4 = readbstrfromstreampadded(( uint)&lpmem, (int )v3);
if ( v4 >= 0 )
{
cobj__setkey((bstr) lpmem);
sysfreestring((bstr) lpmem);
label_8:
if ( v8 )
v4 = readvariantfromstream(( struct tagvariant *)(a1 + 20) , (struct istream *) v3);
return v4;
}
}
return v4;
}
result = 2147549183;
}
return result;
}
cobj::load,顾名思义,是cobj对象加载的方法,需要从内存里读取对象数据,所以一开始便从数据流里读取了0x0c个字节到临时变量v5中。接着判断v5的前4个字节是否为”cobj”来检测是否为要加载的对象类型,并且dwbytes这个变量如果大于8才进行下一步的加载。注意到,dwbytes 这个变量是读取那0x0c个字节的时候一起读取进来的,因为从ida的变量备注中可以看出dwbytes =[bp-0x0c]落在v5=[bp-0x14]和v5+0x0c=[bp-0x08]的内存区间中,所以这里的一个关键是dwbytes的值可以通过修改数据流被控制。
再看下一步,同样从原来的数据流读取dwbytes个字节到临时变量v7中,v7=[bp-0x08],而dwbytes此时却大于8,所以这个读取拷贝必然会覆盖ebp,发生越界拷贝,形成栈溢出漏洞。根据此分析可以推测,正常情况从控件数据读取出来dwbytes值不会大于8,因为如果大于8的话必然导致栈拷贝异常,那么这个漏洞早就被测试出来了。而且通过ida里查看此函数的交叉引用会发现,这个函数似乎作用并不大,都是在加载特定几个控件的开头被调用了一下。所以,我怀疑这个漏洞不是所谓的严重的失误,把本来的小于8写成了大于8,就是微软故意留下来的后门漏洞。
构造触发漏洞的poc
经过上面的原理分析,office在解析listview控件时调用了漏洞函数cobj::load,该函数在加载cobj对象时根据可被篡改的dwbytes读取指定大小的内存数据到8字节的临时变量,且校验大小时存在后门嫌疑,导致可被利用的缓冲区溢出漏洞。为了检验我们的分析是否正确,下面我们参考上面的栈回朔图构造可触发此漏洞的excel文档。
首先excel文档里需要存在一个listview控件,可以通过excel软件里面的开发者工具添加,添加完后相当于文档里嵌入了一个空的listview对象。
接着,还需要往这个对象里面添加listitems以及listitem子对象,这样就能使excel程序调用到cobj::load函数。但是这里有个问题,listitem对象无法直接通过excel操作添加,excel只能通过listview控件的属性添加列表标题,没有直接办法添加列表内容。解决办法是通过编写excel支持的vba程序代码,编译生成一个listitem对象。
但是这样带来另外一个问题,就是如果文档里边存在vba这类宏代码,excel会默认禁止代码执行,这样依旧解析不到listview控件里的listitem对象,一个简单的解决办法就是先写好代码编译运行后生成了初始化好的listview控件,再把所有的生成代码删除后保存即可,因为宏代码会被阻止执行而控件对象不会被阻止解析。
下一步,只要将保存好的文档通过十六进制编辑器打开,定位到cobj对象的数据,修改偏移量为8的dwbytes值为大于8的数值就能触发漏洞。事实上只修改那一个值还无法看到漏洞触发的效果,原因是拷贝函数readbytesfromstreampadded还会接着校验dwbytes的值,幸运的是该校验只是从要拷贝的数据头部读取另一个dwbytes的值,检验两个值是否相等,所以我们只需要把对象数据里的那个数值也修改成相应的大小就可以通过校验从而触发漏洞。
触发漏洞后,由于我们只是简单的用一些随机数据覆盖ebp和相关函数返回地址,所以excel最终优雅的返回一个我们想要看到的程序错误提示框。
漏洞利用
现在,我们得到了一个可以触发的栈缓冲区溢出漏洞,下面要怎么利用这个漏洞来做一些事情就各显神通了,本文还是给大家弹个计算器来抛砖引玉。
通过上面构造的poc,我们可以修改两个dwbytes的值和后面的数据来控制运行栈的内存布局。为了更好的编排数据,最好通过调试样本去动态修改数据以达到目的,最后只要将内存里编排好的数据拷贝到文档对应的部分即可。而调试过程中,我们的第一目标自然是获取程序控制器,控制eip,这里一般是通过覆盖函数返回值或seh链指针来实现。由于mscomctrl.dll没有开启gs保护,我们采取最简单的覆盖函数返回值即可控制eip。然而,为了使程序顺利的走到返回值,我们还需要修改数据,满足一些返回条件,控制程序流程,使之不进入复杂的函数或指令操作集,避免因栈被破坏导致一些异常的发生。
一旦程序顺利到达返回地址,我们便可以根据运行环境做各种事情,比如构造一个rop链绕过dep保护,或者直接跳转到栈空间执行代码,这些对于一个熟悉漏洞利用的人来说都是轻车熟路了。这里有个需求,就是栈内存数据需要有足够大小的空间来容纳无论是rop链还是shellcode,所以需要增加一下listview控件的数据规模,简单的方法就是添加listitem的时候把字符串写的足够长。
当所有的必要条件都具备的时候,我们的代码就可以放进栈里执行了,这里我简单使用一个通用的跳转地址直接跳转至栈内存代码执行,由于xp+office 2007默认不开启dep保护,所以我的环境可以顺利弹出计算器。
关于更多的需求比如如何编写rop链绕过office2010以上默认开启的dep保护,我将会在接下去其他的漏洞分享中陆续展开。另外,限于本文是文稿形式,更详细的视频演示也可以访问本人的博客地址(搜索维一零小站)进行参考。
总结通过本文详细的分析,我们了解到这个漏洞的原理和危害性,由于mscomctl.ocx是基础动态库,影响的应用软件自然比较多,除了office全套装外,sql和其他第三方应用软件,只要存在使用该漏洞库的地方,都有可能被利用。而利用的方法和本文一样,都离不开漏洞的原理,构造的“畸形”数据必须要通过漏洞函数的检验流程,才能最终绕过程序本身的限制,夺取程序的控制权。