Resonance Lock The Harmonic Multiplier
“In the buried vaults of Site-7, you found a scorched circuit board labeled HARMONIX-7. The SoC is barely alive — its supercapacitor holds just 45 seconds of charge once the clock locks. The datasheet fragment says the chip has a hardware multiplier that produces a diagnostic token when exercised, but only after the UART crystal oscillator is phase-locked to exactly 2,345,679 baud. One wrong harmonic and the token is garbage.
A charred sticky note on the board reads:
⚠ DO NOT ATTEMPT FIRMWARE DUMP — HSM tamper fuse will permanently detune oscillator. Chip will respond but all readings will be garbage. There is no recovery. You have been warned.”
Challenge Summary#
We are given a remote service that emulates a UART calibration and hardware-multiplier flow:
- Connect to
chals4.apoorvctf.xyz:1337 - Enter calibration mode
- Send a calibration trigger byte
- Send timed
0x55bursts until oscillator lock - In locked mode, send one multiplier request:
0xAA + A(64B) + B(64B) - Receive fixed flag token before the 45s supercap timeout
Expected flag format: apoorvctf{...}
Initial Triage and Reality Check#
The description mentions:
- PPM feedback lines like
ERR:+00123/ERR:-00045 - lock when
|error| <= 1000for 5 consecutive bursts - avoid any protocol mistakes or tamper fuse blows
During live interaction, the actual service behavior was slightly different:
- Sending
CALIBRATE\n(newline) caused immediate tamper - Sending exactly
CALIBRATE(no newline) worked - Calibration replies were repeated
EXEC_TIME:268380lines, thenLOCKED - After
LOCKED, one extra calibration burst caused tamper
So the exploit/solver must obey the practical wire behavior, not just the text prompt.
Protocol Notes That Matter#
-
No newline after
CALIBRATE- Use
sock.sendall(b"CALIBRATE")
- Use
-
Send trigger byte
0xCA- Single byte, no framing extras
-
Calibration burst format
- Exactly 64 bytes of
0x55 - Byte-spaced for target UART baud
- Exactly 64 bytes of
-
Stop calibration immediately when
LOCKEDappears- Do not send another
0x55burst
- Do not send another
-
Locked payload format
- Exactly
0xAA + 64-byte A + 64-byte B - Any valid operands are accepted (flag is constant)
- Exactly
-
Use
TCP_NODELAYand byte-by-byte timing- Prevent packet coalescing/Nagle jitter
Timing Math#
UART 8N1 means 10 bits transmitted per byte.
Target baud:
2,345,679 bits/stextInter-byte period:
10 / 2,345,679 s = 4.263... microsecondstextNanoseconds used by script:
BYTE_NS = round(10 * 1e9 / 2_345_679) = 4263 nstextBecause sleep() is too coarse for this scale, a busy-wait loop on time.perf_counter_ns() is used.
Exploitation Strategy#
- Create TCP socket and enable
TCP_NODELAY - Send
CALIBRATE(no newline) - Send one byte
0xCA - Repeatedly send timed 64-byte
0x55bursts - Parse responses until
LOCKED - Immediately send timed payload:
- opcode
0xAA A = 1encoded as 64-byte big-endianB = 1encoded as 64-byte big-endian
- opcode
- Read
FLAG:...
I also included optional parsing for the documented ERR:+/-PPM format, so the solver can auto-adjust spacing if that variant appears.
Solver Script#
Saved as:
hw/solve_uart_lock.py
Run:
python3 hw/solve_uart_lock.pybashRecovered Flag#
apoorvctf{3N7R0P1C_31D0L0N_0F_7H3_50C_4N4LY57_N0C7URN3}textWhy This Works#
- The service only gates flag output behind a timing-based lock state.
- Once lock is achieved, multiplier operands are not semantically important for the flag.
- Any syntactically valid 512-bit operand pair is enough to trigger fixed token disclosure.
- Strict byte-level protocol adherence avoids tripping the session tamper fuse.
Pitfalls / Dead Ends Encountered#
- Sending
CALIBRATE\ncausedERR:HSM_TAMPER_FUSE_BLOWN - Sending an extra calibration burst after
LOCKEDcaused tamper - Sending payload as one bulk write after lock often tampered; timed byte send was stable
These are exactly the kind of implementation details that matter more than prompt text in CTF infra.