CVE-2018-8174:从UAF到任意地址读写
2018年5月9日,360发表Blog “Analysis of CVE-2018-8174 VBScript 0day and APT actor related toOffice targeted attack” 揭露了利用“双杀”0day发起的APT攻击,其中使用的漏洞就是IE vbscript 0day:CVE-2018-8174,不久该样本就在互联网被公布。由于360的Blog并没有对漏洞原理和任意地址读写的利用方法详细介绍,且原始样本混淆严重,笔者对样本进行了简化,重点说明该漏洞的原理和如何利用该漏洞实现任意地址读写,希望帮助大家更好的理解这个漏洞利用程序。
0×01 漏洞原理
poc如下:
poc中首先定义了两个数组array_a和array_b,并声明了一个类Trigger,Trigger中重载了析构函数Class_Terminate,在UAF函数中,创建了一个Trigger的实例赋值给数组array_a (1),并通过Erase array_a清空array_a中的元素,这时候在析构array_a中的元素的时候会触发脚本中Class_Terminate的调用,在Class_Terminate中增加了一个array_b(0)对Trigger实例的引用(Trigger实例引用计数+1),再通过array_a (1)= 1删除array_a (1) 对Trigger实例的引用(Trigger实例引用计数-1)来平衡引用计数,这时候Trigger实例会被释放,但是array_b(0)仍然保留了这个Trigger实例的引用,从而array_b(0)指向了被释放的Trigger实例的内存,最终在TriggerVuln中通过b(0) = 0访问未分配内存触发漏洞。开启hpa和ust观察漏洞现场:
显然eax已经在vbscript!VbsErase的调用栈中被释放了,vbscript!VbsErase即对应了脚本中的Erase,而eax正是被VBScriptClass::Release函数释放的VBScriptClass对象也就是脚本中的Trigger实例。这里看下VBScriptClass::Release的逻辑:
VBScriptClass::Release中首先对VBScriptClass的引用计数-1(&VBScriptClass+0×4),如果引用计数=0则调用VBScriptClass::TerminateClass,调用VBScriptClass::TerminateClass时因为在脚本中重载了Class_Terminate函数,所以获得了一次脚本执行的机会,这里就可以在释放VBScriptClass的内存前将即将释放的VBScriptClass内存地址保存脚本控制的变量中(Set array_b(0) =array_a(1)),并通过array_a (1) = 1平衡引用计数,最终释放内存。
1) Set array_a(1) = New Trigger:此时VBScriptClass引用计数=2
2) Erase array_a返回后:
此时Trigger指向的内存已经被释放,但是array_b(0)仍然指向这块被释放的内存,形成悬挂指针。
0×02 漏洞利用
UAF漏洞利用的关键是如何使用这个悬挂指针操作内存。通过分析漏洞原理知道array_b(0)指向被释放的VBScriptClass的内存(大小为0×30),这时候可以用一个VBScriptClass占位(这里称为MyClass2),接着利用悬挂指针array_b(0)释放这块内存再用另外一个VBScriptClass占位(这里称为MyClass1),此时MyClass1和MyClass2将同时指向这块内存。此外VBScriptClass在+0×08处是保存了VBScriptClass成员变量和成员函数NameTbl对象(大小为0×88)的指针,NameTbl对象从+0×48开始保存成员变量和成员函数的指针:
可以理解为下图,通过UAF MyClass1 和MyClass2都指向VBScriptClass的内存,同时MyClass1 MyClass2的变量也都保存在NameTbl中,如果MyClass1 MyClass2的变量在NameTbl内存是错位排列的,那么就有可能通过控制其中一个对象变量的值来修改另一个对象变量的属性,从而实现类型混淆:
简化后的任意地址读写poc:
首先在UAF函数中创建了一些VBScriptClass对象占据系统堆碎片为后面UAF准备,然通过触发漏洞获得指向已释放的Trigger对象内存的array_b,接着通过“Set mycls2 = New MyClass2”,用MyClass2占位释放的内存,此时array_b的7个元素和MyClass2都指向这块内存。
在InitObjects函数的“mycls2.SetProp(myconf)”中会触发Confusion类的Public Default Property Get P函数调用,并将返回值“P=174088534690791e-324”保存在MyClass2的成员变量mem中。在PublicDefault Property Get P函数调用中,再次利用悬挂指针array_b(i)释放了MyClass2的内存,然后用MyClass1占位并将字符串FAKESAFEARRAY赋值给MyClass1的成员变量mem,由于MyClass2依然指向这块内存,因此MyClass2.mem = MyClass1.mem = FAKESAFEARRAY。