导语:我们可以看到浏览器的发展速度太快了,而且也变得越来越好了,因此,浏览器的利用在短短几个月内就过时了,这使它真正成为了利用漏洞的捷径,如果只是为了获取浏览器安全知识肯定收获很大。
0x01 环境搭建
去年我对Chrome的JavaScript引擎V8 的漏洞利用进行了大量研究,尽管这种漏洞利用的大多数概念都是传统技术:例如,类型混淆和释放后使用漏洞,区别之一是UAF的free / malloc没有被直接利用;类型混淆漏洞的可利用性的根本原因之间存在巨大差异,这是由于在触发Type Confusion错误之前JavaScript引擎进入了优化阶段。
正因为如此,在完成了一个漏洞利用并学习很多知识之后,我试图通过阅读代码并编写一些示例来尝试了解V8中的优化流程。v8的开发人员团队使用一个非常出色的工具Turbolizer。
如果你也想做进一步研究,请在终端中执行以下命令:
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git && cd depot_tools && echo "export PATH=\$PATH:`pwd`" >> ~/.bashrc source ~/.bashrc && cd ~ && mkdir v8_turbolizer && cd v8_turbolizer && fetch v8 ./v8/build/install-build-deps.sh ./v8/tools/dev/gm.py x64.debug d8 cd ./v8/tools/turbolizer/ && npm i && npm run-script build
编译执行这些命令可能需要一些时间,具体取决于构建V8的网络速度和CPU能力。
0x02 V8的生态系统
V8是一款非常复杂的软件,它由JavaScript引擎的许多部分组成:解析器,AST,中间表示,字节码解释器,即时编译器,优化器等。
JIT编译器简介
以前,JavaScript纯粹是一种解释性语言,这意味着引擎中将有一些组件可以将代码动态地转换为字节码,以便最终可以执行。
如今,JavaScript需要提高性能,因为它不再只是用来修改图形界面并通过与DOM交互来修改一些HTML输入。现在,它已用于更为复杂的项目中,例如NodeJS,通过JavaScript,我们可以在其中运行完整的服务器。因此,大多数JavaScript引擎已实现JIT编译器。
JIT编译器在JavaScript代码本身运行时动态地动作。从本质上讲,它基于一个非常简单的概念:如果多次使用一个函数,它将被视为“热”函数,因此它将被编译为机器代码,并且在下次调用同一函数时,机器代码将被编译为机器代码。运行,跳过所有中间步骤(中间表示,转换为字节码等)。
Ignition, TurboFan and Optimisation
我们的重点将放在解释器和优化编译器上。V8中带有Ignition(代替了基线编译器,但它也是字节码解释器)和TurboFan(V8的优化编译器之一),就像它是V8引擎一样。
在运行任何JavaScript之前,Turbolizer将在解析器之后运行,它的主要功能是生成字节码,然后将其全部馈送到Turbolizer的解释器部分以运行它。需要记住的一点是,在运行源代码之前,已经进行了某些优化,例如消除了无效代码。
它会通过检查诸如Ignition提供的反馈,例如检查一个函数运行了多少次,为该函数提供的类型和值是什么等。由此,如果代码的任何部分调用很多,Turbofan将进行编译字节码转换为机器码。所以,Turbofan是JIT编译器,但有一点变化:它基于假设优化代码。这就是为什么Turbofan以其优化而闻名。
为了快速优化,有一个重要的概念与V8中的内置函数有关,这些是预编译的函数(JavaScript,字节码或汇编代码),已知它们经常在JavaScript中用于调试目的,因此V8具有一组可以从脚本本身调用的函数,并将触发内置代码。 查找内置文件的另一种方法是通过SimpleInstallFunction在V8的源代码中进行搜索。
以上所有内容都是管道的一部分,在管道中,所有JavaScript代码,字节码和优化都转化成机器代码(得到优化)或流回字节码。
0x03 Turbolizer介绍
很高兴看到大公司和世界一流的团队共享他们的工具,以简化诸如Turbofan之类的软件调试。这就是Turbolizer的全部意义,它是一个图形界面,可帮助调试运行JavaScript代码时每个优化阶段发生的事情。
Turbolizer的示例用法-TypedLowering阶段
Turbolizer的使用示例-EarlyOptimization阶段
0x04 One too many colours
正如在前面的图像中看到的那样,Turbolizer在优化的不同阶段给出了五种颜色。由于Turbofan使用“ 节点海 ” 的概念,这是一个有助于编译优化的框架。在我们的案例中,重要的是要了解大量的节点如何与JavaScript相关联的,并在Turbolizer的支持下了解每个阶段发生了什么。
节点数
黄色:这些节点代表控制节点,改变或描述脚本流程,例如起点或终点,返回,“ if”语句等。
浅蓝色:表示某个节点可能具有或返回的值的节点。比如一个函数总是返回“ 42”,根据优化阶段,我们将其视为图形上的常数或范围(42、42)。
深蓝色:中级语言动作的表示(字节码指令),有助于了解从何处将反馈送到Turbofan中。
红色:这是在JavaScript级别执行的基本JavaScript代码或动作。例如JSEqual,JSToBoolean等。
绿色:机器级别的语言。在此处可以找到V8 Turbolizer上机器级别语言的操作和标识符示例。
请注意,这些描述可能并不完全准确,因为所有文档都是以下链接并在查找V8的源代码:
0x05 优化 Turbofan
如上所述,Turbofan进行了几次优化,并且可以通过类型混淆漏洞来利用许多优化阶段,如果检查Turbolizer,看到大约有20个阶段:
TurboFan阶段
利用过程不在本博客的讨论范围之内,大概的利用方法是通过使用/破坏DataView对象或堆来创建任意的读/写原语,堆喷后使用越界读/写来攻击受害对象,以破坏对象内的指针(该模式始终破坏一组对象属性以具有上述读/写原语)。
0x06 调试分析
由于已经在调试模式下编译了v8,因此本节将首先在Turbolizer上分析要查找的最常见节点之一,然后进行一些调试。要记住的一件事是,我们需要在d8二进制文件所在的位置运行gdb ,以便正确加载源代码,而不必dir在gdb中运行命令。
如之前在有关利用的笔记中所述,方法之一是越界读/写,因此,我们将继续一个JavaScript示例,该示例禁用越界数组访问检查。如果想深入了解更多细节,这篇文章将介绍Turbofan,它比此处更详细地介绍了禁用此类检查和V8漏洞利用代码,但是这有点过时了,代码更改如下:
let arr = [1.1, 2.2, 3.3]; function nochecks(pos){ let mod = 2; var access = arr[pos] % mod; return access; } nochecks(10); for (let i = 0; i < 10000; i++) nochecks(i); nochecks(10);
我们可以看到有一个nochecks将pos变量作为参数的函数。之后,pos计算模2 并用于访问arr数组中的位置,阻止大于1的数字。由此,直到实施最新的V8强化之前,要寻找的常见事情之一就是查看是否优化程序已删除边界检查。对于此任务,有两件事帮助我完成了分析。
一是以下trace``d8标志:
一个非常有价值的flag是--trace-turbo-reduction,它将给我们在与精简代码相关的每个优化阶段对代码进行的所有精简的详细trace。从漏洞利用代码开发角度来看,减少编译器时间将是利用优化的重点。
因此,如果我们使用以下命令运行先前的JavaScript代码段:
./v8/out/x64.debug/d8 --trace-turbo-reduction --trace-turbo javascript_tests/nochecks.js
我们将获得上述优化跟踪,并且因为此后将使用Turbolizer,所以该--trace-turbo标志将帮助我们生成一个.json文件,以后可以将其馈送到Turbolizer中进行分析。如我们所见和期望的那样,优化器发现我们正在访问数组,因此插入了CheckBound节点:
./v8/out/x64.debug/d8 --trace-turbo-reduction --trace-turbo javascript_tests/nochecks.js | grep -i bound
在不同的优化阶段插入的CheckBound和CheckedUint64Bounds节点
然后可以在Turbolizer中观察此数据,这将有助于分析行为,具体取决于生成的节点的数量。例如,让我们观察Turbolizer上CheckBounds该LoadElimination阶段内的节点。为此,我们打开生成的turbo-nochecks-0.json文件,然后选择适当的优化阶段:
在Turbolizer中检查CheckBounds节点
这样,我们可以通过检查优化器中哪些其他组件正在与CheckBounds节点交互来继续进行分析。
如前所述,我们将通过一个简单的调试会话来完成本节和文章,以说明在CheckBounds的情况下v8实现的安全强化。为此,在源代码中VisitCheckBounds在simplified-lowering.cc文件中查找该函数并将断点放在第一if行(d8在断点之前运行一次,否则将不起作用):
断点打在第一if的VisitCheckBounds函数
如果继续调试,我们将看到优化器访问该节点,但是什么也不做。我们对此没有兴趣,因为没有完成降级操作,要进入降级阶段,请输入c调试器,然后输入p lower()。如果Turbofan正在降级阶段,则该lower函数返回true。因此,当我们看到命令返回0x1时,就不继续调试并逐行查看发生了什么。在以前的代码版本中,我们希望调用一个函数,DeferReplacement该函数将消除边界检查。
显示 VisitCheckBounds
边界检查没有了,如果进一步检查,我们将看到v8现在已经实现了某种强化,当尝试访问数组上的越界时,它们会优化代码或直接中止操作。
0x07 学习总结
我们可以看到浏览器的发展速度太快了,而且也变得越来越好了,因此,浏览器的利用在短短几个月内就过时了,这使它真正成为了利用漏洞的捷径,如果只是为了获取浏览器安全知识肯定收获很大。
本文翻译自:https://sensepost.com/blog/2020/intro-to-chromes-v8-from-an-exploit-development-angle/如若转载,请注明原文地址: