- Remove undeployed challenges: Phantom_Byte, Cr4cK_w1f1, Lain_Br34kC0r3 V1, Lain_VS_Knights, Lets_All_Love_UART, AETHER_NET, Last_Train_451, Web3/ - Sync 24 solve/ files from main CTF-Espilon repo - Update all READMEs with real CTFd final scores at freeze - Add git-header.png banner - Rewrite README: scoreboard top 10, edition stats (1410 users, 264 boards, 1344 solves), correct freeze date March 26 2026
143 lines
3.6 KiB
Markdown
143 lines
3.6 KiB
Markdown
# Wired SPI Exfil
|
|
|
|
| Field | Value |
|
|
|-------|-------|
|
|
| Category | Hardware |
|
|
| Difficulty | Medium-Hard |
|
|
| Points | 100 |
|
|
| Author | Eun0us |
|
|
| CTF | Espilon 2026 |
|
|
|
|
---
|
|
|
|
## Description
|
|
|
|
A WIRED-MED module's SPI flash chip is accessible via a probe station.
|
|
Issue standard SPI commands to read the flash contents.
|
|
|
|
- SPI Probe: `tcp/<host>:3500`
|
|
|
|
Not all partitions are listed. Dig deeper.
|
|
|
|
Format: **ESPILON{...}**
|
|
|
|
---
|
|
|
|
## TL;DR
|
|
|
|
Use standard SPI flash opcodes (RDID, SFDP, READ) to identify the chip, discover a hidden
|
|
partition at 0x030000 via vendor-specific SFDP parameters, read the hidden partition contents,
|
|
and XOR-decrypt the data with key `WIRED_SPI` to recover the flag.
|
|
|
|
---
|
|
|
|
## Tools
|
|
|
|
| Tool | Purpose |
|
|
|------|---------|
|
|
| `nc` | Connect to the SPI probe interface |
|
|
| Python 3 | XOR decryption |
|
|
| SPI flash opcode reference | RDID=0x9F, READ=0x03, SFDP=0x5A |
|
|
|
|
---
|
|
|
|
## Solution
|
|

|
|
|
|
|
|
### Step 1 — Connect and assert chip select
|
|
|
|
```bash
|
|
nc <host> 3500
|
|
```
|
|
|
|
```text
|
|
cs 0
|
|
```
|
|
|
|

|
|
|
|
### Step 2 — Read the chip ID
|
|
|
|
Send RDID opcode 0x9F:
|
|
|
|
```text
|
|
tx 9F
|
|
```
|
|
|
|
Response: `EF 40 18` — Winbond W25Q128 (128 Mbit SPI flash).
|
|
|
|
### Step 3 — Read the SFDP header
|
|
|
|
SFDP (Serial Flash Discoverable Parameters) reveals flash capabilities.
|
|
Send SFDP opcode 0x5A with 3-byte address and 1 dummy byte:
|
|
|
|
```text
|
|
tx 5A 00 00 00 00
|
|
```
|
|
|
|
The SFDP header shows 2 parameter tables. The first is the standard JEDEC table;
|
|
the second is a vendor-specific table at offset 0x80.
|
|
|
|

|
|
|
|
### Step 4 — Read the vendor SFDP table
|
|
|
|
```text
|
|
tx 5A 00 00 80 00
|
|
```
|
|
|
|
The vendor table data includes a hidden partition entry:
|
|
|
|
```
|
|
Offset: 0x030000
|
|
Label: HIDDEN
|
|
Size: 4096 bytes
|
|
```
|
|
|
|
This partition does not appear in the normal partition table.
|
|
|
|

|
|
|
|
### Step 5 — Read the hidden partition
|
|
|
|
Use the READ opcode 0x03 with 3-byte address:
|
|
|
|
```text
|
|
tx 03 03 00 00
|
|
```
|
|
|
|
The response starts with the ASCII header `WIRED_HIDDEN_PARTITION` followed by
|
|
XOR-encrypted bytes.
|
|
|
|
### Step 6 — Decrypt the flag
|
|
|
|
The data is XOR'd with the repeating key `WIRED_SPI`:
|
|
|
|
```python
|
|
# data_hex = hex bytes from the tx read response after the header
|
|
key = b"WIRED_SPI"
|
|
encrypted = bytes.fromhex("...") # from response
|
|
flag = bytes(b ^ key[i % len(key)] for i, b in enumerate(encrypted))
|
|
print(flag.rstrip(b'\x00').decode())
|
|
```
|
|
|
|

|
|
|
|
### Key concepts
|
|
|
|
- **SPI flash commands**: Standard opcodes work across most vendors:
|
|
`0x9F` = RDID, `0x03` = READ, `0x5A` = SFDP
|
|
- **SFDP**: Serial Flash Discoverable Parameters standardises capability discovery;
|
|
vendor-specific extensions can hide extra metadata, including non-standard partition info
|
|
- **Hidden partitions**: Not all sectors appear in standard partition tables — manual
|
|
probing or SFDP analysis is required to find them
|
|
- **Data at rest XOR**: Simple XOR protection on stored secrets is common in
|
|
embedded firmware; knowing the plaintext structure (header magic bytes) allows key recovery
|
|
|
|
---
|
|
|
|
## Flag
|
|
|
|
`ESPILON{sp1_fl4sh_3xf1ltr4t3d}`
|