$ cat writeup.md…
$ cat writeup.md…
asisctf2026
Task: a custom line-based VM/object store exposes notes, frames, checkpoints, and signal-driven execution, protected by per-frame seals. Solution: revive a stale bookmark through 8-bit generation wrap, read and rewrite a live frame, forge the primary seal, and abuse RESTORE's hidden /flag path.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
A paranoid VM, a badly timed signal, and a flag begging to escape.
We are given a remote service implementing a custom line-based VM and object store. The timer/signal part matters for creating a checkpoint, but the real exploit is not a classic buffer overflow and not just a race: it is stale-object reuse caused by truncated generation checks, followed by frame seal forgery.
The service understands these commands:
NEWNOTE <hex>EDITBOOKMARKREADWRITEDROPTIMERRUNRESTOREQUITIt manages fixed-size slot objects. Each slot is 0xc8 bytes total: 8 bytes of metadata (state and generation) plus a 0xc0-byte payload. Notes and VM frames both live inside this shared slot array.
That shared storage is where the real bug appears. BOOKMARK saves only the low byte of a note generation. Later, READ and WRITE only require the target slot to still be non-zero and to match that truncated generation byte; they do not require the object to still be a note. So after bookmarking a note, freeing it, and cycling the same slot through 255 generation increments, the old bookmark becomes valid again for one generation window when a frame lands in the same slot.
This gives stale-bookmark arbitrary READ/WRITE over a live frame object. That primitive is enough to win.
The signal machinery is still needed, but only for checkpoint creation. TIMER 1000 followed by RUN <frame_id> 92 <frame_id> 6f reliably makes the VM save a checkpoint when opcode 0x92 runs long enough for SIGALRM to flip the global flag.
...
$ grep --similar