Category: Hardware
Flag: apoorvctf{3N7R0P1C_31D0L0N_0F_7H3_50C_4N4LY57_N0C7URN3}
Challenge Description
Goal Phase-lock UART baud-rate oscillator to exactly 2,345,679 baud, exercise the 512-bit hardware multiplier, extract the fixed flag token before the 45 s supercapacitor drains.
Connection nc chals4.apoorvctf.xyz 1337
Protocol (8N1 framing)
Enter CALIBRATE Send single byte 0xCA (no reply).
Calibration burst (repeat until LOCKED) Send exactly 64× 0x55 bytes with precise baud timing. Server replies: ERR:+00123 / ERR:-00045 (PPM error) Target: |error| ≤ 1,000 PPM for 5 consecutive good bursts → LOCKED
Locked mode (45 s window)
Send: 0xAA + 64-byte A + 64-byte B (512-bit big-endian operands)
Receive: FLAG
Critical Warnings
HSM tamper fuse is one-time and permanent per TCP session.
Never send: JTAG/SWD, flash reads, garbage bytes, or wrong patterns → ERR
Inter-byte delay = 10 / 2_345_679 s ≈ 4.263 µs.
Use busy-wait loop with time.perf_counter_ns() (sleep is too coarse).
No math needed — flag is constant once lock succeeds.
Errors you’ll see
ERR
Disconnect/reconnect for a fresh chip if fuse blows. Good luck!
Analysis
This service behaves like a strict UART emulation with a tamper fuse, so the first thing that mattered was protocol exactness rather than brute force. I used a Python socket script with TCP_NODELAY and time.perf_counter_ns() busy-wait scheduling so each calibration burst sends exactly 64 bytes of 0x55 with microsecond-level spacing. The script also reconnects with fresh sessions and tries only minimal command variants for the initial calibration token because a wrong preamble immediately poisons that session.
The interesting troll was that newline-terminated calibration strings looked natural but were actually wrong for this target. Both CALIBRATE\n and CALIBRATE\r\n immediately triggered the fuse, which is exactly the kind of thing that can waste time if you keep debugging timing while the session is already dead.

Once the script switched to raw CALIBRATE (no newline), the hardware state machine accepted calibration and returned stable execution timing lines, then LOCKED after five good bursts. At that point the challenge description was honest: any valid 512-bit operands work. I sent 0xAA followed by two 64-byte big-endian values (...01 and ...02) and the server returned the fixed flag immediately. The solve ended up being elegantly short after respecting framing details and not overthinking the multiplier stage.

The exact command used to execute the solver was:
python resonance_lock_solver.py=== Session attempt 1 with command b'CALIBRATE\n' ===
[001] ERR:HSM_TAMPER_FUSE_BLOWN
=== Session attempt 2 with command b'CALIBRATE\r\n' ===
[001] ERR:HSM_TAMPER_FUSE_BLOWN
=== Session attempt 3 with command b'CALIBRATE' ===
[001] EXEC_TIME:268380
[002] EXEC_TIME:268380
[003] EXEC_TIME:268380
[004] EXEC_TIME:268380
[005] EXEC_TIME:268380
LOCKED
LOCKED achieved
FLAG:apoorvctf{3N7R0P1C_31D0L0N_0F_7H3_50C_4N4LY57_N0C7URN3}
FLAG_FOUND:apoorvctf{3N7R0P1C_31D0L0N_0F_7H3_50C_4N4LY57_N0C7URN3}Solution
#!/usr/bin/env python3
import re
import socket
import time
HOST = "chals4.apoorvctf.xyz"
PORT = 1337
TARGET_BAUD = 2_345_679
TARGET_PERIOD_NS = int(round((10.0 / TARGET_BAUD) * 1e9))
def recv_chunk(sock: socket.socket, timeout: float = 1.2) -> str:
sock.settimeout(timeout)
try:
data = sock.recv(4096)
except socket.timeout:
return ""
except OSError:
return ""
if not data:
return ""
return data.decode("utf-8", "ignore")
def send_cal_burst(sock: socket.socket, period_ns: int) -> None:
b = b"\x55"
now = time.perf_counter_ns()
sock.sendall(b)
next_ts = now + period_ns
for _ in range(63):
while time.perf_counter_ns() < next_ts:
pass
sock.sendall(b)
next_ts += period_ns
def run_session(cal_cmd: bytes = b"CALIBRATE\n") -> str | None:
with socket.create_connection((HOST, PORT), timeout=8) as sock:
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
sock.settimeout(1.0)
_ = recv_chunk(sock, timeout=0.3)
sock.sendall(cal_cmd)
sock.sendall(b"\xCA")
period_ns = TARGET_PERIOD_NS
sign_dir = 1.0
prev_abs = None
start = time.time()
for _ in range(1, 220):
if time.time() - start > 30:
return None
send_cal_burst(sock, period_ns)
resp = recv_chunk(sock, timeout=1.3)
if not resp:
continue
if "HSM_TAMPER_FUSE_BLOWN" in resp:
return None
if "ERR:PROTO" in resp or "ERR:PATTERN" in resp or "ERR:PAYLOAD" in resp:
return None
if "LOCKED" in resp:
break
m = re.search(r"ERR:([+-]\d+)", resp)
if not m:
continue
err_ppm = int(m.group(1))
abs_err = abs(err_ppm)
if prev_abs is not None and abs_err > prev_abs * 1.10:
sign_dir *= -1.0
gain = 0.85
corr = sign_dir * (err_ppm / 1_000_000.0) * gain
corr = max(min(corr, 0.20), -0.20)
period_ns = int(max(200, min(200_000, round(period_ns * (1.0 + corr)))))
prev_abs = abs_err
else:
return None
A = b"\x00" * 63 + b"\x01"
B = b"\x00" * 63 + b"\x02"
sock.sendall(b"\xAA" + A + B)
out = ""
end = time.time() + 8
while time.time() < end:
chunk = recv_chunk(sock, timeout=0.8)
if not chunk:
continue
out += chunk
m = re.search(r"([A-Za-z0-9_]+\{[^}]+\})", out)
if m:
return m.group(1)
return None
def main() -> None:
attempts = [b"CALIBRATE\n", b"CALIBRATE\r\n", b"CALIBRATE"]
for cmd in attempts:
flag = run_session(cmd)
if flag:
print(flag)
return
print("FLAG_NOT_FOUND")
if __name__ == "__main__":
main()python resonance_lock_solver.pyapoorvctf{3N7R0P1C_31D0L0N_0F_7H3_50C_4N4LY57_N0C7URN3}