Rids Writeup - Cyber Apocalypse 2024
→ 1 Introduction
This writeup covers the Rids Hardware challenge from the Hack The Box Cyber Apocalypse 2024 CTF, which was rated as having an ‘easy’ difficulty. The challenge involved reading data from a flash memory chip via a simulated interface.
The description of the challenge is shown below.
→ 2 Key Techniques
The key techniques employed in this writeup are:
- Manual Python source code review
- Finding and interpreting the datasheet for a flash memory chip
→ 3 Artifacts Summary
The downloaded artifact had the following hash:
$ shasum -a256 hardware_rids.zip
e97aae256cef7f9296ff3bb35a21765aeb97e2195e46719ddd1e6e083cf367ec hardware_rids.zip
The zip file contained a single Python source code file:
$ unzip hardware_rids.zip
Archive: hardware_rids.zip
creating: hardware_rids/
inflating: hardware_rids/client.py
$ shasum -a256 hardware_rids/*
e4a99c29798bb8e6508df49ded6a76b59e9afa10f5600988ad404662ced7ec09 hardware_rids/client.py
→ 4 Static analysis
client.py
contained code for interfacing with a TCP
service that is a facade to a flash memory chip. Some notable
aspects:
-
Line 11 contains clues that the hardware device is characterized by
ftdi
and2232h
-
Line 15 indicates that
pyftdi
is used to interface with the device. -
Line 41 contains an example of sending a byte
0x9F
and receiving ajedec_id
in return.
import socket
import json
def exchange(hex_list, value=0):
# Configure according to your setup
host = '127.0.0.1' # The server's hostname or IP address
port = 1337 # The port used by the server
cs=0 # /CS on A*BUS3 (range: A*BUS3 to A*BUS7)
usb_device_url = 'ftdi://ftdi:2232h/1'
# Convert hex list to strings and prepare the command data
command_data = {
"tool": "pyftdi",
"cs_pin": cs,
"url": usb_device_url,
"data_out": [hex(x) for x in hex_list], # Convert hex numbers to hex strings
"readlen": value
}
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
# Serialize data to JSON and send
s.sendall(json.dumps(command_data).encode('utf-8'))
# Receive and process response
data = b''
while True:
data += s.recv(1024)
if data.endswith(b']'):
break
response = json.loads(data.decode('utf-8'))
#print(f"Received: {response}")
return response
# Example command
jedec_id = exchange([0x9F], 3)
print(jedec_id)
→ 5 Obtaining the device datasheet
The challenge description indicated the device is a W25Q128 flash
chip. Searching online, the datasheet for this was found.
The datasheet documents the instruction for “Read JEDEC ID”, which
matches 0x9F
in the sample code.
→ 6 Determining the instruction to read data
The datasheet also documents the instruction to read data, namely
0x03
followed by a 24 bit starting address. It also
indicates the entire memory can be read with a single instruction as
long as the clock continues.
→ 7 Adapting the code to read data
client.py
was modified:
- Lines 8-9 set the remote endpoint.
-
Line 46 sends the bytes
0x03 0x00 0x00 0x00
to the endpoint to read data starting from an address of0x000000
and to read 100 bytes. - Line 47 prints the memory as is, which is an array of integers
- Line 48 converts each integer to a character
- Line 49 joins each character with the empty string
import json
def exchange(hex_list, value=0):
# Configure according to your setup
# host = '127.0.0.1' # The server's hostname or IP address
# port = 1337 # The port used by the server
host='94.237.51.96'
port=50933
cs=0 # /CS on A*BUS3 (range: A*BUS3 to A*BUS7)
usb_device_url = 'ftdi://ftdi:2232h/1'
# Convert hex list to strings and prepare the command data
command_data = {
"tool": "pyftdi",
"cs_pin": cs,
"url": usb_device_url,
"data_out": [hex(x) for x in hex_list], # Convert hex numbers to hex strings
"readlen": value
}
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
# Serialize data to JSON and send
s.sendall(json.dumps(command_data).encode('utf-8'))
# Receive and process response
data = b''
while True:
data += s.recv(1024)
if data.endswith(b']'):
break
response = json.loads(data.decode('utf-8'))
#print(f"Received: {response}")
return response
# Example command
#jedec_id = exchange([0x9F], 3)
#print(jedec_id)
# Read data (03h followed by 24bit address)
memory = exchange([0x03, 0x00, 0x00, 0x00], 100) # read 100 bytes starting from address 0x000000
print(memory)
print([chr(h) for h in memory])
print(''.join([chr(h) for h in memory]))
→ 8 Obtaining the flag
client.py
was run, resulting in the flag being
printed:
$ python3 client.py
[72, 84, 66, 123, 109, 51, 109, 48, 50, 49, 51, 53, 95, 53, 55, 48, 50, 51, 95, 53, 51, 99, 50, 51, 55, 53, 95, 102,
48, 50, 95, 51, 118, 51, 50, 121, 48, 110, 51, 95, 55, 48, 95, 53, 51, 51, 33, 64, 125, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 2
55, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
['H', 'T', 'B', '{', 'm', '3', 'm', '0', '2', '1', '3', '5', '_', '5', '7', '0', '2', '3', '_', '5', '3', 'c', '2', '
3', '7', '5', '_', 'f', '0', '2', '_', '3', 'v', '3', '2', 'y', '0', 'n', '3', '_', '7', '0', '_', '5', '3', '3', '!'
, '@', '}', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ',
'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ
', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ', 'ÿ']
HTB{m3m02135_57023_53c2375_f02_3v32y0n3_70_533!@}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
→ 9 Conclusion
The flag was submitted and the challenge was marked as pwned