$ cat writeup.md…
$ cat writeup.md…
asisctf2026
Task: reverse a remote worker that accepts hex-encoded bytecode for a tiny custom VM guarded by a seed-based capability check. Solution: recover the RUN parser, VM opcodes, and initial register state, then send a minimal six-word program that copies the seed, computes its fmix64 value, and triggers the flag gate.
speaks in mysterious bytes
guards its secrets like a dragon
answers every mistake with the emotional range of a toaster
We are given a downloadable challenge bundle and a remote worker at nc 91.107.151.102 18113. The service is not a memory-corruption target; it is a strict text protocol that decodes a hex payload into a small custom VM program.
Connecting to the service and sending INFO returns the banner:
RB/2 release=fe753d7fdd51acc3b3a23109 words=64 regs=8
Static reversing of revenant-worker showed that the worker accepts only three commands:
INFORUN <hex>QUITRUN requires a non-empty even-length hex string with a maximum of 0x200 characters. The bytes are decoded, grouped into 4-byte little-endian words, and then transformed into internal VM words before validation and execution.
The executor starts with:
r0..r6 = 0 r7 = seed typestate = [0,1,1,1,1,1,1,2]
The important recovered opcodes were:
0xce dst, imm15 → reg[dst] = imm150x2f dst, src, rot6 → reg[dst] = rol64(reg[src], rot6)0x8d dst, src → reg[dst] = fmix64(reg[src] ^ 0x0ae7854d6e616ef3)0xd9 dst, a, b, imm15 → reg[dst] = ((imm15 + 1) * reg[b]) ^ reg[a]0x9e seedReg, hashReg → checks reg[seedReg] == seed and reg[hashReg] == fmix64(seed ^ 0x0ae7854d6e616ef3), then opens flag0xdc → haltBecause r7 already contains the per-connection seed and r0 is zero, the shortest useful program is just: hash r7, copy the seed into a type-accepted register, copy the hash into another type-accepted register, call the capability gate, then halt.
The minimal valid transformed words were:
0xefc6ab35 0x0000398d 0x00023fd9 0x000209d9 0x00000f9e 0x000000dc
One valid raw preimage for those words was:
0xe9cf8542 0x075782d1 0x30e082c0 0xc8befb7a 0x975f3e1d 0xd381cb3b
I used the recovered preimage words directly and packed them as little-endian dwords. That produced the final payload:
RUN 4285cfe9d1825707c082e0307afbbec81d3e5f973bcb81d3
When sent to the worker, the program executes this logic:
fmix64(seed ^ 0x0ae7854d6e616ef3),0x9e,The following script reproduces the successful interaction without embedding the secret in the body:
#!/usr/bin/env python3 import socket import struct HOST = "91.107.151.102" PORT = 18113 RAW_WORDS = [ 0xE9CF8542, 0x075782D1, 0x30E082C0, 0xC8BEFB7A, 0x975F3E1D, 0xD381CB3B, ] def build_payload() -> str: blob = b"".join(struct.pack("<I", w) for w in RAW_WORDS) return "RUN " + blob.hex() def recv_until(sock: socket.socket, marker: bytes) -> bytes: data = b"" while marker not in data: chunk = sock.recv(4096) if not chunk: break data += chunk return data def main() -> None: payload = build_payload() print(payload) with socket.create_connection((HOST, PORT), timeout=5) as sock: sock.sendall(b"INFO\n") print(recv_until(sock, b"\n").decode(errors="replace"), end="") sock.sendall(payload.encode() + b"\n") response = recv_until(sock, b"\n").decode(errors="replace") print(response.replace("ASIS{", "ASIS{REDACTED")) if __name__ == "__main__": main()
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar