[Reverse] TPCTF2017 - Bad Encryption


Bad Encryption
100

I was making an encryption program, but it is far from perfect. Instead of make the encryption work, I decided to just encrypt everything 100 times.

Author: Kevin Higgs

We have a python program, which is little bit obfuscated with this horrible named variables:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
for i in range(1,101):
tel1l1l1l1l1l1l1lt = "REDACTED"
import builtins, random
l1l1l1l1l1l1l1l = getattr(builtins, "__import__")
l1l1l1l1l1l1l1l = l1l1l1l1l1l1l1l("PIL.Image")
l1l1l1l1l1l1l1ll1l1l1l1l1l1l1l = l1l1l1l1l1l1l1l.Image
l1l1l1l1l1l1l1ll1l1l1l1l1l1l1ll1l1l1l1l1l1l1l = l1l1l1l1l1l1l1ll1l1l1l1l1l1l1l.new("RGB", (len(tel1l1l1l1l1l1l1lt), 1), "white")
l1l1l1l1l1l1l1ll1l1l1l1l1l1l1ll1l1l1l1l1l1l1ll1l1l1l1l1l1l1l = l1l1l1l1l1l1l1ll1l1l1l1l1l1l1ll1l1l1l1l1l1l1l.load()
l1l1l1l1l1l1l1ll1l1l1l111l1l11 = 0
for l1l1l1l1l1l1l1ll1l1l1l1l1l1l11 in tel1l1l1l1l1l1l1lt:
l1l1l1l1l1l1l1ll1l1l1l1l1l1l11 = ord(l1l1l1l1l1l1l1ll1l1l1l1l1l1l11)
l1l1l1l1l1l1l1ll1l1l1l1lll1l111 = random.randint(1,256)
l1l1l1l1l1l1l1ll1l1l1l1lll1l112 = random.randint(1,256)
l1l1l1l1l1l1l1ll1l1l1l1lll1l113 = random.randint(1,256)
l1l1l1l1l1l1l11ll1l1l1l1lll1l111 = (l1l1l1l1l1l1l1ll1l1l1l1lll1l111/256)
l1l1l1l1l1l1l11ll1l1l1l1lll1l112 = (l1l1l1l1l1l1l1ll1l1l1l1lll1l112/256)
l1l1l1l1l1l1l11ll1l1l1l1lll1l113 = (l1l1l1l1l1l1l1ll1l1l1l1lll1l113/256)
l1l121l1l1l1l11ll1l1l1l1lll1l111 = l1l1l1l1l1l1l1ll1l1l1l1l1l1l11*l1l1l1l1l1l1l11ll1l1l1l1lll1l111
l1l121l1l1l1l11ll1l1l1l1lll1l112 = l1l121l1l1l1l11ll1l1l1l1lll1l111*l1l1l1l1l1l1l11ll1l1l1l1lll1l112
l1l1l1l1l1l1l1ll1l1l1l1l1l1l1ll1l1l1l1l1l1l1ll1l1l1l1l1l1l1l[l1l1l1l1l1l1l1ll1l1l1l111l1l11,0] = (l1l1l1l1l1l1l1ll1l1l1l1lll1l111, l1l1l1l1l1l1l1ll1l1l1l1lll1l112, round(l1l121l1l1l1l11ll1l1l1l1lll1l112*10))
l1l1l1l1l1l1l1ll1l1l1l111l1l11 += 1
l1l1l1l1l1l1l1ll1l1l1l1l1l1l1ll1l1l1l1l1l1l1l.save("out"+str(i)+".png")

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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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))

Running it

1
2
python3 reverse.py
tpctf{i_c4nt_7h1nk_0f_a_fUnny_f14g_:(}