在看雪上学习到了很多东西,于是想写一点力所能及的东西作为回报,本人菜鸟,有什么错误的地方希望大佬一定指出,虚心求教。
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的数组,在这样我们就可以实现任意地址读写了,因为在在用户空间所有的地址都是我们构造数组的项,原理是这样首先构造两个字符串
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-2-1 13:47
被kanxue编辑
,原因: