HackTheBox Space Heist Challenge
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: decode the captured signal, map the bitstream, and recover the flag.