引言
本文记录了从未接触过浏览器漏洞利用的开发人员针对SpiderMonkey JavaScript Shell解释器和Mozilla Firefox(在Windows 10 RS5 64位系统上)开发三个漏洞利用的过程。近年来,浏览器利用引起了广泛关注,几乎每个主要CTF比赛都包含浏览器挑战,每月都有相关技术文章发布。
我选择了Firefox的SpiderMonkey JavaScript引擎及其zn13编写的Blazefox挑战作为研究对象。本文展示了在这次探索中的发现和编写的三个漏洞利用:
- basic.js:针对特定JavaScript解释器构建的漏洞利用,包含大量硬编码偏移
- kaizen.js:改进版本,动态解析各种信息并使用基线JIT生成ROP gadget
- ifrit.js:最终针对Firefox浏览器的漏洞利用,利用JIT编译整个原生代码payload
环境设置
首先需要设置调试环境,建议在虚拟机中进行。获取Mozilla的代码仓库(使用Git镜像):
|
|
应用漏洞补丁文件blaze.patch后,安装Mozilla-Build工具链并配置x64调试构建:
|
|
SpiderMonkey内部机制
JS::Values和JSObjects
SpiderMonkey使用JS::Value作为JavaScript值的内部表示,其中高17位(JSVAL_TAG)用于编码类型信息,低47位(payload_)存储实际值或对象指针。
JavaScript对象在内存中的布局:
- 原生对象(NativeObject)包含shapeOrExpando_指针(描述对象属性)和存储元素/属性值的空间
- 数组对象(ArrayObject)继承自NativeObject,使用ObjectElements存储长度等信息
形状(Shapes)
形状对象描述对象的属性,可以看作是一个哈希表,其中键是属性名,值是该属性值存储的槽位号。形状对象通过parent字段链接在一起形成属性链。
漏洞利用开发
漏洞分析
漏洞源于添加的array_blaze方法,该方法将数组内部大小字段修改为420,从而允许越界访问:
|
|
basic.js漏洞利用
- 构建内存访问原语:通过相邻数组和TypedArray,将有限的内存读写转换为任意内存访问
- 对象地址泄露原语:通过操作相邻对象的属性存储槽位泄露对象地址
- 控制流劫持:覆盖js::Class的cOps函数指针
- 栈转移:使用ntdll中的gadget实现栈转移
- ntdll基址泄露:通过kernel32导入表获取ntdll地址
- 执行原生代码:通过ROP链调用VirtualProtect使shellcode可执行
kaizen.js改进
- 提高内存原语可靠性:使用Tenured堆中的ArrayBuffer替代Nursery堆中的对象
- 动态解析函数地址:通过PE结构解析导出函数地址
- JIT gadget生成:强制JIT编译特定函数生成所需ROP gadget
ifrit.js高级利用
- 编译Firefox:构建64位Firefox用于漏洞研究
- 配置开发环境:禁用沙箱和多进程模式便于调试
- JIT完整payload:通过精心构造的JavaScript函数,使JIT编译器生成完整的原生payload
结论
本文详细介绍了从基础的漏洞利用到高级的JIT代码重用技术的演进过程。通过三种逐步改进的漏洞利用方法,展示了如何克服现代浏览器安全机制的挑战。虽然这些技术针对特定版本的SpiderMonkey,但其中涉及的概念和方法对理解现代浏览器漏洞利用具有普遍意义。
完整代码和材料可在blazefox GitHub仓库获取,包括调试扩展、漏洞利用代码、构建的二进制文件和脚本等。