V8基础内容梳理

96 阅读8分钟

V8基础内容梳理

看到一个不错的V8基础内容介绍,exploitreversing.com/2025/01/22/… ,想到可以写一个简单的内容梳理。

Chrome架构介绍

chrome是多进程架构,毕竟开一个网页那么多任务,单进程会很慢。

  • Browser Process:负责管理整个浏览器的生命周期。处理 UI(地址栏、书签、前进/后退按钮等)。管理其他进程的创建与销毁。负责网络请求(通过 Network Service)、存储(如 Cookie、本地存储)、安全策略等。
  • Renderer Process:负责渲染网页内容(HTML、CSS)。执行网页中的 JavaScript 代码。(V8)处理 DOM 操作、事件处理、样式计算等。每个标签页(或站点)通常运行在独立的 Renderer Process 中(出于安全和隔离考虑,采用站点隔离 Site Isolation 策略)。
  • Plugin and Extension Process:用于运行 NPAPI/PPAPI 插件(如旧版 Flash)。扩展本质上是用 HTML/CSS/JS 编写的“微型网页”。扩展的后台脚本(background scripts)、内容脚本(content scripts)等运行在独立的 Renderer Process中(有时与网页共享,有时隔离)。也会运行V8。
  • GPU Process:处理所有与图形加速相关的任务(如 3D 变换、WebGL、视频解码、Canvas 渲染等)。将渲染指令发送给 GPU,提高图形性能并隔离 GPU 驱动崩溃风险。
  • Utility Process:执行各种“辅助性”任务,多为短期操作。

尽管 Chrome 启动了多个进程(Browser、Renderer、GPU、Utility 等),但只有 Browser Process 拥有较高的系统权限,负责协调全局资源(如文件访问、网络连接、窗口管理等)。

所有其他进程(Renderer、GPU、Utility、Extension 等)都运行在严格限制的沙箱(Sandbox)环境中,其特点包括:

  • 权限受限:无法直接访问文件系统、网络、设备或用户数据。
  • 隔离性强:即使某个 Renderer Process 被恶意代码攻破(例如通过浏览器漏洞),攻击者也无法直接读取用户文件或控制系统。
  • 依赖 Browser Process 代理:当沙箱进程需要执行特权操作(如保存文件、发起网络请求),必须通过进程间通信(IPC, Inter-Process Communication) 向 Browser Process 发起请求,由 Browser Process 在验证安全策略后代为执行。

V8 是 Chrome 中负责高性能解析、编译和执行 JavaScript 与 WebAssembly 代码的核心引擎,运行于沙箱化的渲染进程中,是现代 Web 应用流畅运行的关键基石。

JS执行流程:Ignition (解释器) → Sparkplug (非优化编译器) → Maglev (轻量优化编译器) → Turbofan (深度优化编译器)

WebAssembly 代码走独立编译路径(Liftoff 快速编译器 + TurboFan 优化),不经过 Ignition/Sparkplug/Maglev。

1. Ignition(解释器)
  • 作用:快速启动执行,生成字节码(bytecode)并解释执行。
  • 优点:启动快、内存占用低。
  • 所有 JS 代码首先由 Ignition 执行
  • 在执行过程中,V8 会收集类型反馈(type feedback)调用频率等信息。
2. Sparkplug(快速编译器,2021 年引入)
  • 作用:将 Ignition 的字节码直接编译为未经优化的机器码,不依赖类型反馈。
  • 目标:减少 Ignition 解释执行的开销,提升中等热度函数的性能,同时避免 TurboFan 的高编译成本。
  • 特点:
    • 编译速度极快(比 TurboFan 快 10 倍以上)。
    • 生成的代码性能优于 Ignition,但不如优化后的 TurboFan。
  • 触发条件:函数被多次调用(但尚未热到需要 TurboFan)。
3. Maglev(轻量级优化编译器,2023 年正式启用)
  • 作用:介于 Sparkplug 和 TurboFan 之间的中等优化编译器
  • 目标:以较低的编译开销获得比 Sparkplug 更好的性能,缓解 TurboFan 的压力。
  • 特点:
    • 利用 Ignition 收集的类型反馈进行简单优化(如内联缓存、常量传播)。
    • 编译速度比 TurboFan 快,代码质量优于 Sparkplug。
  • 适用场景:中等热度、不适合 TurboFan(如编译成本过高)的函数。
  • 注意:Maglev 并非对所有函数都启用,目前主要用于提升 Web 应用的整体响应速度。
4. TurboFan(深度优化编译器)
  • 作用:对“热点函数”(hot functions)进行高度优化的机器码生成
  • 依赖:丰富的类型反馈和运行时 profile 数据。
  • 优化手段:内联、死代码消除、类型特化、逃逸分析等。
  • 缺点:编译耗时长、内存开销大。
  • 触发条件:函数被频繁调用(如循环内、高频事件处理器)。

TurboFan 是优化编译器,也是类型混淆漏洞的高发区(因其依赖类型反馈,若反馈错误会导致错误优化,如错误的类型假设 → 越界访问)。

Maglev 虽然较新,但也引入了新的攻击面(如其简化版的类型推断逻辑可能存在漏洞)。

Ignition 字节码本身也可能成为攻击目标(如字节码 handler 漏洞)。

V8内存管理基础概念

Pointer Tagging:为了更好地利用空间,V8会用指针的最低位来进行标识。如果是0则代表一个小整数,如果是1则表示这是一个堆对象指针。

指针压缩:V8为了节省空间,指针只记录基于区域基地址的偏移。

主要元素类型:SMI_ELEMENTS (仅包含小整数)、DOUBLE_ELEMENTS (仅包含浮点数)、ELEMENTS (包含其他类型,如字符串、对象)。

Map

为了增加访问速度,JS为具有相同属性名和顺序的对象创建一个共享的Map。

Map 结构包含了对象的动态类型、大小、属性描述符数组 (DescriptorArray)、原型指针等元信息。

当给一个对象添加新属性时,V8 会创建一个新的 Map(称为过渡),并建立从旧 Map 到新 Map 的链接。这形成了一个Map 过渡链

对象属性

  • Fast Properties:对于属性结构稳定的对象,属性值直接存储在对象内部或一个紧凑的数组中,通过索引快速访问。
  • Slow Properties:对于频繁增删属性的对象,属性会以字典形式存储,访问较慢但修改灵活。
  • In-object properties:部分属性可以直接存储在对象自身的内存空间内,访问速度最快。

在 V8 漏洞利用中(如类型混淆、越界读写、OOB 等),通常需要:

  • 稳定地布局堆内存(heap grooming);
  • 精确控制对象内存布局,以便通过越界访问篡改相邻对象的字段(如 Map 指针、元素指针等);
  • 构造可靠的 addrof / fakeobj 原语

Fast Properties 正好提供了可预测、紧凑、连续的内存布局,使其成为理想目标。

Backing Store

Backing store 是 ArrayBuffer 底层用于实际存储二进制数据的内存区域,JavaScript 代码通过 ArrayBuffer 对象间接访问它。

在旧版 V8 中,ArrayBufferbacking store 可以通过 ArrayBuffer.prototype.transfer() 或类型混淆被重定向,实现任意地址读写,backing store是老版本写poc常用的跳板。

现在 V8 已将 backing store 指针移入 External Pointer Table,并通过句柄访问。

即使你能篡改 ArrayBuffer 对象内部的指针字段,也无法直接控制其指向任意地址——必须先破坏外部指针表伪造合法句柄

V8垃圾回收

V8 使用分代式垃圾回收。新创建的对象在 新生代 (New Space),存活时间长的对象会被晋升到 老生代 (Old Space)

目前是采用用 Scavenge 算法。

  • 使用 semi-space 结构(From/To 空间)。
  • 活跃对象从 From 复制到 To,死亡对象直接丢弃。
  • 优点:速度快、无碎片;缺点:内存利用率仅 50%。
  • 适合处理大量短命对象(符合“弱分代假说”)。

V8沙箱

除了操作系统级别的进程沙箱,V8 自身也引入了堆内沙箱(In-Process Sandbox),专门防御内存破坏漏洞在 Renderer 进程内部的利用。

传统的内存破坏漏洞(如越界读写)可以被用来篡改对象的 Map 或指针,从而实现任意读写(addrof/fakeobj 原语),最终执行任意代码。V8 沙箱旨在从根本上缓解这类攻击。

核心思路:将整个 V8 堆(包括对象、代码等)放入一个受保护的、隔离的 1TB 虚拟地址空间(即沙箱)中。

实现机制:

  • 沙箱化指针 (Sandboxed Pointers):V8 堆内部的对象间引用不再使用原始的 64 位指针,而是使用 40 位偏移量(相对于沙箱基地址)。这确保了任何指针篡改都只能在沙箱内部进行,无法直接指向沙箱外的任意地址。
  • 指针表 (Pointer Tables):对于必须存在于沙箱外部的对象(如 DOM 节点、ArrayBuffer 的后备存储),V8 使用外部指针表 (External Pointer Table)。沙箱内的对象只持有指向该表的句柄 (Handle)(一个带类型的索引)。访问时,V8 会通过句柄查表,并进行类型检查,防止类型混淆。
  • 可信空间 (Trusted Space):一些关键的、不可变的 V8 内部对象(如字节码)被放入一个单独的“可信空间”,并通过可信指针表 (Trusted Pointer Table) 进行访问,进一步隔离。
  • 代码指针沙箱化 (Code Pointer Sandboxing):函数指针也被放入代码指针表 (Code Pointer Table),防止攻击者通过篡改函数指针来劫持控制流(如 JIT-ROP)。