Post

Say Chesse! - HTB Business CTF 2024 The Vault Of Hope

Url : HackTheBox Business CTF 2024
Challenge : Say Cheese!
Date : flag le 22/05/2024 à 2h00

Un script python ‘client.py’ nous est fournit :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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)

Cela semble à première vu être un programme coté client qui vient interagir et requêter une puce de mémoire flash, l’énoncé nous indique que c’est une flash SPI et plus précisément la W25Q128FV

dont voici la datasheet : https://www.pjrc.com/teensy/W25Q128FV.pdf

Dans le script python nous remarquons la présence de cette variable :

1
2
3
# Example command
jedec_id = exchange([0x9F], 3)
print(jedec_id)

JEDEC (Joint Electron Device Engineering Council) est un organisme de normalisation qui élabore des normes pour l’industrie microélectronique. JEDEC ID  est un identifiant unique attribué à chaque puce par le fabricant selon les normes JEDEC.

Regardons plus précisément ce que fait la commande : La commande appelle la fonction exchange avec comme paramètres 0x9F et 3

  • 0x9F : Valeur hexadécimale représentant la commande pour demander le JEDEC ID du périphérique de mémoire flash. (voir screen ci dessous)
  • 3 : paramètre qui indique le nombre d’octets à lire.

Le résultat de cette commande est donc printé grace au print(jedec_id)

donc si nous exécutons notre script client.py pour requêter le JEDEC ID :

1
2
3
┌──(plm㉿oppida)-[~/home/CTF/say_cheese]
└─$ python client.py 
[239, 64, 24]

La liste de tout les autres paramètre peut se trouver en ligne.

mais il y en a un qui nous intéresse tout particulièrement :

l’instruction read data permet de lire de la data séquentiellement depuis la mémoire par exemple en envoyant :

1
2
jedec_id = exchange([0x03, 0x00, 0x00, 0x00], 128 )
print(jedec_id)

nous lisons les 128 premiers bytes de la mémoire à partir de 0x000000 résultat :

1
2
3
┌──(plm㉿oppida)-[~/home/CTF/say_cheese]
└─$ python client.py
[39, 5, 25, 86, 86, 44, 137, 202, 102, 68, 161, 42, 0, 169, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 154, 11, 173, 5, 5, 5, 0, 106, 122, 95, 102, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 5, 25, 86, 111, 89, 72, 244, 94, 204, 163, 59, 0, 29, 26, 157, 128, 1, 0, 0, 128, 66, 24, 112, 216, 252, 221, 250, 5, 5, 2, 3, 76, 105, 110, 117, 120, 45, 51, 46, 49, 48, 46, 49, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Lorsque nous passons ces bytes dans cyberchef. nous devinons qu’il s’agit d’une image LZMA compressé

j’ai passé beaucoup de temps à essayer de lire des adresses mémoire afin de voir si le flag n’était stocké en claire quelque part, mais au bout de plusieurs heures, j’ai compris qu’il fallait extract l’entièreté du firmware. pour ceci :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import socket
import json
from pwn import * 

def exchange(hex_list, value=0):
    # Configure according to your setup
    host = '83.136.248.205'  # The server's hostname or IP address
    port = 37104        # 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

# Dump du firmware
f = open("dump.bin", "wb")
for i in range(256):
	print(i)
	jedec = exchange([0x03,i,0,0], 65536)
	for i in jedec:
		f.write(p8(i))
f.close()
  • p8(i) : Convertit l’entier i en un octet unique.
  • f.write(p8(i)) : Écrit cet octet dans le fichier binaire.

Une fois la fin du script, nous avons un firmware correcte

1
2
3
┌──(plm㉿oppida)-[~/home/CTF/say_cheese]
└─$ file dump.bin 
dump.bin: u-boot legacy uImage, jz_fw, Linux/MIPS, Firmware Image (Not compressed), 11075584 bytes, Wed May 15 11:48:58 2024, Load Address: 00000000, Entry Point: 00000000, Header CRC: 0X562C89CA, Data CRC: 0XE89A0BAD

Exécution de la commande binwalk sur le système afin d’extraire la data :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(plm㉿oppida)-[~/home/CTF/say_cheese]
└─$ binwalk -e dump.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             uImage header, header size: 64 bytes, header CRC: 0x562C89CA, created: 2024-05-15 11:48:58, image size: 11075584 bytes, Data Address: 0x0, Entry Point: 0x0, data CRC: 0xE89A0BAD, OS: Linux, CPU: MIPS, image type: Firmware Image, compression type: none, image name: "jz_fw"
64            0x40            uImage header, header size: 64 bytes, header CRC: 0x6F5948F4, created: 2020-05-26 05:03:55, image size: 1907357 bytes, Data Address: 0x80010000, Entry Point: 0x80421870, data CRC: 0xD8FCDDFA, OS: Linux, CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name: "Linux-3.10.14"
128           0x80            LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: -1 bytes
2097216       0x200040        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 3289996 bytes, 414 inodes, blocksize: 131072 bytes, created: 2024-05-15 11:42:45
5570624       0x550040        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 593566 bytes, 13 inodes, blocksize: 131072 bytes, created: 2020-08-20 09:14:54
6225984       0x5F0040        JFFS2 filesystem, little endian
6230340       0x5F1144        Zlib compressed data, compressed
6258764       0x5F804C        JFFS2 filesystem, little endian
6625136       0x651770        JFFS2 filesystem, little endian
6626980       0x651EA4        JFFS2 filesystem, little endian
[...]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌──(plm㉿oppida)-[~/home/CTF/say_cheese/_dump.bin.extracted/]
└─$ ls -la
total 3277396
drwxr-xr-x  4 plm plm    24576 May 22 11:00 .
drwxr-xr-x  3 plm plm     4096 May 22 11:00 ..
-rw-r--r--  1 plm plm  3289996 May 22 11:00 200040.squashfs
-rw-r--r--  1 plm plm   593566 May 22 11:00 550040.squashfs
-rw-r--r--  1 plm plm 10551232 May 22 11:00 5F0040.jffs2
-rw-r--r--  1 plm plm    47012 May 22 11:00 5F1144
-rw-r--r--  1 plm plm 10546876 May 22 11:00 5F1144.zlib
-rw-r--r--  1 plm plm 10518452 May 22 11:00 5F804C.jffs2
-rw-r--r--  1 plm plm 10152080 May 22 11:00 651770.jffs2
-rw-r--r--  1 plm plm     2762 May 22 11:00 A86BF8
-rw-r--r--  1 plm plm  5739528 May 22 11:00 A86BF8.zlib
-rw-r--r--  1 plm plm  5737608 May 22 11:00 A87378.jffs2
-rw-r--r--  1 plm plm      435 May 22 11:00 A87EDC
-rw-r--r--  1 plm plm  5734692 May 22 11:00 A87EDC.zlib
-rw-r--r--  1 plm plm  5734324 May 22 11:00 A8804C.jffs2
drwxrwxrwx 25 plm plm     4096 Apr 20  2020 squashfs-root
drwxrwxrwx  2 plm plm     4096 Aug 20  2020 squashfs-root-0

Affichage du flag dans le etc/init.d/rcS :

1
2
3
┌──(plm㉿oppida)-[~/home/CTF/say_cheese/_dump.bin.extracted/squashfs-root]
└─$ cat etc/init.d/rcS | grep HTB
# HTB{SPI_t0_b4ckd00r1ng_4_cam3r4_ismart12}

Il y a également une ligne au dessus du flag une vidéo Youtube très interessante sur le backdoring de caméra IP :

1
2
3
4
┌──(plm㉿oppida)-[~/home/CTF/say_cheese/_dump.bin.extracted/squashfs-root]
└─$ cat etc/init.d/rcS | grep HTB -B 1
# https://www.youtube.com/watch?v=hV8W4o-Mu2o
# HTB{SPI_t0_b4ckd00r1ng_4_cam3r4_ismart12}
This post is licensed under CC BY 4.0 by the author.