V8学习

V8环境搭建

找个代理以后,直接参考这个师傅的文章:v8调试环境搭建。但是自己本地的代理对于浏览器这种应用可以,不知道为什么终端就是不走代理(正好和参考文章的情况相反),这里我使用的是在终端中输入export https_proxy="http://127.0.0.1:8889/export http_proxy="http://127.0.0.1:8889/这种暂时的的代理,剩下的就没什么区别了。

如果本地还是存在一些奇奇怪怪的问题,可以参考:工欲善其事:Github Action 极简搭建 v8 环境,这篇文章使用GitHub Action去搭建V8,由于这个项目最后更新的是2020,有一些东西和现在已经不同了。之前在GitHub Action使用的是Ubuntu18,但现在Ubuntu18似乎已经被遗弃,改用Ubuntu20;之前使用的是牛奶快传这种工具,但是现在好像也没法去正常使用,最后改用文叔叔。

这里给出我魔改的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
name: BUILD v8

on:
push:
branches: [ master ]
# watch:
# types: started

env:
PATCH_FLAG: true
COMMIT: 568979f4d891bafec875fab20f608ff9392f4f29
DEPOT_UPLOAD: true
SRC_UPLOAD: true
BINARY_UPLOAD: false

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
build:
# The type of runner that the job will run on
runs-on: ubuntu-20.04
if: github.event.repository.owner.id == github.event.sender.id

steps:
- name: Checkout
uses: actions/checkout@master

# init ubuntu2004 environment
- name: init env
run: |
sudo apt-get update
sudo apt-get -y install pkg-config git subversion curl wget build-essential python xz-utils zip p7zip-full ninja-build

# get depot_tools
- name: depot_tools
run: |
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
echo export PATH=\"\$PATH:`pwd`/depot_tools/\" >> ~/.bash_profile

# fetch v8 source code
- name: fetch v8
run: |
source ~/.bash_profile
fetch v8

# patch source code
- name: patch v8
if: env.PATCH_FLAG == 'true' && !cancelled()
run: |
cd v8
git reset --hard $COMMIT
cd ..

- name: build v8
run: |
source ~/.bash_profile
cd v8
gclient sync -f
./tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release
./tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug
cd ..

# zip d8
- name: 7zip v8_src
run: |
mkdir d8
cp -R v8/out.gn d8
cp -R v8/src d8
cp -R v8/tools d8
zip -q -r v8.zip d8

# upload v8.zip
- name: upload v8_src
if: env.DEPOT_UPLOAD == 'true' && !cancelled()
run: |
wget https://github.com/Mikubill/transfer/releases/download/v0.4.17/transfer_0.4.17_linux_amd64.tar.gz
tar -xzvf transfer_0.4.17_linux_amd64.tar.gz
./transfer wss --block 2621440 -s -p 64 --no-progress v8.zip 2>&1 | tee cowtransfer.log
echo "::warning file=wenshushu.cn::$(cat cowtransfer.log | grep https)"

V8源码分析

本人很菜,现在只能对builtins-array.cc中常见的函数进行一些总结。

builtins-array.cc是JavaScript 数组操作相关的内置函数的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取传入参数个数
uint32_t len = args.length();

// 获取数组的当前长度,并将其转换为 uint32_t 类型。
uint32_t len = static_cast<uint32_t>(array->length().Number());

// 将args的第二个参数转换为数字类型,并将结果存储在 result 中。
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, Object::ToNumber(isolate, args.at<Object>(1)));

// 将数组中的特定索引位置的元素设置为经过转换的数字值
elements.set(idx, value->Number());

//从 elements中获取length的索引位置的元素
*(isolate->factory()->NewNumber(elements.get_scalar(length)))

具体题目:starctf2019-oob数字经济-final-browser

V8漏洞利用

addressOf和fakeObject

1
2
3
var obj = {"a": 1};
var obj_array = [obj];
var float_array = [1.1];

obj_array数组中存储的是obj的地址,float_array数组中存储的是浮点数,v8完全依赖map类型对js对象进行解析。如果可以修改obj_array的map为float_array的map,再去使用obj_array[0]是去按浮点数去取值,原来的值是obj的地址,最后以浮点数的形式泄漏地址;同样如果可以修改float_array的map为obj_array的map,再去使用float_array[0]是去按对象地址去取值,原来的值是浮点数,最后以16进制的IEEE754数为地址得到一个对象,当然这个地址与地址中的内容需要提前构造好。

利用ArrayBuffer去任意读写

1
2
3
4
5
6
7
8
9
var data_buf = new ArrayBuffer(48);
%DebugPrint(obj);
%SystemBreak();
pwndbg> tele 0x7d9b1b8e748
00:00000x7d9b1b8e748 —▸ 0x1c5bac984371 ◂— 0x80000055d5b5822 (map)
01:00080x7d9b1b8e750 —▸ 0x55d5b582cf1 ◂— 0x55d5b5828
02:00100x7d9b1b8e758 —▸ 0x55d5b582cf1 ◂— 0x55d5b5828
03:00180x7d9b1b8e760 ◂— 0x30 /* '0' */ (byte_length)
04:00200x7d9b1b8e768 —▸ 0x55e3856e4f90 ◂— 0x0 (backing_store)

BackingStore地址是申请堆上的一段内存,是真正写入数据的地址,如果修改BackingStore指针,那么就可以获得任意读写的能力了。

当然要想任意读写的前提是先泄漏一个合法的地址,再修改BackingStore指针,如果V8使用了指针压缩,将指针的有效位数减少到32位或更少,虽然对BackingStore指针没有影响,但是前面提到的obj_array中存的会是一个32位地址(将高32直接去掉,只保留低32位),这样无法泄漏完整的地址。

伪造map

有时如果没法泄漏map地址,是可以直接伪造map

具体题目:34c3 v9

wasm执行shellcode

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

结合其它漏洞将原本内存中的的wasm代码替换为shellcode,当后续调用f函数时,实际上调用的就是shellcode。

先得到f函数的地址,通过shared_info–>WasmExportedFunctionData–>Instance这一系列关系,最后在Instance的固定偏移处(不同V8版本偏移是不同的),就能读取到存储wasm代码的内存页起始地址,这个内存页是RWX段并且是页对齐的。如果直接可以得到wasmInstance的地址就不用上述麻烦办法,其实Instance的地址就是wasmInstance的地址。

对于一些低版本的V8,RWX段是可以直接在f函数的地址的偏移处找到的(具体题目:34c3 v9

也有shared_info中没有WasmExportedFunctionData的这种情况(还不清楚是什么原因,具体题目:PlaidCTF roll a d8


V8学习
https://xtxtn.github.io/2023/09/10/V8/
作者
xtxtn
发布于
2023年9月10日
更新于
2023年9月18日
许可协议