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实际上由两个独立的堆组成,彼此不可见:
-
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 漏洞触发流程
setRemoteCandidatesAPI被调用,传入一个JavaScript数组(arr2)- EdgeHTML创建
CModernArray<>内部结构(分配在DOM堆) - 遍历arr2数组:
- 将arr2[0]复制到
CModernArray<>的缓冲区 - 访问arr2[1]时触发getter方法
- 将arr2[0]复制到
- 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虽然是强大的缓解措施,但并非完美
- 安全机制的设计假设需要全面验证
- 复杂系统中的交互边界是漏洞的高发区域
- 混合使用自动和手动内存管理时需特别谨慎
此案例展示了即使是最精心设计的安全机制也可能存在盲点,强调了深度理解系统内部工作原理的重要性,以及防御性编程的必要性。