742 words
4 minutes
ApoorvCTF 2026 - Resonance Lock: The Harmonic Multiplier - Hardware Writeup

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{…} (flag is fixed; any valid A/B works)

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 (all future flags garbage). Use TCP_NODELAY, send byte-by-byte. Server times the last 63 bytes (first byte is trigger only). Solver tips

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, ERR, ERR, ERR, TIMEOUT

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.

tableflip

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.

smile

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.py
apoorvctf{3N7R0P1C_31D0L0N_0F_7H3_50C_4N4LY57_N0C7URN3}
ApoorvCTF 2026 - Resonance Lock: The Harmonic Multiplier - Hardware Writeup
https://blog.rei.my.id/posts/101/apoorvctf-2026-resonance-lock-the-harmonic-multiplier-hardware-writeup/
Author
Reidho Satria
Published at
2026-03-10
License
CC BY-NC-SA 4.0