Ziran OS — Filesystem CTF · Tier 1 (in-browser)

A real from-scratch 64-bit kernel is booting below — QEMU compiled to WebAssembly, not a replay. Its RAM disk hides a FLAG{…} in bytes with no directory entry: ls will never show it, and cat can't name it. Your job is to make the filesystem hand it to you anyway — by crafting a disk image whose own rules betray it. ← the guided tour · plain live boot

loading…

🚩 Captured!

You made the filesystem hand you a flag it was built to hide:

That's the whole primitive — one dishonest length field turned a bounds check into an out-of-bounds read. Remember this is the white-box sandbox: the flag lived in your tab, so it was always in reach. The uncheatable version, where the flag lives only on a server and the exploit must land over the wire, is Tier 2.

Nice work — submit a writeup to get on the solvers list.

1 · Build a disk image

The kernel's load command mounts a base64 image you paste in. Start from the presets — each one rebuilds the base64 below and explains what it does. Then hit Load into the kernel and try ls / and cat /notes.txt.

① A normal, well-formed disk with two files. The secret sits past data_end, unlisted — ls won't show it and a normal cat can't reach it.

2 · Walkthrough (spoilers — try it first)

How the disk is laid out

ZranFS v3 is dead simple. A 16-byte superblock: "ZRFS", version 3, a file count, the total_size (must equal the image length), and a data_end — the declared ceiling of the "real" data region. Then a table of 32-byte directory entries (a 20-byte name, then a 32-bit offset, length, and kind). Then the file bytes. The flag is appended after data_end, with no entry pointing at it.

Where the bug is

The strict reader (Fs::mount) does confine every file's extent to [.., data_end) — so an entry can't normally reach the flag past it. But data_end is a field inside the image, and you supply the whole image, superblock included. Validating an extent against a number the attacker also controls isn't validation. Preset ② proves the confinement is real (an over-long extent with an honest data_end is rejected — ExtentEscapesData). Preset ③ removes the guard by lying.

The exploit

Forge data_end = total_size (now the whole image counts as "data"), and stretch a listed file's length so its extent runs off the end of its own bytes and over the flag region. Mount passes every check — because you moved the ceiling — and cat /notes.txt zero-copies from its declared offset for its declared length: its own bytes, then everything after them, including the flag. That's preset ③. One dishonest field turns a bounds check into an out-of-bounds read.

Why this is a sandbox — and what Tier 2 is

Everything here runs in your tab, so the flag was always in your reach; the lesson is the primitive, not the secret. Tier 2 makes it a real capture: the kernel runs on a server, the flag lives only in that process's RAM (never in this page, never in git), and the only way to get it is to land this same exploit over the wire — develop it here, capture it there. That's the version worth bragging about.

Tier 2 is built and being hardened for public launch — it is not live yet. Develop your exploit here in the meantime; when the remote capture opens, this same crafted image is what you'll fire over the wire.