HTB 2022 Cyber Apocalypse CTF - Hardware - Secret Codes - Revisited
In this blogpost we revisit the Hack The Box Cyber Apocalypse 2022 Secret Codes hardware challenge. While we did get the flag at the end of our previous blogpost, we found a better way of doing it, and present that here.
We continue with the knowledge from the previous blogpost, so we recommend reading that first: HTB 2022 Cyber Apocalypse CTF - Hardware - Secret Codes original blogpost.
A new way to look at the file
Since the data is signal data, there should be the option of opening it in SigRok’s Pulseview. To do this, we can export the analog signal in Saleae to a csv (comma-separated-value), which we can import in Pulseview.
Steps for export:
- Open the file in Saleae Logic
- Click on the File -> Export Data… menu item
- Select only the analog channel
- Optionally downsample - note the new sample rate (we used a downsample ratio of 25, to get a 100kS/s sample rate - this reduces the loading time in Pulseview later)
- Set the time range to all
- And select csv
- Click on Export. This will create a file called
analog.csv
in your working directory, which contains the exported data.
Steps for import:
- Open Pulseview
- Click the small triangle next to the open icon (second from the left in the menu bar)
- Choose “Import comma-separated values…”
- Select analog.csv
- Now change the following options to the given values:
- Column format specs:
t,a
- Samplerate: your selected sample rate (
100000
for us)
- Column format specs:
- And then press “ok”
After the import, the signal should look the same in PulseView as it did in Saleae Logic.
Analog to digital
Pulseview then has support to convert the analog signal to a digital signal using a threshold. This allows us to add decoders for the data.
We can do this bt clicking on the analog channel, and choosing to logic via threshold
for the Conversion
option.
Setting the Show traces for
option is also recommended.
Decode the digital data
The decoder that does Manchester decoding is called OOK
: “On-Off-Keying”.
This decoder can be set up to decode the Manchester encoded data properly with the following settings:
- Data:
Channel 1
- Invert data:
yes
- Decode type:
Manchester
- Preamble:
auto
- Filter length:
3
- Transition at start:
0
We got these values by experimenting with the decoder, knowing what the data should be from the previous blog post.
Bits to ASCII
Sadly, neither this decoder, nor any of the default decoders that can be stacked on top, can be used to readily print the flag. Instead of copying the data out and parsing it with Python, we wrote a stacked decoder that could do what we needed:
import sigrokdecode as srd
'''
Data input format:
Samples: [<start>, <finish>, <state>]
<start> is sample number of the start of the bit
<finish> is the sample number of the end of the bit
<state> is the single character string which is the state:
'0'
'1'
'E'
'''
class Decoder(srd.Decoder):
api_version = 3
id = 'rawconversion'
name = 'Raw Conversion'
longname = 'Raw conversion'
desc = 'Raw conversion'
license = 'MIT'
inputs = ['ook']
outputs = []
tags = ['Encoding']
annotations = (
('bit', 'Bit'),
('hexnibble', 'Hex Nibble'),
('hexbyte', 'Hex Byte'),
('asciinibble', 'Ascii Nibble'),
('asciibyte', 'Ascii Byte')
)
annotation_rows = (
('bit', 'Bit', (0,)),
('hex', 'Hex', (1,2,)),
('ascii', 'Ascii', (3,4,)),
)
options = (
{'id': 'skipbits', 'desc': 'Bits to skip', 'default': 0},
)
def __init__(self):
self.reset()
def reset(self):
self.skipbits = 0
def metadata(self, key, value):
pass
def start(self):
self.out_ann = self.register(srd.OUTPUT_ANN)
self.skipbits = self.options['skipbits']
def decode(self, ss, es, data):
data = data[self.skipbits:]
# Bits
for sample in data:
self.put(sample[0], sample[1], self.out_ann, [0, [str(sample[2])]])
# Nibbles
for i in range(0, len(data) // 4):
s, e = i * 4, i * 4 + 4
if e-1 > len(data):
break
n = ''.join([x[2] for x in data[s:e]])
if 'E' not in n:
n = int(n, 2)
self.put(data[s][0], data[e-1][1], self.out_ann, [1, [hex(n)]])
self.put(data[s][0], data[e-1][1], self.out_ann, [3, [chr(n)]])
else:
self.put(data[s][0], data[e-1][1], self.out_ann, [1, [n]])
self.put(data[s][0], data[e-1][1], self.out_ann, [3, [n]])
# Bytes
for i in range(0, len(data) // 8):
s, e = i * 8, i * 8 + 8
if e-1 > len(data):
break
n = ''.join([x[2] for x in data[s:e]])
if 'E' not in n:
n = int(n, 2)
self.put(data[s][0], data[e-1][1], self.out_ann, [2, [hex(n)]])
self.put(data[s][0], data[e-1][1], self.out_ann, [4, [chr(n)]])
else:
self.put(data[s][0], data[e-1][1], self.out_ann, [2, [n]])
self.put(data[s][0], data[e-1][1], self.out_ann, [4, [n]])
Add the above in /usr/share/libsigrokdecode/decoders/raw_conversion/pd.py
, and the following in the same directory but file __init__.py
:
'''
Raw conversion
'''
from .pd import Decoder
This decoder can be stacked on top of the ook
decoder, and can output the flag bytes for us.
For this challenge, we need to skip the first bit (which is likely meant for synchronization in the original protocol).
Actually getting the flag out
To get the flag out, we can export the annotations.
First choose only Ascii Byte
and not Ascii Nibble
for the Raw Conversion: Ascii
row.
Then right-click it, and choose Export all annotations for this row
.
And then we can parse them to get the flag - though now without mistakes:
$ cat annotation_file | grep -o ".$" | while read l; do echo -n "$l"; done; echo
HTB{c0d35_f10471n9_7h20u9h_5p4c3!@*&^76}