# Scrambler puzzle (CTF forensics problem by me!)

Before anyone starts hyperventilating, this is not part of an ongoing CTF! I just made this example CTF problem for fun...

For this problem, the flag is in the format: (so you'll know when you've found it)

flag{xxxxxxx}

Here is what you have to work with: (Google Drive links; you should open these in new tabs)

### Hint 0

Have you read the title of the question? "Scrambler puzzle..." Hmmmm... So are we scrambling something? Or perhaps something was scrambled?

### Hint 3

Unscramble the image using the key, then apply hints 1 and 2 to the result to get the flag.

• Are you sure that the key is correct? I have tried unscrambling the flag more than 50 times iteratively with the key file. It didn't work. So, tried enciphering the image (scrambling the image) nearly the same amount of time and it didn't work too. – John Brookfields Jun 3 '20 at 1:29
• Did you use AES in counter mode to encipher (scramble) the image? – John Brookfields Jun 3 '20 at 1:32
• Rot13(Bar boivbhf guvat vf gung lbh unir hfrq zbqhyb-37 nevguzrgvp va pbyhzaf 0 naq 2 naq zbqhyb-8 nevguzrgvp va pbyhzaf 1 naq 3 va) key.txt – John Brookfields Jun 3 '20 at 1:43
• @JohnBrookfields — For your first comment: it’s a little bit more than just that. For your second and third comments: it’s not quite as complicated as that. – Voldemort's Wrath Jun 3 '20 at 12:39
• @JohnBrookfields -- Perhaps Hint 1 will be helpful! – Voldemort's Wrath Jun 3 '20 at 12:57

Partial

From the hints, it's fairly evident the image is scrambled, and the key seems to list

Two coordinates, presumably new and old coordinates.

Applying this scramble with the below script

from PIL import Image
import numpy as np

im = Image.open('flag.png')
width, height = im.size

with open('key.txt', 'r') as f:

key = {}
for line in lines:
nums = list(map(int, line.split(' ')))
key[(nums[0], nums[1])] = (nums[2], nums[3])

orig = np.array(im)

arr = np.zeros([height, width, 3], dtype=np.uint8)
for (x1, y1) in key:
(x2, y2) = key[(x1, y1)]
arr[y2][x2] = orig[y1][x1]

img = Image.fromarray(arr, 'RGB')
img.save('new.png')



gives

(compression has murdered the image, but you can run the script yourself)

And now I don't know where to go.

• I think my code is wrong oops – Quintec Jun 4 '20 at 20:05
• You're on the write track... but I'm not sure what you did to flag.png. That's not at all what it should look like. Additionally, you're not interpreting Hint 1 correctly. Just assume that Hints 1 and 2 are hinting at the same thing. – Voldemort's Wrath Jun 4 '20 at 20:50
• @Voldemort'sWrath fixed my image... i think... still no idea though – Quintec Jun 4 '20 at 21:25
• That image is better, now, I think. None of the image's pixels should be black but it's tough to tell if they are or not. – Voldemort's Wrath Jun 4 '20 at 22:03
• Did you try mapping the columns to ASCII? The columns are $8$ high, so if the image is made binary (by thresholding $>127$) maybe that will give a flag of length $37$? – Carl Löndahl Jun 5 '20 at 9:08

In order to unscramble the image, I interpreted the key.txt file as pairs of coordinates - the first (x,y) is telling me which pixel to set, and the second (x,y) is telling me which pixel to get from the flag to set it as. (I also tried this vice-versa). So, the unscrambled flag looks like this: OR, inverted, looks like this: (screenshots for ease of viewing) ...these obviously don't look like anything, and even taking the RGB and converting that to binary gives me nonsense. Threshholding values to find a black and white pattern like Carl mentioned in the comment on Quintec's answer didn't work either. I'm stumped but I do think that my interpretation of the key is correct.

• You are also on the right path! Not sure why you would invert the image, but ok. I will tell you this: something of what you said in your answer is veeeeeery close to what you have to do to get the flag now. Keep thinking! – Voldemort's Wrath Jun 6 '20 at 20:18
• My writeup has been posted! – Voldemort's Wrath Jun 12 '20 at 20:27

Another idea

Firstly, swap pixels on coordinates listed in key.txt file

from PIL import Image
picture = Image.open("./flag.png")

key = open('./key.txt', 'r')

# Read coordinates and swap those pixels
while line:
coords = list(map(int, line.split(" ")))
pixelA = picture.getpixel( (coords[0] , coords[1]) )
pixelB = picture.getpixel( (coords[2], coords[3]) )
picture.putpixel( (coords[0] , coords[1]), pixelB )
picture.putpixel( (coords[2], coords[3]), pixelA )

picture.save("decoded.png")


The picture I've obtained looks like that:

Next step is swapping binary data in this file using below line "< decoded.png xxd -p -c1 | tac | xxd -p -r > swapped.png" Then I've inspected hexdump of the file, but the flag wasn't there. I would appreciate a hint if it's possible. I was thinking about copying pixels instead swapping, but ended up with the same result.

• Keep up the amazing work! This is on the right track and is still very close! – Voldemort's Wrath Jun 9 '20 at 11:13
• My writeup has been posted! – Voldemort's Wrath Jun 12 '20 at 20:26

# My Own Writeup

From the title and Hint 0, we can guess that flag.png was scrambled in some way. Looking at key.txt, we can assume that it is formatted like this:

OLD_X OLD_Y NEW_X NEW_Y
OLD_X OLD_Y NEW_X NEW_Y
OLD_X OLD_Y NEW_X NEW_Y

So, writing a Python program (using PIL), we can unscramble flag.png to get the image with all of its OLD_Xs and OLD_Ys. The unscrambled image looks something like this:

(enlarged to show detail)

Now, using Hints 1 and 2, we see that this image is encoded with something that has to do with binary. It’s also very interesting that there are 8 rows (meaning the number of pixels is a multiple of 8). This suggests that the actual pixels represent some binary value. What we can do with this is assume that the dark pixels represent 0’s and the light ones represent 1’s. Here’s what this might look like:

(enlarged to show detail)

...and so on.

We continue, then, to write another Python program (using PIL, of course). We make the following assumptions: a pixel is considered dark when the sum of its R, G, and B values is less than 350; a pixel is considered light when the sum of its R, G, and B values is greater than 500; and the binary is read from left to right and top to bottom.

The resulting binary string is: 01100110011011000110000101100111011110110110010000110000011011100111010001011111011101010101111101101010011101010011010101110100010111110011000100110000001100000011000000110000001100000011000001110110001100110011001100110011010111110110001101110010011110010111000001110100001100000011111101111101

Converting this to ASCII, we get: flag{d0nt_u_ju5t_1000000v333_crypt0?} and this is our flag!

• Tbh, it's not hard to come up with the idea of solving until the penultimate steps (as many people aware or at least me, we have to "swap" then convert to binary). The last step however, need some assumption i.e. "considered dark if sum < 350" and "considered light if sum > 500". This kind of assumption should be generally avoided. I actually tried to find something with R or G or B to get the bit, e.g. by xor-ing them or else, because a good puzzle needs to have a deterministic step to solve. If you want solvers to get that assumption, it's better to leave a clue of 350 and 500 somewhere. – athin Jun 12 '20 at 23:07
• athin echoes my thoughts exactly. As someone who does a lot of CTFs, this challenge would certainly be complained about in an actual CTF as being way too guessy. – Quintec Jun 12 '20 at 23:21
• I also think this required way too many guesses, including the order of reading bits, the idea of converting them to ASCII, the guessing of what pixels are considered dark and what pixels are considered light and the structure of the text file (is it old_x old_y new_x new_y, or new_x new_y old_x old_y or something else?) – the default. Jun 13 '20 at 11:54
• I consider myself well-versed in CTFs, and I didn't like the problem, especially after seeing the correct solution. As you can easily see from looking at the other answers, all the people who tried to solve this simply haven't tried enough possibilities to obtain the flag. I do not think a good problem should require this much guesswork. I also feel like the 3 last comments you posted here are saying pretty much "This is a good problem, you're all wrong" (literal quote: "applies just fine to a CTF and the topic"). – the default. Jun 13 '20 at 13:55
• I must agree with Voldemort on this one. I would say that it was not THAT guessy. I mean the mapping to ASCII was pretty obvious, at least if you played a few CTFs. Also the three coordinates being used suggested that some distance (1 or 2-norm was used) and based on the hints a map to binary should be mostly evident. But I cannot say this is my favorite type of problem (and you see similiar problems from time to time), I don’t find it very entertaining so I never attempted to solve it. – Carl Löndahl Jun 13 '20 at 21:47