HTB Cyber Apocalypse 2023 - (Hardware) HM74
‘HM74’ was one of the challenges in the ‘Hardware’ category at HTB’s Cyber Apocalypse 2023.
Its difficulty was ‘Medium’ and it involved error correcting codes (Hamming Codes).
My solution is almost surely not the ‘most correct’ … but it worked.
Challenge description
The description of the challenge mentioned something about noisy data transmissions.
We got access to a docker container and an archive.
The archive contains a single file that we got was “encoder.sv”, a Verilog module.
Noisy data transmission
Let’s take a look at the docker container first.
If I connect to the container, it sends me some binary data in a loop.
The data doesn’t decode to anything obvious for now.
The encoder, parity bits
Now, let’s look at the Verilog module.
This encoder module takes a 4-bit signal and turns it into a 7-bit signal that contains the original 4 data bits and 3 parity bits.
The parity bits are computed by XORing 3 of the data bits.
This scheme is also known as Hamming(7,4), which you can read more about here.
The server is sending messages that were encoded that way and the messages get corrupted somehow before reaching us.
I knew about Hamming codes, but I had no idea how to actually implement the error correction.
On top of that, I thought that I can solve it without that because the server keeps transmitting the message.
I was right … but the solution is not the most pretty one.
Decoding using Python
In order to test my solution offline, I took the received data and put it in a folder (after stripping anything that wasn’t the message from it).
Analyzing that data, I noticed that every message had 136*7 bits. This meant that the message is 136*4 bits long.
That would be 68 bytes (or 136 nibbles).
For solving this, I implemented a python function that took 7bits (what I called an N-gram) from the received string, checked that the parity bits are correct and then returned the 4 data bits.
Reconstructing the message
I knew that the message is 136 nibbles long, so I created an array and initialized it with 136 instances of ‘????’
Then, I went through each n-gram of a message and if it was valid, I added it to the result array.
In order to print the message stored in that array, I joined everything in that array, looked at every 8-gram and did the following:
- if there was any ‘?’ character in the 8-gram, I decoded the character as ‘?’
- if there was no ‘?’ character in the 8-gram, I parsed the 8-gram as binary (the int() function allows you to specify the base) and decoded that
The full code looks like this
decode_v1.py (click to expand)
After running it, I got the following message: HTB{hmmßs1h_s0m3_ana1ysÑ5_yu_c6l_6(7ract_7h;_h4mmYn9_7Ý4o3nc_Ve49}
It’s clear that I’m getting close, but this is nowhere near as close as it needs to be …
For troubleshooting, I printed the flag after every iteration (an iteration = another line/message from the server).
When running it like that, I could see the issue: sometimes correct characters are overwritten with wrong ones.
- Iteration 15-16 I can see ‘w1th’ becoming ‘y1th’, which seems like the wrong choice
- Iteration 17-18 I can see ‘HTB’ becoming ‘HTA’, which is surely the wrong choice
I don’t know why this happens. My best two guesses are:
- the message might get corrupted in just the right way so that the parity bits are also flipped and the message seems correct
- I don’t know what I’m doing (very, very possible situation)
Fixing the issue
A quick & dirty solution that I came up with is not adding any n-gram that was valid to the answer.
Instead, I made a list of all valid values for every nibble in the answer.
Then, at the end, I took the value that had the most occurrences for each nibble.
Another changes that I made:
- instead of relying on a file, I used pwntools to connect to the server and parse messages live
- after the first 30 iterations, the program stopped if nothing changed in the last 5 iterations
The full code looks like this
decode.py (click to expand)
After running it, I got a flag that looked valid.
And after submitting it, that was indeed the correct flag:
HTB{hmm_w1th_s0m3_ana1ys15_y0u_c4n_3x7ract_7h3_h4mmin9_7_4_3nc_fl49}
Can’t wait to find out what was the proper way of solving this one…
More hardware challenges
These are the other hardware challenges from this CTF
- Hardware 1/5 - Very Easy - Critical Flight
- Hardware 2/5 - Very Easy - Timed Transmission
- Hardware 3/5 - Easy - Debug
- Hardware 4/5 - Easy - Secret Code
- Hardware 5/5 - Medium - HM74 (you are here)