Google CTF 2023 - v8box
flyyy 发表于 北京 二进制安全 202浏览 · 2025-05-20 04:40

Google CTF 2023 - v8box

环境搭建

查看下build.dockerfile,本地编译一个环境



Bash
复制代码
根据附件所给的编译选项,修改args.gn的内容

继续执行

留意上方的编译参数,这里的v8关闭了jit、maglev、turbofan、webassembly,然后开启了这个v8exposememorycorruptionapi,这个api的作用是可以通过官方写的一些函数,对于sandbox内的空间进行操作,位于src/sandbox/testing.cc

有以下方法,相当于是直接有了oob原语。

后来查阅了新版本,大致是v8 12.x以上的,新增了一些方法,同时编译选项也发生了一些变化,变成了V8_ENABLE_MEMORY_CORRUPTION_API,同时多出了一些方法,见下方

同时开启了这个编译选项v8_code_pointer_sandboxing,实现的issue id。因为我们此时已经又了4GB空间任意读写,所以如果修改了某些函数指针的值,是可以很容易的控制rip,所以这个保护就是来阻止这种行为。对于可执行的函数指针,都会有一个固定的入口(code pointer table),然后根据偏移来索引对应的代码。

漏洞分析

patch分析

下面是v8.patch的内容

找到对应的源码看下,这里关掉了一些d8的选项,为了编译更轻量化?

接着是0001-Protect-chunk-headers-on-the-heap.patch的内容

先创建了三个方法

下面在RwMemoryWriteScope命名空间下新增了三个方法,分别是ProtectSpace、SetWritable、SetReadOnly。

ProtectSpace的作用是将一段地址赋为指定的权限,核心是这里mprotect(addr, RoundDown(sizeof(MemoryChunk), 0x1000), prot),就是熟悉的权限修改。

SetWritable的作用是将当前的地址空间赋为rw权限,需要注意其处理,对于young generation且没有被gc处理过的内存空间,会将fromspace和tospace的地址空间权限赋为rw,反之就是已经处理过的old generaion 直接调用ProtectSpace去赋权限;SetReadOnly的作用同理,结构和SetWritable是一样的。

这里其实就已经存在了一个条件竞争的问题,两个函数使用了nestinglevel变量来约束,但是对于多线程的情况其实就会出现问题,但这个并不是预期解法。

漏洞分析

Ignition作为v8的字节码解释器,JavaScripts代码会被转化为字节码,然后字节码通过Ignition解释执行,所以即使Maglev/Jit关闭,也不会影响到Ignition。

Ignition 会从字节码数组中读取指令执行,这些字节码通常是从源代码编译而来的,所以通过修改这些字节码数组的内容,可以实现指令的执行,但是要如何实现沙箱逃逸呢?

v8的js解释器定义了一些opcode,这些其中Ldar和Star出现了缺乏边界检查的问题,我们可以利用这两个实现沙箱逃逸

这里是Ldar指令对应的字节码

下面是Ldar指令对应的汇编,r12寄存器是bytecodearray的基址,r9是索引。可以不难看出这里的返回值rac由rbx赋值,rbx是通过rdx+rbx索引得到,然后rdx由rbp赋值,也就是说这个指令会返回一个栈上的数值。

通过demo可以看下这个的效果

此时的指令执行情况



此时栈和leak的情况



由于指针压缩的存在,只能泄漏出低32位的值,同时由于类型是smi,所以会对应的 >>1 ,这里0x000000000fdb6c0e * 2 = 0x1fb6d81c 

下面是Star的汇编指令,与Ldar同理。这里将rax,也就是上一次执行的返回值,赋给栈上。

执行了这一段代码

不难从下面看出,rbp+0x0处的值被修改为rax寄存器内的值

利用思路

通过上面的分析,此时已经具备了泄漏栈上低4字节数据和栈上8字节数据写的原语。

首先需要解决的是如何控制RIP,通过栈上8字节写的方法,其实已经可以控制rbp了,通过修改字节数组,可以多执行几次leave,这样就可以实现栈迁移,其实这里就可以劫持控制流了。

那么如果获取gadget,也就是泄漏d8的基地值呢,从栈上的值是可以发现一些位于d8的代码片段的。

对于地址随机化,这里的低四位字节是变化很小/基本上不变化,那么现在就是需要泄漏d8基地址的高位字节。因为堆块开头部分还是具有读权限的,所以高位字节可以从堆块开头读到,然后拼接起来,再减去尾部的偏移,就可以拿到d8的基地值,拿到d8的基地值之后,就可以获取gadget,通过这些gadget写rop提权

exp



参考链接

https://github.com/google/google-ctf/tree/main/2023/quals/sandbox-v8box/solution

0 条评论
某人
表情
可输入 255