强网杯2020-GooExec chrome pwn分析及两种利用思路
字数 2095 2025-08-20 18:18:05

强网杯2020-GooExec Chrome Pwn漏洞分析与利用教学

1. 环境搭建

1.1 题目环境

  • 操作系统: Ubuntu 20.04
  • Chrome启动命令:
    ./chrome --js-flags=--noexpose_wasm --no-sandbox
    
    • --js-flags=--noexpose_wasm: 关闭WASM功能,阻止使用WASM填写shellcode
    • --no-sandbox: 关闭沙箱

1.2 题目下载

2. V8基础知识

2.1 V8数组元素类型

V8中数组元素有以下几种类型:

  1. PACKED_SMI_ELEMENTS: 小整数(Smi)
  2. PACKED_DOUBLE_ELEMENTS: 双精度浮点数
  3. PACKED_ELEMENTS: 常规元素(不能表示为Smi或双精度的值)

2.2 类型转换规则

  • 转换只能从特定类型向更一般的类型进行
  • 一旦数组被标记为更一般的类型,就不能回到更特定的类型

示例代码:

const array = [1, 2, 3]; // PACKED_SMI_ELEMENTS
array.push(4.56); // PACKED_DOUBLE_ELEMENTS
array.push('x'); // PACKED_ELEMENTS

2.3 PACKED到HOLEY类型转换

密集数组可以转换为稀疏数组:

const array = [1, 2, 3, 4.56, 'x']; // PACKED_ELEMENTS
array.length; // 5
array[9] = 1; // array[5]到array[8]现在是holes
// 类型变为HOLEY_ELEMENTS

3. 漏洞分析

3.1 漏洞补丁

漏洞与Chromium Issue 799263相同,引入漏洞的补丁删除了state = state->KillMaps(alias_info, zone())这行代码。

补丁差异:

diff --git a/src/compiler/load-elimination.cc b/src/compiler/load-elimination.cc
index ff79da8c86..8effdd6e15 100644
--- a/src/compiler/load-elimination.cc
+++ b/src/compiler/load-elimination.cc
@@ -866,8 +866,8 @@ Reduction LoadElimination::ReduceTransitionElementsKind(Node* node) {
     if (object_maps.contains(ZoneHandleSet<Map>(source_map))) {
       object_maps.remove(source_map, zone());
       object_maps.insert(target_map, zone());
-      AliasStateInfo alias_info(state, object, source_map);
-      state = state->KillMaps(alias_info, zone());
+      // AliasStateInfo alias_info(state, object, source_map);
+      // state = state->KillMaps(alias_info, zone());
       state = state->SetMaps(object, object_maps, zone());
     }
   } else {
@@ -892,7 +892,7 @@ Reduction LoadElimination::ReduceTransitionAndStoreElement(Node* node) {
     if (state->LookupMaps(object, &object_maps)) {
       object_maps.insert(double_map, zone());
       object_maps.insert(fast_map, zone());
-      state = state->KillMaps(object, zone());
+      // state = state->KillMaps(object, zone());
       state = state->SetMaps(object, object_maps, zone());
     }
     // Kill the elements as well.

3.2 漏洞原理

  • KillMaps函数负责消除alias对象的map信息
  • 去除KillMaps会导致本应没有map信息的一些node仍保留着信息
  • ReduceCheckMaps函数中,残留的map信息可能导致maps.contains错误地返回true
  • 这会导致错误地删除CheckMaps节点,造成类型混淆

3.3 POC分析

function foo(a, b) {
  let tmp = {};
  b[0] = 0;
  a.length;
  for (let i = 0; i < a.length; i++) {
    a[i] = tmp;
  }
  let o = [1.1];
  b[15] = 4.063e-320;
  return o;
}

let arr_addr_of = new Array(1);
arr_addr_of[0] = 'a';
for (let i = 0; i < 10000; i++) {
  eval(`var tmp_arr = [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];`);
  foo(arr_addr_of, [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]);
  foo(tmp_arr, [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]);
}

var float_arr = [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];
var oob_array = foo(float_arr, float_arr, {});
console.log(oob_array.length);
  • a[0]=0会传入arr_addr_of(HOLEY_ELEMENTS)和tmp_arr(PACKED_DOUBLE_ELEMENTS)
  • 优化编译时,浮点数组会被转化为对象数组(HOLEY_ELEMENTS)
  • 漏洞触发后,数组b被转化为对象数组,但访问仍按浮点数组类型进行
  • 由于指针压缩,浮点数组转对象数组后长度缩短一半,可以精准覆盖后面数组o的长度

4. 漏洞利用

4.1 利用思路一: 重新开启WASM

步骤:

  1. 泄露V8 ELF基址:

    • 通过obj.constructor->code->text_addr(Builtins_ArrayConstructor函数地址)泄露
  2. 查找并修改FLAG_expose_wasm:

    • 使用IDA查找"FLAG_expose_wasm"特征字符
    • 找到.data区中该变量的地址并修改为true
  3. 利用WASM执行shellcode:

    • 根据mark查找wasm_function对象地址
    • 通过wasm_function->shared_info->WasmExportedFunctionData(data)->instance+0x68找到rwx区域
    • 将shellcode写入该区域

注意事项:

  • Chrome运行时会有多个进程,需要确定哪个进程运行V8
  • 通常在第3个进程(libv8.so)中
  • 修改FLAG_expose_wasm只影响当前进程
  • 需要两个HTML文件: 一个开启WASM,一个利用WASM

4.2 利用思路二: ROP链利用

步骤:

  1. 获取libc基址:

    • 通过泄露printf .got表上的printf函数地址
    • 减去libc中printf的偏移得到libc基址
  2. 获取栈地址:

    • 查找libc中的environ变量偏移
    • environ变量保存着栈地址
  3. 布置ROP链:

    add rsp 0x78; pop rbx; pop rbp; ret
    ...
    pop rdi; ret shellcode_addr
    pop rsi; ret 0x1000
    pop rdx; ret 0x7
    mprotect_addr
    shellcode_addr
    
  4. 执行shellcode:

    • 使用mprotect修改堆区域属性为rwx
    • 跳转到该区域执行shellcode

注意事项:

  • Chrome会启动多个进程执行JS
  • 需要在带有--no-v8-untrusted-code-mitigations标志的进程中查找ROP链
  • 可以先开启WASM创建对象,确定JS运行的进程

5. 参考链接

  1. https://mem2019.github.io/jekyll/update/2020/09/19/QWB-GooExec.html
  2. https://github.com/ray-cp/browser_pwn/tree/master/v8_pwn/qwb2020-final-GOOexec_chromium
  3. https://bugs.chromium.org/p/chromium/issues/detail?id=799263

6. 利用代码

强网杯2020-GooExec Chrome Pwn漏洞分析与利用教学 1. 环境搭建 1.1 题目环境 操作系统: Ubuntu 20.04 Chrome启动命令: --js-flags=--noexpose_wasm : 关闭WASM功能,阻止使用WASM填写shellcode --no-sandbox : 关闭沙箱 1.2 题目下载 下载地址: https://github.com/De4dCr0w/Browser-pwn/blob/master/Vulnerability%20analyze/qwb2020-final-GOOexec%20%26%20Issue-799263/file.7z 2. V8基础知识 2.1 V8数组元素类型 V8中数组元素有以下几种类型: PACKED_ SMI_ ELEMENTS : 小整数(Smi) PACKED_ DOUBLE_ ELEMENTS : 双精度浮点数 PACKED_ ELEMENTS : 常规元素(不能表示为Smi或双精度的值) 2.2 类型转换规则 转换只能从特定类型向更一般的类型进行 一旦数组被标记为更一般的类型,就不能回到更特定的类型 示例代码: 2.3 PACKED到HOLEY类型转换 密集数组可以转换为稀疏数组: 3. 漏洞分析 3.1 漏洞补丁 漏洞与Chromium Issue 799263相同,引入漏洞的补丁删除了 state = state->KillMaps(alias_info, zone()) 这行代码。 补丁差异: 3.2 漏洞原理 KillMaps 函数负责消除alias对象的map信息 去除 KillMaps 会导致本应没有map信息的一些node仍保留着信息 在 ReduceCheckMaps 函数中,残留的map信息可能导致 maps.contains 错误地返回true 这会导致错误地删除 CheckMaps 节点,造成类型混淆 3.3 POC分析 a[0]=0 会传入 arr_addr_of (HOLEY_ ELEMENTS)和 tmp_arr (PACKED_ DOUBLE_ ELEMENTS) 优化编译时,浮点数组会被转化为对象数组(HOLEY_ ELEMENTS) 漏洞触发后,数组b被转化为对象数组,但访问仍按浮点数组类型进行 由于指针压缩,浮点数组转对象数组后长度缩短一半,可以精准覆盖后面数组o的长度 4. 漏洞利用 4.1 利用思路一: 重新开启WASM 步骤: 泄露V8 ELF基址 : 通过 obj.constructor->code->text_addr (Builtins_ ArrayConstructor函数地址)泄露 查找并修改FLAG_ expose_ wasm : 使用IDA查找"FLAG_ expose_ wasm"特征字符 找到.data区中该变量的地址并修改为true 利用WASM执行shellcode : 根据mark查找wasm_ function对象地址 通过 wasm_function->shared_info->WasmExportedFunctionData(data)->instance+0x68 找到rwx区域 将shellcode写入该区域 注意事项: Chrome运行时会有多个进程,需要确定哪个进程运行V8 通常在第3个进程(libv8.so)中 修改FLAG_ expose_ wasm只影响当前进程 需要两个HTML文件: 一个开启WASM,一个利用WASM 4.2 利用思路二: ROP链利用 步骤: 获取libc基址 : 通过泄露printf .got表上的printf函数地址 减去libc中printf的偏移得到libc基址 获取栈地址 : 查找libc中的 environ 变量偏移 environ 变量保存着栈地址 布置ROP链 : 执行shellcode : 使用mprotect修改堆区域属性为rwx 跳转到该区域执行shellcode 注意事项: Chrome会启动多个进程执行JS 需要在带有 --no-v8-untrusted-code-mitigations 标志的进程中查找ROP链 可以先开启WASM创建对象,确定JS运行的进程 5. 参考链接 https://mem2019.github.io/jekyll/update/2020/09/19/QWB-GooExec.html https://github.com/ray-cp/browser_ pwn/tree/master/v8_ pwn/qwb2020-final-GOOexec_ chromium https://bugs.chromium.org/p/chromium/issues/detail?id=799263 6. 利用代码 漏洞利用一: exp-FLAG_ expose_ wasm.html exp-FLAG_ expose_ wasm1.html 漏洞利用二: exp.html