After fixing the code to be more readable I got this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
from PIL import Image for i in range(1,101): import builtins, random img = Image.new("RGB", (len(flag), 1), "white") pixels = img.load() counter = 0 for character in flag: character = ord(character) rand0 = random.randint(1,256) rand1 = random.randint(1,256) rand3 = (rand0/256) rand4 = (rand1/256) rand5 = character*rand3 rand6 = rand5*rand4 pixels[counter,0] = (rand0, rand1, round(rand6*10)) counter += 1 img.save("out"+str(i)+".png")
We can see rand0 and rand1 are being random generated, but they are putted directly into the image in the pixels red and blue!, by having these two we can calculate round(rand6*10) easily by doing some arithmetic operations, with this we can do a script that brute-forces the flag byte by byte, by comparing the blue pixels from the image with the ones we calculated:
The pseudo code:
1 2 3 4 5 6 7 8 9 10 11 12
flag = '' for character in all_printable_characters: for pixel in image: rand0 = pixel.red rand1 = pixel.blue rand3 = rand0/256 rand4 = rand1/256 rand5 = ord(character)*rand3 rand6 = rand5*rand4 if rand6 == pixel.blue: flag += character break
But we ran into a problem, as the description of the challenge says sometimes the encryption doesn’t work and one of the reasons is the calculations made by the rand variables can be above 255 (Kind of depends of the random value or the character), we know that color pixels from the images can only handle colors in the range of 0-255 (in this case python will set the pixel as 255), a byte!
This why the encryption does it multiple times (100) with different random values, another problem we have is once in a while multiple characters matches the same blue pixel, in these both cases we can’t know for sure if it is the character we want.
So my solution to this was to ignore all characters that were above 255 and those that had multiple solutions to that byte position of the flag, since we have more than enough images (100) the characters we failed to find we can recheck them in the rest of the pictures.
The final python script I used was (note that using python3 in this challenge was absolutely necessary):
import os, sys from PIL import Image import string # flag size is 38 flags = [] for i in range(1,101): flag = [] im = Image.open("out%d.png"%i) width = im.size[0] #define W and H height = im.size[1] pix = im.load() for x in range(0,width): stop = True found = 0 for character in string.printable: r,g,b = pix[x,0] rand0 = r rand1 = g rand3 = rand0/256 rand4 = rand1/256 rand5 = ord(character)*rand3 rand6 = rand5*rand4 if round(rand6*10) >= 255: continue else: if round(rand6*10) == b: found += 1 stop = False if found == 1: flag.append(character) else: flag[x] = 'x' if stop: flag.append('x') flags.append(''.join(flag))
final_flag = list('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') for flag in flags: for i,c in enumerate(flag): if c != 'x': final_flag[i] = c print (''.join(final_flag))