V8-pwn入门(2)——[*CTF]oob
0x00 环境配置🔧
oob.diff
1 | diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc |
编译v8,在v8目录下
1 | git checkout 6dc88c191f5ecc5389dc26efa3ca0907faef3598 -f |
如果报错,说明找不到python命令,可以/usr/bin目录下创个软链接(尽量链接到python2,python3后续还会报错)
1 | File "/root/depot_tools/gclient.py", line 4653, in main |
解决
1 | sudo ln -s $(which python2) /usr/bin/python |
如果报错
1 | tar (child): xz: Cannot exec: No such file or directory |
解决
1 | sudo apt-get install xz-utils |
如果报错
1 | File "/home/fuzz/Fuzzing_v8_75/v8/build/config/linux/pkg-config.py", line 246, in <module> |
解决
1 | sudo apt install pkg-config |
0x01 patch分析🧐
注册了叫oob的array内置函数,可以通过形如arr.oob()
对任意array调用这个oob
1 | SimpleInstallFunction(isolate_, proto, "oob", |
具体内置函数实现分析
1 | BUILTIN(ArrayOob){ |
0x02 调试方法🐞
这个patch是用CSA实现的内置函数,最终会转换成汇编,不好打断点调试,因此可以用CSA里面的PrintF来代替printf
- 对于
Handle<Object>
(和Object
的⼦类),可以直接在上⾯调⽤xxx->Print()
来输出内存布局
这里加上
然后重新编译
1 | ninja -C out/x64.release d8 |
看看调试结果,相当于把job xxx的信息打印出来了
1 | var a = [1.1]; |
0x03 漏洞分析🔍
oob使用
首先写过double_arr和obj_arr,u2d d2u gc hex这些都是工具function,后面会用到
1 | function gc() { |
array.splice(0)
表示从数组的第一个元素开始,删除所有元素。它是用于清空数组或截取数组内容的一种简便方法,同时返回被删除的元素集合。
验证一下越界读
1 | let double_arr = [1.1]; |
可以发现oob返回的是map*,同时在内存布局中,也是elements元素1.1的后一个
再试试越界写,oob需要传参
1 | let double_arr = [1.1]; |
直接把map*改成了1,也就是说给oob传的参数会向后覆盖一个元素,刚好对应这个
map*
🤔这里就有个利用点,可以构造一个double array,然后用oob把它的
map*
leak出来,后面再构造一个object array,利用oob传参越界写的特性,把object array的map*
覆盖成前面leak的double array的map*
,实现一个object array –> double array的类型混淆
原语构造
leak任意object的address
1 | function addrof(obj_param) { |
其实就是利用上面说的trick,把要leak地址的object先存到object array中,然后把object array混淆成double array,这样后面读取elements的时候就会以double array的方式直接返回(这样就是返回address),就不会按原来object array的方式(指针解引用然后…)
也可以反过来伪造任意对象,传入任意地址,存到double array中,把double array混淆成object array,这样再访问刚刚传入的地址,就会被当作HeapObject Pointer来解析
1 | function fakeobj(addr) { |
之后还需要伪造object对应的内存布局,因此需要了解map*结构
回顾一下map的结构,第二个int值比较重要,代表了type
1 | // +----+----------+---------------------------------------------+ |
可以伪造一个map,然后改
instance_type
,这样就能伪造任意类型的object了
伪造object例子🌰
之前提到ArrayBuffer可以通过改backing store的大小来实现任意地址读写,因此这里可以尝试伪造一个ArrayBuffer,伪造的话肯定需要map*,这里先看一个正常的ArrayBuffer
1 | let ab =new ArrayBuffer(0x20); |
之前说过map结构里第二个int值表示type,因此可以直接把0x1900042319080808
copy过来,就可以伪造一个ArrayBuffer的map了,其余结构参考上面job的结果
1 | let fake_array_buffer_obj = [ |
u2d
是前面写的工具function,unsigned int->double这里可以看到
map*
的位置写的是0,这个是因为我们还不知道我们构造的fake map的具体地址,现在需要把fake map的address leak出来,然后填到map*
的位置
那么怎么leak这个fake map的address呢🤔,由于前面构造原语的章节说过,可以利用类型混淆来leak出任意object的address,因此我们可以在运行poc的时候leak出构造的fake_array_buffer_obj
的地址,之后只要加上构造的fake map
相对于fake_array_buffer_obj
的偏移就可以了。
那么怎么获得这个偏移🤔其实我们构造的fake_array_buffer_obj
实质上是一个JSArray,回顾一下之前说的JSArray的结构,后面存储的元素其实是在FixedArray的Elements中,而Elements相对于JSArray头的偏移其实是**固定**的
1 | graph TD |
计算fake map偏移
首先看一下构造的fakeobj
1 | let fake_array_buffer_obj = [ |
可以看到Elements相对于JSArray头的偏移是0x48,所以再加上fake map相对于Elements的偏移(4x16=64)就得到了fake map相对于JSArray的偏移\
这样就可以构造一个ArrayBuffer了
1 | let fake_array_buffer_obj = [ |
可以把DataVeiw理解成是用来读写传入的ArrayBuffer的backing store中的值的,而我们再构造ArrayBuffer时,把backing store的长度被我们改得很大(0x4000),所以可以实现任意地址读写
wasm写shellcode
web assembly创建的Instance附近有一块权限为rwx的内存,可以写入shellcode,这块内存相对于Instance的偏移也是固定的,现在我们来找一下这个偏移
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]); //一个简单的main函数 |
这样就可以得到偏移是0x88了
接下来就需要利用之前构造的DataView来任意地址读写了,需要实现一些工具function
1 | function get_32(addr) { |
完整POC
实际上前面的工作就是创建一个JSArray(fake_array_buffer_obj
),然后为了利用ArrayBuffer的Backing Store和DataView来实现任意地址读写,fake_array_buffer_obj
数组元素是构造好一个ArrayBuffer的内存布局,后面构造fake map等一系列操作其实就是把fake_array_buffer_obj
的Elements改造成一个ArrayBuffer
,这也是为什么最后得到fake_array_buffer
时传入fakeobj
的其实是fake_array_buffer_obj
的Elements的address,之后在寻找wasm instance附近的权限为rwx的内存的偏移,最后只要利用这些工具类写入shellcode就行了,这里简单使用一个弹计算器的shellcode
1 | function gc() { |
可以看到弹计算器成功了
m系列mac上用UTM运行的x86虚拟机UI界面实在太卡了,ssh连接更方便点
- 标题: V8-pwn入门(2)——[*CTF]oob
- 作者: Sl0th
- 创建于 : 2024-11-14 12:52:03
- 更新于 : 2024-11-21 16:00:54
- 链接: http://sl0th.top/2024/11/14/V8-pwn入门-2-——-CTF-oob/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。