At the first glance it looked like an easy challenge, we had the following image and the description stated that only one bit was flipped.
So all we have to do to get the flag was to flip that bit again. Easy, huh? Well, no.
Downloading the image we find out that it's size is 146 KB
which is 1,168,000 bits
which is quite a lot to bruteforce through.
We spent sometime searching for similar old challenges but ended up only finding bruteforcing scripts and nothing more so we decided to write a script looping on each bit, flipping it, saving the image, running tesseract
-an OCR tool- on it then delete the image.
import codecs
import os
start_byte = 0
stop_byte = 1168000
with codecs.open('../bs.jpg', 'rb') as input_file:
data = input_file.read()
for byte in range(start_byte, stop_byte):
for bit in range(8):
modified = chr(ord(data[byte]) ^ (1 << bit))
output_data = data[:byte] + modified + data[byte + 1:]
with codecs.open(str(byte) + '_' + str(bit) + '.jpg', 'wb') as output_file:
output_file.write(output_data)
os.system('tesseract ' + str(byte) + '_' + str(bit) + '.jpg ' + str(byte) + '_' + str(bit) + '.txt '+'-l eng')
os.system('rm ' + str(byte) + '_' + str(bit) + '.jpg')
We ran the script and we'd check the text files generated by tesseract
every once in a while to check if we got anything. But after a while we stopped the script as it was an overhead for our laptops and surely it wasn't the right way to go.
After solving a couple of challenges, we decided to give it another look. There was a new hint for the challenge which stated that if you know the structure of the JPEG, you'll narrow the bruteforcing range
The script wasn't a waste after all but we have to find the right offsets for the start_byte
and stop_byte
so we started googling the structure of JPEG right until we landed on this presentation and on slide 18 we got what we were looking for
Rereading the hint, we suspected that we're aiming either for Huffman tables or Quantization Tables as they're both essencial for the image data compression.
Firing up a hex editor and started looking for our candidate offsets until we found this one
Trying out start_byte = 20736
and end_byte = 20800
and running our script then checking out the text files
We got some dummy text so we decided to have a look for ourself, maybe tesseract
isn't seeing what we can. We ran the script again with start_byte = 20760
and end_byte = 20773
without tesseract
and keeping the images this time.
Browsing throw these images we find out that we successfully repaired the image and got the flag by flipping the 7th bit in the 20771 byte
Here's a closer look on the flag
We submitted the flag literally with 1 minute left on the CTF to steal the first place back and if you're wondering how one bit can change your life, here's how