ZDI年度五大漏洞之——利用内存垃圾回收MemGC的盲点
字数 1777 2025-08-27 12:33:37

MemGC内存垃圾回收机制中的盲点分析与利用

1. 漏洞概述

CVE-2018-8179是一个在Microsoft Edge浏览器中发现的高危漏洞,由Pwn2Own冠军Richard Zhu(fluorescence)发现并利用。该漏洞利用了MemGC(内存垃圾回收)机制中的一个设计盲点,成功实现了Use-After-Free(UAF)攻击。

2. MemGC机制基础

MemGC是Microsoft设计的一种内存保护机制,旨在防止Use-After-Free漏洞:

  • 设计目标:跟踪MemGC堆中所有存在的指针,确保当指针仍然存在时,不会释放相关内存
  • 工作原理:通过"标记-清除"算法进行垃圾回收
    • 标记阶段:扫描所有存活的堆分配、堆栈和寄存器,寻找指向其他堆分配的指针
    • 清除阶段:释放未被标记的内存块

3. MemGC的实际实现细节

3.1 双堆结构

MemGC实际上由两个独立的堆组成,彼此不可见:

  1. Chakra堆

    • 用于JavaScript引擎(Chakra)内部
    • 存储JavaScript对象和Chakra内部数据结构
    • 每个JavaScript执行线程有自己的Chakra堆实例
  2. DOM堆

    • 由chakra.dll提供给外部使用(如edgehtml.dll)
    • 存储DOM对象和edgehtml.dll的大多数堆分配
    • 取代了Internet Explorer早期的内存保护机制

3.2 回收器(Recycler)实现

  • 每个堆由独立的chakra!Memory::Recycler实例管理
  • 回收器只了解自己管理的堆分配范围
  • 在标记阶段会过滤掉不属于自己管理范围的地址值

4. 漏洞原理分析

4.1 漏洞触发流程

  1. setRemoteCandidates API被调用,传入一个JavaScript数组(arr2)
  2. EdgeHTML创建CModernArray<>内部结构(分配在DOM堆)
  3. 遍历arr2数组:
    • 将arr2[0]复制到CModernArray<>的缓冲区
    • 访问arr2[1]时触发getter方法
  4. getter方法中:
    • 释放arr2[0]引用的对象(Chakra堆分配)
    • 回收内存并用攻击者控制的数据覆写
  5. setRemoteCandidates继续执行,尝试访问arr2[0]引用的对象时崩溃

4.2 为什么MemGC未能阻止UAF

  1. 跨堆指针不可见

    • CModernArray<>缓冲区在DOM堆分配
    • 其中包含指向Chakra堆的指针
    • Chakra堆的回收器会过滤掉这些指针(因为不在Chakra堆范围内)
  2. 垃圾回收时的行为

    • 内存压力触发Chakra堆的垃圾回收
    • 回收器扫描到指向CModernArray<>缓冲区的指针(在DOM堆)
    • 立即过滤掉这个指针,不扫描其内容
    • 导致未清空的指针被忽略,相关内存被错误释放

5. 漏洞利用技术

5.1 关键利用点

  • 通过精心设计的JavaScript数组和getter方法
  • 控制内存释放和重新分配的时机
  • 利用跨堆指针的不可见性绕过MemGC保护

5.2 利用链构建

  1. 第一个UAF:触发跨堆指针问题
  2. 第二个UAF:进一步控制执行流程
  3. 组合利用实现代码执行

6. 补丁分析

Microsoft通过以下方式修复此漏洞:

  1. 显式引用计数

    • 在将对象添加到CModernArray<>前调用chakra::JsVarAddRef
    • 手动管理对象生命周期
  2. 特定情况处理

    • 对于edgehtml!ORTC::UnpackArrayObjectVar等特殊情况
    • 放弃依赖MemGC,改用显式对象管理

7. 防御建议

  1. 内存管理设计

    • 统一内存管理域,避免"分裂堆"问题
    • 确保跨堆指针能被正确跟踪
  2. 代码审计重点

    • 关注跨组件/跨堆的接口调用
    • 特别注意带有回调或getter/setter的API
  3. 缓解措施

    • 及时应用安全更新
    • 启用控制流保护(CFG)等缓解技术

8. 总结与启示

  • MemGC虽然是强大的缓解措施,但并非完美
  • 安全机制的设计假设需要全面验证
  • 复杂系统中的交互边界是漏洞的高发区域
  • 混合使用自动和手动内存管理时需特别谨慎

此案例展示了即使是最精心设计的安全机制也可能存在盲点,强调了深度理解系统内部工作原理的重要性,以及防御性编程的必要性。

MemGC内存垃圾回收机制中的盲点分析与利用 1. 漏洞概述 CVE-2018-8179是一个在Microsoft Edge浏览器中发现的高危漏洞,由Pwn2Own冠军Richard Zhu(fluorescence)发现并利用。该漏洞利用了MemGC(内存垃圾回收)机制中的一个设计盲点,成功实现了Use-After-Free(UAF)攻击。 2. MemGC机制基础 MemGC是Microsoft设计的一种内存保护机制,旨在防止Use-After-Free漏洞: 设计目标 :跟踪MemGC堆中所有存在的指针,确保当指针仍然存在时,不会释放相关内存 工作原理 :通过"标记-清除"算法进行垃圾回收 标记阶段:扫描所有存活的堆分配、堆栈和寄存器,寻找指向其他堆分配的指针 清除阶段:释放未被标记的内存块 3. MemGC的实际实现细节 3.1 双堆结构 MemGC实际上由两个独立的堆组成,彼此不可见: Chakra堆 : 用于JavaScript引擎(Chakra)内部 存储JavaScript对象和Chakra内部数据结构 每个JavaScript执行线程有自己的Chakra堆实例 DOM堆 : 由chakra.dll提供给外部使用(如edgehtml.dll) 存储DOM对象和edgehtml.dll的大多数堆分配 取代了Internet Explorer早期的内存保护机制 3.2 回收器(Recycler)实现 每个堆由独立的 chakra!Memory::Recycler 实例管理 回收器只了解自己管理的堆分配范围 在标记阶段会过滤掉不属于自己管理范围的地址值 4. 漏洞原理分析 4.1 漏洞触发流程 setRemoteCandidates API被调用,传入一个JavaScript数组(arr2) EdgeHTML创建 CModernArray<> 内部结构(分配在DOM堆) 遍历arr2数组: 将arr2[ 0]复制到 CModernArray<> 的缓冲区 访问arr2[ 1 ]时触发getter方法 getter方法中: 释放arr2[ 0 ]引用的对象(Chakra堆分配) 回收内存并用攻击者控制的数据覆写 setRemoteCandidates 继续执行,尝试访问arr2[ 0 ]引用的对象时崩溃 4.2 为什么MemGC未能阻止UAF 跨堆指针不可见 : CModernArray<> 缓冲区在DOM堆分配 其中包含指向Chakra堆的指针 Chakra堆的回收器会过滤掉这些指针(因为不在Chakra堆范围内) 垃圾回收时的行为 : 内存压力触发Chakra堆的垃圾回收 回收器扫描到指向 CModernArray<> 缓冲区的指针(在DOM堆) 立即过滤掉这个指针,不扫描其内容 导致未清空的指针被忽略,相关内存被错误释放 5. 漏洞利用技术 5.1 关键利用点 通过精心设计的JavaScript数组和getter方法 控制内存释放和重新分配的时机 利用跨堆指针的不可见性绕过MemGC保护 5.2 利用链构建 第一个UAF:触发跨堆指针问题 第二个UAF:进一步控制执行流程 组合利用实现代码执行 6. 补丁分析 Microsoft通过以下方式修复此漏洞: 显式引用计数 : 在将对象添加到 CModernArray<> 前调用 chakra::JsVarAddRef 手动管理对象生命周期 特定情况处理 : 对于 edgehtml!ORTC::UnpackArrayObjectVar 等特殊情况 放弃依赖MemGC,改用显式对象管理 7. 防御建议 内存管理设计 : 统一内存管理域,避免"分裂堆"问题 确保跨堆指针能被正确跟踪 代码审计重点 : 关注跨组件/跨堆的接口调用 特别注意带有回调或getter/setter的API 缓解措施 : 及时应用安全更新 启用控制流保护(CFG)等缓解技术 8. 总结与启示 MemGC虽然是强大的缓解措施,但并非完美 安全机制的设计假设需要全面验证 复杂系统中的交互边界是漏洞的高发区域 混合使用自动和手动内存管理时需特别谨慎 此案例展示了即使是最精心设计的安全机制也可能存在盲点,强调了深度理解系统内部工作原理的重要性,以及防御性编程的必要性。