Rids: Winbond W25Q128FV

Platform: Hack The Box

Category/Tags: Cyber Apocalypse 2024, Hardware

Difficulty: Easy

Introduction

Upon reaching the factory door, you physically open the RFID lock and find a flash memory chip inside. The chip's package has the word W25Q128 written on it. Your task is to uncover the secret encryption keys stored within so the team can generate valid credentials to gain access to the facility.

Information Gathering

This challenge provided a Python file and a Netcat server.

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)

It looks like this code will connect to the server and execute commands using the hex list that the user will provide to output information about the device/hardware. Also, this is using the intermediary layer to communicate with the device/hardware.

PyFtdi aims at providing a user-space driver for popular FTDI devices, implemented in pure Python language. From PyFtdi Documentation.

I did some research on the W25Q128 device, and I found the datasheet containing information about this hardware.

The W25Q128FV (128M-bit) Serial Flash memory provides a storage solution for systems with limited space, pins and power. From W25Q128FV Datasheet

Winbond W25Q128FV

Going back to the Python code, the default hex list stored in a list is 0x9F, which is to read the JEDEC ID that contains the manufacturer ID of the hardware.

W25Q128FV datasheet 8.2.29

So I try to connect to the server (change the host and port) and see what the output of 0x9 is, and this is the raw output in decimal form.

python3 client.py
result in raw value

I had to modify the script to convert the raw output into hex form,

hexd = [hex(num) for num in jedec_id]
print("Hex value: ", hexd, '\n')
result with hex value

I found this table on GitHub, and the hex we got matches the JEDEC ID; indeed, it is a Winbond W25Q128,

Winbond devices JEDEC ID

Solution

So, I decided to read all of the information about this device, such as the device ID, security register, SFDP, and unique ID, but had no luck at all. Until I saw the data instruction 0x03,

W25Q128FV datasheet 8.2.6

Again, I modify the script for me to run commands easily (as args) and I added hex to string conversion to output the characters in readable form,

import socket
import json
import sys

def exchange(hex_list, value=0):

    # Configure according to your setup
    host = '<SERVER_IP>'  # The server's hostname or IP address
    port = <SERVER_PORT>       # 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
value = exchange([int(sys.argv[1], 16)], int(sys.argv[2]))
hexd = [hex(num) for num in value]
string = ''.join([chr(int(hex_str[2:], 16)) for hex_str in hexd])

print("Raw value: ", value, '\n')
print("Hex value: ", hexd, '\n')
print("String value: ", string, '\n')

I run the Python script with 0x03 to read data and 49 (it depends, but the length of the flag is 49 characters) as the length of the returned data by the server.

python3 client.py 0x03 49

Here we go, this is the flag.

result with string value

Resources

Last updated