查看原文
其他

一个拼凑起来的CVE-2018-8373的EXP

AllFromZero 看雪学院 2019-05-26


CVE-2018-8373这个漏洞是一个vbscript引擎的UAF漏洞,有关详细的漏洞成因与利用趋势,文末附上的文章已经介绍的很详细(见下文参考链接),但是介绍的利用是执行shellcode。因为同是vbscript引擎的漏洞,这让我想起来CVE-2016-0189的利用方法,通过泄露一个VBScript对象的地址,然后找到开启上帝模式的标志位从而开启上帝模式。我觉得这个利用的思路很清晰,对我这种菜鸟来说上手应该简单一些。于是我将这两个cve的利用方法拼凑了起来开启上帝模式。



下面简单介绍一下漏洞的逻辑


       

盗用一下趋势的图,这是一个简单的触发漏洞的poc,set cls =new Myclass这句会调用class_Initialize这个函数,会定义一个索引个数为3的一维数组。cls.array(2)=cls这句就是出发漏洞的关键,因为先执行的是cls.array(2)这一句,此时会将cls.array(2)的地址从堆栈中取出,而后获取cls类的默认属性值时会调用类中的回调函数Default Property Get P。我们可以看到在回调函数里面改变了array的大小,这个会导致原来的数组被free,然后再申请一个新的数组空间,但是保存栈中的原来数组的地址并没有因此改变,会将函数返回的值写入到原来的地址,但是此时原来的地址的空间已经被free所以会导致程序崩溃。



接下来看一下利用部分


就像其他UAF利用一样,我们需要将原来的地址空间申请下来变成我们可以控制的内存。因为cls.array(2)所占的内存占的是0x30字节,寻找内存正好是0x30字节的结构。对于二维数组的SAFEARRAY结构的大小恰好是0x30,因为二维数组最后有两个tagSAFEARRYBOUND,于是我们要申请大量的二维数组,首先我们需要定义一个二维数组array2(0,6),然后将array(2)改变大小为array(100000),然后array(i)= array2(0,6),这样会将先前释放的0x30字节占用,此时再向原来地址赋值的时候程序就不会崩溃,因为这时的地址已经是busy状态,我们将0x0fffffff当作返回值,这样就会将一个vartype=3的整形对象写入到原来array(2)地址处,此时这个地址已经是一个二维数组SAFEARRAY->tagSAFEARRYBOUND的地址了,将SAFEARRAY->tagSAFEARRYBOUND[0]变为了3,tSAFEARRAY->tagSAFEARRYBOUND[1]变为了0x0fffffff


同样趋势的一张图很清楚地展示很清楚这个过程:


 

于是我们就拥有了这个二维数组的后面3*0x0fffffff项的控制权,关键是这二维数组后面跟着许多我们申请的正常二维数组的具体内容部分,而且因为内存块头部的存在,这样下一个二维数组就不是以0x10字节对齐的了,将会错开8个字节。


我们是如何找到这个幸运的二维数组的,毕竟我们申请了100000个二维数组,这个当然很简单,我们只需要遍历所有的二维数组找到那个二维数组的第二维的元素数是0x0fffffff的就可以了,为了后面的叙述方便,我们先将这个数组称作luckyarray。


让我们来看看这么多二位数组的在内存中是怎么排列的{前面我们已经将二维数组中的值写成了3(vartype=2)}:



其中红线处就是块头数据,也正是因为块头数据的错位让我们拥有了利用的机会。同样由于CVE-2016-0189的启发,我们首先给luckyarray[10]赋值一个对象,这样篮框的数据就会是该对象的地址。然后我们再通过紫色线(这是一个luckyarray后面跟着的一个正常的array(i)中的一项,跟luckyarray错了8字节)项修改黄色框内的数据为3,这样我们就会把luckyarray[10]存储的vartype变成3,这样就是一个整形数,我们再读出luckyarray[10]就会把蓝框内的数据读出来,造成了对象地址的泄露。

原来二维数组在内存中是连续排列的,这样我们就省事很多了,不用再去寻找符合这样条件的内存。但事与愿违,当第二次调试,内存变成了这样:



看来我们需要判断条件寻找符合条件的内存了,一篇来自冰神银雁冰的文章给了我答案(见文末参考链接)。对于luckyarray来说,符合条件的内存片就是将vartype变成2还是3的区别,一开始我们给所有二维数组赋值都为3,因为8字节的错位,本来的正常array的数据3变成了luckyarray的vartype 3,根据这个判断条件vartype从2变为了3,便找到了符合条件的内存区域。我们称luckyarray中符合条件的项为luckyitem(有很多luckyitem)。


那么我们还需要知道的是这个在条件区域的正常数组的索引,这个也比较简单,我们只需要给luckyitem=“一个字符串”,这样Vartype就变成了8(代表字符串),我们在遍历所有的array中第一项数据为8的array(先称之为conditionarray,它的项称之为conditionitem) (上图黄框上面的数据),因为一开始我们为所有的array赋值为3。其实到这里已经实现了内存地址的泄露,利用同样的原理就可以实现任意地址的读写,就像CVE-2016-0189中的一样。但是看到冰神的文章还是感觉冰神的方法比较巧妙,并且想尝试不一样的方法,于是我还是采用了冰神的方法,放弃了CVE-2016-0189中的做法。


原来是这样,通过构造一个基地址为0,项大小为1字节,项数为0x7fffffff的数组,我们就可以实现任意地址读写了,因为在用户空间所有的地址都是我们构造数组的项,原理是这样首先构造两个字符串:



仔细观察fake_array就会发现,这个字符串就是一个标准的SAFEARRAY,维数为1,基地址为0,项大小为1字节,项数为0x7fffffff。当我们把这个字符串赋值给luckyitem,而后我们又将通过conditionarray修改luckyitem的vartype为c(代表数组),这样我们就构造了一个功能强大的数组。至于fake_str是用来后面任意地址读写用的,实际的代码如下所示:



其中array(index_vul)(index_a+2,0)=fake_array就是我们的luckyitem,array(index_b)(0,2)就是conditionitem。


任意地址写已经写好了,因为我们构造了一个强大的数组,任意地址写就是给数组任意项赋值(内存可写才可以)。


我们来看看任意地址读(经过测试直接用构造的数组向外赋值是不可以的),也是用了一个很巧的方法:


首先我们将目标地址addr加上4后利用任意地址写放入我们刚才泄露的fake_str字符串首地址+8处;然后将首地址赋值为8,原来这又构造了一个地址为addr+4,vartype为8字符串对象。为什么地址为addr+4,后来便调用了LenB函数,这个函数返回一个字符串的所占的字节,这又要说到BSTR对象了:



因为BSTR对象的前四个字节存储的就是这个字符串所占的字节数,我们传入的地址为addr+4,那么理所应当的addr就变成了header,这样LenB就把addr处的数据当作字符所占字节返回了,也就是读出了addr的数据。


好了,到现在我们泄露VBScriptClass对象地址,任意地址写,任意地址读全部做好了,接下来我们就可以修改vbscript!SafetyOption来开启上帝模式了。



根据上图可以定位到vbscript!SafetyOption的具体位置,接下来就是修改它:



最后的这个a是SafetyOption标志位的地址,但是经过调试发现,使用我们构造的数组任意地址写的地方会比目标地址少8个字节,所以我们要传入 目标地址-8。最后开启上帝模式弹出cmd。





参考链接


趋势文章https://blog.trendmicro.com/trendlabs-security-intelligence/use-afterfree-uaf-vulnerability-cve-2018-8373-in-vbscript-engine-affectsinternet-explorer-to-run-shellcode/


冰神的CVE-2018-8373利用构造过程: https://bbs.pediy.com/thread-246327.htm


《CVE-2016-0189》利用分析: http://www.freebuf.com/sectool/131766.html




看雪ID: AllFromZero 

https://bbs.pediy.com/user-785365.htm


本文由看雪论坛 AllFromZero 原创

转载请注明来自看雪社区



热门技术文章推荐:






戳原文,看看大家都是怎么说的?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存