pwnfreemedium

bookmaker

umdctf

Task: a stripped PIE QuickJS service executes attacker-supplied JavaScript with native Ledger and Wire bindings, including a recycled Ledger whose stale ArrayBuffer alias remains valid. Solution: overlap freed Ledger storage with a new ArrayBuffer, corrupt its metadata to leak PIE and gain arbitrary read/write, overwrite the global settle callback with win, then trigger settle() to spawn a shell and read the flag.

$ ls tags/ techniques/
function_pointer_overwritearbitrary_read_writeheap_uaf_overlaparraybuffer_metadata_corruptionpie_base_leak

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

bookmaker — UMDCTF

Description

No separate organizer description was present in the provided workspace files.

English summary: the service accepts JavaScript and executes it inside an embedded QuickJS environment until the terminator token __END_MARKET_SCRIPT__. Several native bindings are exposed to script code, and one of them makes it possible to turn a heap use-after-free into code execution.

Reconnaissance

Initial triage showed an amd64 PIE ELF with:

  • Full RELRO
  • NX
  • no stack canary
  • stripped symbols

The binary also embeds QuickJS and exposes the following JavaScript bindings:

  • Ledger
  • Wire
  • mintWire
  • wireRead
  • wireWrite
  • wireDispatch
  • poke64
  • print
  • help
  • settle

The remote service reads JavaScript source until __END_MARKET_SCRIPT__, then executes it. That immediately suggests looking for mistakes in the native objects that are wrapped into JavaScript values, especially anything returning ArrayBuffer views into native memory.

Ledger was the interesting object. Its view() method returns an ArrayBuffer alias to native backing storage, and recycle() frees that storage. That combination is already suspicious: if the engine keeps the old JavaScript object alive after the native free, the script may still access freed heap memory.

Vulnerability

The core bug is a use-after-free in Ledger backing storage.

The vulnerable pattern is:

  1. Create a Ledger.
  2. Call ledger.view() to get an ArrayBuffer pointing at the Ledger's native bytes.
  3. Call ledger.recycle().
  4. Continue using the old ArrayBuffer.

The stale ArrayBuffer remains accessible even though the native allocation has already been freed.

For a Ledger(0x30), the freed chunk can be immediately reclaimed with new ArrayBuffer(8). That places a new QuickJS-managed ArrayBuffer object over the old Ledger backing region. A typed array over the stale view then exposes the overlapped metadata:

...

$ grep --similar

Similar writeups