[原创]异想天开之文档格式漏洞ByPass ASLR+DEP

发布者:仙果
发布于:2013-08-19 01:39
异想天开之文档格式漏洞ByPass ASLR+DEP

   题记:首先必须强调的是文章通篇都是假设,真实的漏洞利用中几乎不可能做到,只是在理论情况下有成功的可能性,今天分享出来目的是大家一起来讨论讨论。
   事情的起因是与同事的讨论文档格式的漏洞有没有可能性做到绕过ASLR(地址随机化)和DEP(数据执行保护),他的观点是不可能做到,要反驳他的观点,我只需要提出一种绕过系统保护的可能性,不用考虑有没有这个漏洞,怎么触发漏洞。此篇文章就是我好同事讨论的总结。这里非常感谢我的那位同事能够听下我的异想天开,并且一起讨论。
一.前置信息
   目前的漏洞利用情况来看,ASLR和DEP是横亘在漏洞利用之上的一个天堑,若没有很好的方法是无法绕过的,只能放弃。IE等有脚本解析执行引擎的程序,可以多次触发漏洞,通过泄露出基址来做到绕过ASLR+DEP,但是文档格式方面的漏洞,比如doc之类,一般情况下漏洞触发之后,如何进行堆填充都是一个问题,doc的漏洞很难做到多次控制程序的执行流程,而且开启了ASLR+DEP保护的系统上,更难实现。本文提出一种理论来绕过系统保护。
二.ByPass ASLR + DEP 的假设
   以下就是各种假设,我会详细解释每种假设的作用。
1.操作系统开启ASLR+DEP保护
   这是一个必须条件,不然本文也无必要写了。就是说操作系统必须是Windows Vista 或Vista 以上系统,最典型的系统就是win7系统。
   以Word 为例,Word进程包括所有模块都开启了ASLR和DEP保护,我们没有办法找到任何一个未ASLR的模块,无法直接编写ROP链。
2.文档的文件格式非常了解
   意思就是说需要对文件格式非常熟悉,熟悉到可以任意构造出任意符合文件格式的文档。这个条件并不容易达到,复合型文件格式非常复杂,构造的文档既要符合正常的文件格式又要能够做到控制程序执行流程。
3.触发异常但不导致程序崩溃
   这是一个很关键的点,理解了这个假设,就能搞清楚这套异想天开理论的基础。至于原因,听我解释:
   第一,由于是复合型文件格式的文档(PDF,DOC,PPT        等等),程序针对异常检测也是非常多的,当一个异常触发之后,程序就会接管这个异常,进入自己的异常处理过程,这里不会导致程序崩溃,具体原理可以参考SEH相关知识。
   第二,解释下为什么需要这类的异常。需要明白的是在泄露出模块基址之前是无法真正控制程序执行流程,也不能导致程序崩溃,一旦崩溃就会走到一个不可控的地址,与我们的需求不一致。我们需要这类异常来破坏程序自有的堆,导致程序堆结构发生变化,虽然程序会接管异常,但是某些情况下,堆结构可能并不会恢复,这为泄露出某些模块基址提供了可能性。
   在整个处理的过程中,我们是没有办法直接读取内存数据,这不像IE、Flash的漏洞那样可以通过脚本引擎解析特定的脚本来读取到内存中的数据。权限方面只有通过修改文档数据来触发异常,进而做到修改堆内存的特定结构,无法直接读取内存并显示到文档中,即使显示到文档中,针对泄露数据的引用还是一个问题。
   记住这一切都是基于假设,别当真。
   第三,解释为什么需要破坏堆结构。熟悉开发的人肯定都知道这么一个常识,程序的虚函数表是存放在堆上的,并且存在一定的结构,若我们能够破坏程序堆上存放虚函数表的结构,就能够泄露出一些模块基址。另外一个原因是由于开启了DEP保护,像栈溢出的利用方式已经没用,比如找到一种利用方法,就是利用程序堆中的虚函数表结构。
4.能够泄露出相对固定的模块基址
   通过之前第三条假设,通过触发异常来破坏堆内存结构中的虚函数表,这就引入了第四条假设,一系列的异常之后,在破坏的堆内存结构中,固定的一个相对偏移中存放了一个固定的模块地址。这里的固定的相对偏移指的是,程序执行过程中,某一个虚函数表所在的堆内存地址是相对固定的,比如第一次是在0x04123456,第二次是在0x05123456,第三次是在0x0A123456,这样的堆地址都是相对固定的。相对固定的堆地址之中存放了虚函数表,即[0x04123456+0]= 0x65341d28 ntdll!DbgBreakPoint ,此时我们就泄露了一个模块的固定地址,当然这个地址也是ASLR之后的地址,模块的函数偏移是固定的。
5.通过异常覆写已泄露的虚函数表构造ROP链
   这同样是基于以上假设来进行推导的。本身来说现实情况几乎不可能,但是不考虑现实,只想象,提出假设即可。
   目前在突破ASLR+DEP的方法上并没有大的突破,或者说大牛们的方法还捂在被窝里。当前的方法还是通过构造ROP链来绕过ASLR+DEP保护。第四条假设泄露了一个虚函数表,并且能够到一个模块的固定函数,接下来要做就是在泄露地址的基础上进行ROP链的构造。
   具体过程假设是这样的,基址泄露之后,通过其他类型的异常去覆写这个虚函数表,把ROP链的数据写入到虚函数表中,这是一个很麻烦的过程,可能需要多次触发多次写入才能完成ROP链的写入,想想都是一件非常复杂的事情。
   覆写的过程中,由于存在了模块地址,这个地址是我们必须要用到的,不能直接覆盖,而是要通过计算出需要的函数偏移,比如VirtualProtect()函数与泄露函数之间的偏移,得到了这个偏移之后,再次通过异常来写入改写模块地址,从而使其指向VirtualProtect()函数地址,最后还要通过异常完成对ROP链剩余数据的改写。
6.触发漏洞控制程序执行流程
   首先泄露了基址,接着构造了ROP链,万事俱备只欠东风,接下来就是触发一个漏洞,接管程序的执行流程,使其执行到构造的ROP链中,绕过ASLR+DEP保护,执行ShellCode。
   这个漏洞的要求是必须要使用到之前泄露出来的虚函数表,因为堆内存结构已经被破坏,这个条件还是有可能的。
   由于覆写了虚函数表,漏洞最终执行的汇编代码类似于 call [reg + offset],至于说漏洞类型可以是UAF(Use After Free),Double Free,或者是整数溢出,只要能够正确的控制到程序执行流程即可。
   至此,理论上可以做到文档格式漏洞利用自身来绕过ASLR+DEP保护,虽然需要做很多工作,但确实会存在这种可能性,堆漏洞也是可以堆出成果来的。
三.总结
   总结一下以上假设,若文档格式的漏洞做到绕过ASLR+DEP,以上假设都能够成立的话,需要多少个异常或漏洞才能满足要求呢?
   我这里给出的公式是:
   
N+M+1

   解释一下,我们需要N+M+1个异常或者漏洞才能完成对ASLR+DEP的绕过。
   N代表的是通过N个异常来泄露出堆上的一个虚函数表,这个虚函数表在堆上的位置是相对固定的。
   M代表M个异常操作之后,完成对ROP链的构造。
   最后的1表示最终触发文档漏洞的一处代码,这一步之后控制程序执行流程,执行ROP和shellcode。
四.Info_Q
这部分内容是我与没谱的讨论记录,一些我在文章里写的不明不白的地方,他都问到了,经过讨论我也发现了自己在文章中的不足之处。在此谢谢没谱。
没谱 23:10:09

没太看懂这里
仙果 23:15:00
我的意思是通过一些异常处理来破坏程序自身堆的结构,特别是虚函数表的结构,破坏之后,能够在堆上的一个相对固定的地址上找到一个模块地址,
这个模块地址也是相对固定的
没谱 23:16:04
破坏之后,能够在堆上的一个相对固定的地址上找到一个模块地址啥意思
CObject 地址为
0x004000000x
00400000
0x00500000(vtable) xxxx xxxxx xxxx
0x00400010 xxxxxxxxxx xxxx xxxxx xxxx
0x00500000 addr1 addr2 addr3 .....
仙果 23:17:37
因为本身堆是不断变化的,需要找到一个相对固定,比如0x05123456 0x08123456 这种堆地址,指向的数据是固定的
仙果 23:17:58
就是你写的这种
我表述不是很清晰
没谱 23:18:21
你的意思是,我每次去查找固定的数据,看看这个数据是在哪个地址?
仙果 23:18:22
基本概念都不熟悉
仙果 23:18:31

没谱 23:19:22
那要把堆破坏成什么样子呢
仙果 23:20:06
相对固定的堆地址之中的数据需要也是相对固定的
最好是虚函数表
没谱 23:24:10
那怎么找这个地址呢,这个时候你是不能执行代码的
仙果 23:24:55
通过异常来破坏堆,但是这个异常不会导致程序崩溃,至于说具体怎么着,只能通过调试
没谱 23:25:24
你是不是想,多次触发漏洞,让这个值分配到不同的内存里,然后把这个内存地址找出来?
仙果 23:25:29
因为我们没有办法通过文档格式来读取内存
没谱 23:25:33

仙果 23:25:35
嗯嗯
仙果 23:25:44
就是多次触发异常
没谱 23:26:03
我们调试的话肯定能找到这个地址啊,但是exp里面怎么去找到这个地址呢
仙果 23:26:34
通过调试分析已经找到了,Exp里直接覆写ROP
没谱 23:28:36
假设 0x1000 这个偏移里总是存放着 mso.dll的基址
第一次可能是0x00401000里存放着mso.dll的基址
第二次可能是0x00501000里存放着mso.dll的基址
那你下面具体要怎么做呢
仙果 23:33:03
0x00501000这个是相对固定的,Mso.dll的基址也是相对固定的,此时计算MSO.dll中VirtualPorect函数的偏移,与基址进行相减,就能够得到这个偏移,接着使用异常去改写0x00501000中的基址,接着再使用异常来改写0x00501000之后的内存,构造ROP链
没谱 23:35:17
接着使用异常去改写0x00501000中的基址这个过程应该是exp在做的吧?exp是怎么知道0x00501000这个地址的呢
仙果 23:36:46
Exp利用的最后一个漏洞需要引用0x00501000这个地址
或者这么说0x00501000存放了原始的虚函数表,我们通过异常把它改成了ROP链,最终利用的漏洞触发之后,会引用0x00501000中的虚函数,通过call [0x00501000+offset]来执行到ROP链中
没谱 23:38:46
嗯,那你需要把0x00501000写到exp里吧?
仙果 23:39:00
不需要
没谱 23:39:12
exp动态来找出这个0x00501000?
仙果 23:39:37
0x00501000这个地址是之前异常泄露出来的
没谱 23:40:18
泄漏出来也是需要引用或者保存这个值才行啊,你如果要操作这个值,就得需要执行代码啊
仙果 23:41:03
没有办法保存,我们是无法直接操作这个值的,只能通过异常来操作
没谱 23:41:47
异常代码是系统的还是你自己的?
仙果 23:42:21
是程序的异常,通过修改文档数据来触发这些个异常
没谱 23:42:49
那这个异常里也不会做啥操作啊,只是修复一下访问异常的地址,让程序不崩溃
没谱 23:42:57
我还是没明白
仙果 23:44:29
这里的异常作用是破坏堆内存结构
没谱 23:45:49
1、利用漏洞代码来破坏堆
2、然后触发异常,但是异常处理了这个异常,导致程序不会发生异常
3、再次触发漏洞,去找出某个值
4、然后我就理解不了了
没谱 23:45:54
最好搞个例子出来啊
没谱 23:46:05
自己写个符合条件的程序
仙果 23:46:24
第四点我还在写
没谱 23:46:41
恩,那你写完了我再看看
仙果 23:46:49
好像是可以搞出来例子的,明天我去问问做开发的同事
没谱 23:47:09
你前面说的那几条应该是没问题的,应该有些漏洞可以做到
仙果 23:48:14
目前还停留在想象阶段,只能是一种理论
没谱 23:48:21

没谱 23:48:35
前面几条还是挺现实的
仙果 23:48:48
非常感谢啊,我把我们的聊天记录也发到看雪怎么样
仙果 23:49:11
前面确实比较容易实现,后面的就难了
没谱 23:49:15
可以啊,不过我好像没说啥有营养的话

附件:为DOC格式,方便阅读
异想天开之文档格式漏洞ByPass ASLR+DEP.doc
上传的附件:

声明:该文观点仅代表作者本人,转载请注明来自看雪