forbytten blogs

Rids Writeup - Cyber Apocalypse 2024

Last update:

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.

Rids challenge description

2 Key Techniques

The key techniques employed in this writeup are:

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:

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.

W25Q128BV datasheet confirms the “Read JEDEC ID” instruction is 0x9F, which corresponds to 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.

W25Q128BV datasheet documents the read data instruction

7 Adapting the code to read data

client.py was modified:

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

Submission of the flag marked the challenge as pwned