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:

Steps for import:

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:

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}