Rotate.zip contains two files

- flag.jpg.enc
- rotate.py

flag.jpg.enc is an encrypted version of flag.jpg which we must decrypt inorder to find the flag.

rotate.py contains the following code

import sys

import math

import struct

p = lambda x: struct.pack('f', x)

u = lambda x: struct.unpack('b', x)[0]

if len(sys.argv) != 3:

sys.exit(1)

filename = sys.argv[1]

key = math.radians(int(sys.argv[2]))

bs = open(filename, 'rb').read()

enc = open(filename + '.enc', 'wb')

for i in range(0, len(bs), 2):

x, y = u(bs[i]), u(bs[i+1])

enc.write(p(x * math.cos(key) - y * math.sin(key)) + p(x * math.sin(key) + y * math.cos(key)))

rotate.py takes two arguements, the first being the file to encrypt, the second being a numeric key. The key is converted to raidians. Finally the code goes through each byte of the file and does various trigonometric functions with it. We could look at reversing the cipher, but math is for nerds.

Instead, since we know that jpeg files begin with a specific value, we can bruteforce all the possible keys to find with the known plaintext to compare against the ciphertext. Once a match is found, we can then use the discovered key to decrypt the rest of the image.

Radians, and trigonometric functions such as sin() and cos() are all cyclical, since they're all based on the circle. As the code converts the key into integers and then radians, we know there are only 360 possible values (less than 9 bits of entropy). A better alternative would be to have the key be a 64 bit floating point number.

import sys

import math

import struct

p = lambda x: struct.pack('f', x)

u = lambda x: struct.unpack('b', x)[0]

def encrypt(first,second,rotate):

key = math.radians(rotate)

x, y = u(first), u(second)

return p(x * math.cos(key) - y * math.sin(key)) + p(x * math.sin(key) + y * math.cos(key))

if len(sys.argv) != 2:

sys.exit(1)

filename = sys.argv[1]

enc = open(filename, 'rb').read()

bs = open(filename + '.jpg', 'wb')

rotate = 0

for i in range(0, 360):

APP0 = "ffd8".decode('hex')

test = encrypt(APP0[0], APP0[1], i)

if test == enc[0:8]:

rotate = i

print "found key"

break;

for i in range(0, len(enc), 8):

for h in range(0, 256):

k=0

for j in range(0, 256):

test = encrypt(chr(h), chr(j), rotate)

if test == enc[i:i+8]:

print '.',

bs.write(chr(h)+chr(j))

break;

else:

k=1

if k == 0:

print '+',

break;

else:

print '!'

And when its all said and done we see...