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.

Challenge Image

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

JPEG Structure

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

file

Trying out start_byte = 20736 and end_byte = 20800 and running our script then checking out the text files

strings

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

Fixed Image

Here's a closer look on the flag

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