2024 Cyber Apocalypse: Hardware Rids

Challenge Information

AttributeDetails
Event2024 Cyber Apocalypse
CategoryHardware
ChallengeHardware Rids
DifficultyEasy

Summary

Hardware Rids challenges participants to read data from a simulated W25Q128 flash memory chip via SPI interface. The challenge provides a Python client that communicates with a remote endpoint mimicking an FTDI device. By understanding the SPI command set and using the correct read instruction (0x03), the flag can be extracted from the flash memory.


Analysis

Key components:

  1. Device: W25Q128 flash memory chip (128 Mbit)
  2. Interface: SPI (Serial Peripheral Interface)
  3. Connection: JSON-based TCP protocol to FTDI device
  4. Tools: pyftdi library for device communication
  5. Commands: Standard W25Q128 SPI instruction set

The challenge hints at the device through:

  • References to ‘ftdi’ and ‘2232h’ in the source code
  • Usage of ‘0x9F’ instruction for JEDEC ID reading
  • Need to identify the read instruction (0x03)

Solution

Step 1: Identify the Device

From the source code analyzing instruction 0x9F:

jedec_id = exchange([0x9F], 1)

This is the “Read JEDEC ID” instruction, revealing the device as W25Q128.

Step 2: Consult Device Datasheet

W25Q128 datasheet specifies:

  • Read Data (0x03): Followed by 24-bit address
  • Page Program (0x02): Write up to 256 bytes
  • Sector Erase (0x20): 4KB blocks
  • Block Erase (0xD8): 64KB blocks
  • Status Register (0x05): Device status

Step 3: Construct Read Command

To read from address 0x000000:

# Instruction: 0x03
# Address: 0x000000 (24-bit, big-endian)
# Length: Data to read
read_command = [0x03, 0x00, 0x00, 0x00]

Step 4: Send Command and Receive Data

import socket
import json
def exchange(hex_list, readlen=0):
host = '94.237.55.246'
port = 31251
cs = 0
usb_device_url = 'ftdi://ftdi:2232h/0'
command_data = {
"tool": "pyftdi",
"cs_pin": cs,
"url": usb_device_url,
"data_out": [hex(x) for x in hex_list],
"readlen": readlen
}
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
s.sendall(json.dumps(command_data).encode('utf-8'))
data = b''
while True:
data += s.recv(1024)
if data.endswith(b']'):
break
response = json.loads(data.decode('utf-8'))
return response

Step 5: Read Flag Data

# Read 50 bytes from address 0x000000
flag_data = exchange([0x03, 0x00, 0x00, 0x00], 50)
print(flag_data)
# Output: [72, 84, 66, 123, 109, 51, 109, 48, 50, 49, 51, 53, ...]

Step 6: Convert to ASCII

flag_bytes = bytes(flag_data)
flag = flag_bytes.decode('utf-8')
print(flag)
# HTB{m3m02135_57023_53c2375_f02_3v32y0n3_70_533!@}

W25Q128 SPI Commands Reference

Basic Commands

CommandOpcodeDescription
Read Data0x03Read memory from address
Fast Read0x0BHigher-speed read
Page Prog0x02Write up to 256 bytes
Sector Ers0x20Erase 4KB sector
Block Ers0x52Erase 32KB block
Block Ers0xD8Erase 64KB block
Chip Erase0xC7Erase entire chip

Status Commands

CommandOpcodeDescription
Read Status Reg0x05Read device status
Write Status Reg0x01Modify status
Write Enable0x06Enable write ops
Write Disable0x04Disable write ops

ID Commands

CommandOpcodeDescription
Read JEDEC ID0x9FManufacturer & capacity
Read Unique ID0x4BDevice unique identifier

Advanced Commands

CommandOpcodeDescription
Deep Power-down0xB9Minimize power usage
Release Power-down0xABWake from sleep
Read Security Reg0x48Security data
Program Security Reg0x42Write security data

Protocol Details

Command Data Structure

{
"tool": "pyftdi",
"cs_pin": 0,
"url": "ftdi://ftdi:2232h/0",
"data_out": ["0x3", "0x0", "0x0", "0x0"],
"readlen": 50
}

Response Format

Array of decimal byte values:

[72, 84, 66, 123, ...]

Key Takeaways

  • SPI is a common interface for flash memory devices
  • Device datasheets are essential for understanding command sets
  • FTDI chips bridge USB to SPI/JTAG/UART interfaces
  • Standard instruction codes (0x9F for JEDEC ID) identify devices
  • Reading from address 0x000000 typically contains stored data
  • JSON-based protocols simplify hardware simulation in CTF challenges