深入解析V8引擎TurboFan的漏洞利用技术
引言
很久以前我写过一篇关于内核池的博客,而今天我们要探索的是节点海洋!当前趋势是攻击JavaScript引擎,特别是优化JIT编译器如V8的TurboFan、SpiderMonkey的IonMonkey、JavaScriptCore的DFG & FTL或Chakra的SimpleJIT & FullJIT。
本文将重点讨论TurboFan及其使用的"节点海洋"结构,然后研究Google CTF 2018中由@_tsuro编写的一个易受攻击的优化阶段,并为其编写漏洞利用代码。我们将在x64 Linux环境下进行,但Windows平台上的利用过程完全相同(只需使用不同的shellcode)。
环境搭建
构建V8
构建V8非常简单,只需使用depot_tools获取源码并构建:
|
|
d8 shell
V8提供了一个方便的d8 shell,为加快构建可以只编译它:
|
|
准备Turbolizer
Turbolizer是调试TurboFan节点海洋图的工具:
|
|
编译流水线
考虑以下代码:
|
|
使用--trace-opt
可以观察到函数f最终会被TurboFan优化。
节点海洋
TurboFan工作在称为"节点海洋"的程序表示上。节点可以表示算术运算、加载、存储、调用、常量等。有三种边类型:
- 控制边:控制流图中的边,用于分支和循环
- 值边:数据流图中的边,显示值依赖关系
- 效果边:排序操作如读取或写入状态
实验优化阶段
处理NumberAdd
考虑以下函数:
|
|
观察不同类型阶段的图形变化:
- 图形构建阶段:生成JSCall、NumberConstant和SpeculativeNumberAdd节点
- 类型阶段:TurboFan访问每个节点并尝试简化它们
- 类型降低阶段:运行更多简化器,将SpeculativeNumberAdd替换为NumberAdd
范围类型
考虑带有条件分支的代码:
|
|
Phi节点的类型是两个范围的联合,即Range(5,10)。
CheckBounds节点
考虑以下代码:
|
|
在简化降低阶段,TurboFan会比较索引和长度。如果索引的最小范围值大于等于0且最大范围值小于长度值,则会删除CheckBounds节点。
不同类型的加法操作码
V8有多种加法操作码:
- SpeculativeSafeIntegerAdd:推测整数加法
- SpeculativeNumberAdd:推测数字加法
- Int32Add:32位整数加法
- JSAdd:复杂对象加法
- NumberAdd:普通数字加法
DuplicateAdditionReducer挑战
理解简化过程
DuplicateAdditionReducer优化类似x + 1 + 1
的情况为x + 2
。但由于IEEE-754双精度浮点数的精度限制,这种转换可能不正确。
理解漏洞
当处理大于2^53-1的数字时,会有精度损失。例如:
|
|
这种精度损失导致x += 1; x += 1;
不等同于x += 2
。
漏洞利用
改进原始利用
通过多次加法放大精度损失:
|
|
完整利用步骤
- 破坏FixedDoubleArray
- 破坏JSArray并泄漏ArrayBuffer的后备存储
- 获取伪造对象
- 构建任意读写原语
- 覆盖WASM的RWX内存
- 执行shellcode
WASM RWX内存
通过实例化WASM模块,从其函数中获取包含RWX内存指针的WASM实例对象:
|
|
结论
本文详细分析了TurboFan优化编译器中的一个漏洞,通过类型混淆和范围分析错误实现越界读写,最终利用WebAssembly的RWX内存执行任意代码。这种攻击展示了现代JIT编译器复杂优化过程中可能出现的安全问题。
推荐阅读
- V8的TurboFan文档
- Benedikt Meurer的演讲
- Mathias Bynens的网站
- Vyacheslav Egorov的博客
- Samuel Groß关于攻击客户端JIT编译器的BlackHat演讲