Quick we need to get access to the bunker and we are running out of time! The door is using an advanced steam-powered door locking mechanism which we cannot breach. One of our scientists managed to make a tool that measures the mechanical stress of the pipes moving steam during the verification of the password and created a power consumption model but it looks like just random signals. Can you find anything useful in the data?

Category: hardware

Solver: rgw

Flag: HTB{c4n7_h1d3_f20m_71m3}

Writeup

In this challenge, we get a remote container as well as some python code that can be used to talk to it. When running the script, the server asks the client for a password. After guessing a password, the server sends some binary data that is interpreted as a 1000-element NumPy array of measurements that looks like this:

[-0.20979757 -0.16274394 -0.30431337 -0.48358536 -0.23253828 -0.199225
 -0.35388743 -0.18253244 -0.120403   -0.30962142 -0.48970272 -0.43847888
 -0.46813139 -0.15836405 -0.153939   -0.39243379 -0.15445498 -0.23950641
 -0.43257999 -0.58911743 -0.08425488 -0.1941009  -0.05628938 -0.16563285
 -0.28682638 -0.54433299 -0.26713258 -0.29094738 -0.2016906  -0.00103324
 -0.28420763 -0.48121093 -0.48555699 -0.43820257 -0.48621702 -0.05918698
 -0.16951941 -0.5318846  -0.59982784 -0.07673312 -0.15018049 -0.24099132
 -0.13796123 -0.2872664  -0.1526497  -0.31796715 -0.19353571 -0.35775008
 -0.31040582 -0.28919463 -0.4066198  -0.50202594 -0.19568491 -0.19411991
 -0.30400802 -0.48653632 -0.1630739  -0.32888972 -0.1641641  -0.30862783
 -0.19511367 -0.20058613 -0.49763135 -0.55450173 -0.48074822 -0.49928282
 -0.52733085 -0.40796361 -0.16375752 -0.27194523 -0.17078945 -0.24661013
 -0.19072903 -0.29251013 -0.12717011 -0.2739726  -0.26580045 -0.39421924
 ... 
 -0.12730652 -0.33734566 -0.54149761 -0.14008503 -0.21165112 -0.55421833
 -0.2218916  -0.36660566 -0.55732013 -0.20110808 -0.34029531 -0.32260168
 -0.12310121 -0.37722785 -0.49553628 -0.15342243 -0.26856934 -0.48974869
 -0.25899613 -0.33214945 -0.46055076 -0.1032769 ]

We suspect that when guessing a password that is partially correct, the measurements might differ from the measurements taken when the password contains incorrect characters.

Therefore, we try to use statistics on the measurements: We average the measurement vectors over all letters and subtract the individual vectors from the average. At the end, we compute the absolute value of these differences. While guessing single-letter passwords, we notice that while most chars have an absolute deviation of about 30, one character, H, has a deviation of 162:

a 39.949657649801225
b 40.714569725158626
c 39.61497940004325
d 39.474006697622215
e 40.29897016028263
f 41.89109899208532
g 40.241643524264596
h 39.309716549203934
i 39.826753110922525
j 39.777182594552954
k 39.984043260288885
l 40.47633608780173
m 40.24759348709958
n 38.12986267742626
o 39.86780215040693
p 38.877102226438495
q 40.05740659718354
r 39.21962494136937
s 38.49766987388506
t 40.75136015479862
u 37.47011649858465
v 39.562042262040976
w 39.760485470084944
x 41.521272764450664
y 40.38963761988974
z 39.494044855249996
A 39.76983364485786
B 38.95036756181348
C 39.870353888659444
D 40.0154409203386
E 38.282992764086416
F 40.88182735993278
G 40.06438310252747
H 162.12007396563763
I 38.40294490783265
J 38.353039747285145
K 39.8239059214363
L 42.0861582308512
M 40.778670231970096
N 41.28603219141854
O 40.181731007751935
P 39.84819858493701
Q 39.96896304132981
R 41.978422895479774
S 39.38199930291793
T 39.065399506199704
U 40.71543270098678
V 39.419632346973536
W 39.28195141479476
X 39.60843822728355
Y 40.985558841970594
Z 39.782674332876205
0 42.02124852746148
1 40.50830029920388
2 40.17730906035423
3 39.95775017387069
4 40.689673535418514
5 41.29425831812978
6 40.643575457603916
7 39.762556561411344
8 41.082097079921425
9 40.9476795039626
! 40.75906409666984
" 40.962444940909904
# 40.085388705516564
$ 41.12138177846864
% 38.85483813133038
& 40.05988767387291
' 38.90183514120064
( 38.476441274792656
) 39.070732636868215
* 38.67164570943267
+ 40.134294920793835
, 40.74501273938988
- 40.89069266048346
. 39.52948594200726
/ 40.037632622120825
: 40.362748698461616
; 42.56763182240383
< 41.25076473654308
= 42.18869178715396
> 40.731563821069386
? 42.52021658053573
@ 42.31315719820586
[ 37.59811557020994
\ 41.16434211160815
] 41.07575097475494
^ 41.06401710669304
_ 39.14158587863376
` 40.95357310946744
{ 38.97310258482758
| 39.63342981398347
} 40.5395185352735
~ 40.75728522823533

Therefore, by looking at the character with the maximum absolute difference, we can guess the correct password one letter at a time.

Our final script looks like this:

#!/usr/bin/env python
import time
import socket
import base64
import numpy as np
from string import ascii_lowercase, ascii_uppercase, digits, punctuation

HOST = '167.172.51.181'
PORT = 32164

def b64_decode_trace(leakage):
    byte_data = base64.b64decode(leakage) # decode base64
    return np.frombuffer(byte_data) # convert binary data into a NumPy array

def connect_to_socket(option, data):
    data = data.encode()
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        resp_1 = s.recv(1024)
        s.sendall(option)
        resp_2 = s.recv(1024)
        s.sendall(data)
        resp_data = b''
        tmp = s.recv(8096)
        while tmp != b'':
            resp_data += tmp
            tmp = s.recv(8096)
        s.close()
        return resp_data

def get_power_trace(guess):
    leakage = connect_to_socket(b'1', guess)
    time.sleep(0.1)

    return b64_decode_trace(leakage)

def get_next_char(initial_password):
    charset = ascii_lowercase + ascii_uppercase + digits + punctuation
    power_traces = {}
    for letter in charset:
        print(letter, end='', flush=True)
        power_traces[letter] = get_power_trace(initial_password+letter)
    print()
    avg = np.sum(list(power_traces.values()), 0)/len(charset)
    absolute_differences = {letter: np.sum(np.absolute(avg-power_traces[letter])) for letter in charset}
    return max(absolute_differences, key=absolute_differences.get)

if __name__ == '__main__':
    password = ''
    while True:
        char = get_next_char(password)
        password += char
        print(password)
        if char == '}':
            break

After taking some time to run, the final output looks like this:

output.png

We get the flag HTB{c4n7_h1d3_f20m_71m3}.