Skip to content

Instantly share code, notes, and snippets.

@Lense
Last active September 23, 2016 01:33
Show Gist options
  • Save Lense/a8e94e96f886cb773f646b8aaea806fc to your computer and use it in GitHub Desktop.
Save Lense/a8e94e96f886cb773f646b8aaea806fc to your computer and use it in GitHub Desktop.
CSAW quals 2016 brainfun

Challenge

Title: brainfun
Competition: CSAW quals 2016
Category: Forensics
Points: 150
Description: Scrambled Fun for Everyone! Author: fang0654 <brainfun.png>

Background

  • There's nothing hidden in the file other than the pixel data.
  • The image is 512x512, but can be scaled down 32x32 to match the blocks to pixels.
  • The RGB values are all multiples of 0x10. An example pixel could be (0x40, 0xf0, 0x20).
  • There is also transparency.
  • There aren't that many different alpha values. Many occur around the same number of times (28-30).
  • The alpha values are in the printable ASCII range.
  • The values that occur around the same number of times are lowercase letters, and the others are +, -, ., and a single newline.
  • The name of the challenge is brainfun, which sounds kind of like brainfuck, which is an esoteric programming language which uses those symbols.
  • The challenge description says that it's scrambled.

Progress

  • Pige0n noticed that the letters can be rearranged to spell "my rainbow", which suggests that there is a definite order to the pixels.
  • There are probably sections of lowercase letters and sections of brainfuck symbols.
  • Brainfuck symbols had red values around the middle, and lowercase letters had low and high values.

Solution

  • Sort the pixel values by RGB value, using the key red<<8 + green<<4 + blue. Edit (thanks BookGin):
    • It should be red<<16 + green<<8 + blue to sort by full values, but I abused the fact that all the RGB values take the format of 0xN0, so a pixel (0x10, 0x20, 0x30) will get converted to 0x1230 this way.
    • I probably should have used struct.pack("hhh", red, green, blue)
  • It turns out that they spell "owmybrain", but whatever.
  • Running that as brainfuck spits out the flag: flag{w3_r_th3_h0llow_m3n}
  • Here's a one-liner (it's not very efficient):
python3 -c '__import__("pybrainfuck").BrainFck().run("".join([chr(p[3]) for p in sorted([list(__import__("PIL", globals(), locals(), ["Image"]).Image.open("brainfun.png").getdata())[r*512 + c] for r in range(0, 512, 16) for c in range(0, 512, 16)], key=lambda p: (p[0]<<8) + (p[1]<<4) + p[2])]))'
owmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmyb++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.++++++.-----------.++++++.++++++++++++++++++++.----.--------------------------------------------------------------------.++++++++++++++++++++++++++++++++++++++++++++.+++++++++++++++++++.-------------------.+++++++++++++++++++++.------------.-----------------------------------------------------.++++++++++++++++++++++++++++++++++++++++++++.+++++++++.--------------------------------------------------------.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++..+++.++++++++.------------------------.++++++++++++++.----------------------------------------------------------.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.+++++++++++++++.
owmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmybrainowmyba
#!/usr/bin/python3
from PIL import Image
from pybrainfuck import BrainFck
# Read image and get pixel data as list
pixels = list(Image.open('brainfun.png').getdata())
# Extract just get the blocks. Fun fact: PIL's resize with PIL.Image.NEAREST
# for nearest neighbor messes with the values, but mtPaint does it correctly.
pixels = [pixels[r*512 + c] \
for r in range(0, 512, 16) \
for c in range(0, 512, 16)]
# Sort the pixels by RGB value
pixels.sort(key=lambda p: (p[0]<<8) + (p[1]<<4) + p[2])
# Run the alpha values as Brainfuck
BrainFck().run("".join([chr(p[3]) for p in pixels]))
@BookGin
Copy link

BookGin commented Sep 19, 2016

Hi, your background information is very helpful. It's much better than those who provide only the answers.

Sort the pixel values by RGB value, using the key red<<8 + green<<4 + blue

However, I wondor why we should sort those pixels with key red<<8 + green<<4 + blue ?

@nazywam
Copy link

nazywam commented Sep 19, 2016

Hey BookGin!

The values had to be sorted by 3 keys, red being the most important one.
By using binary shifts on red, green and blue, you are able to sort them using only one key :)

x = red<<8 + green<<4 + blue
sort_by(x)

is equal to:

sort_by(blue)
sort_by(green)
sort_by(red)

Cheers!

PS: Here's my solution :)

@BookGin
Copy link

BookGin commented Sep 20, 2016

Thanks for your prompt reply!

I think the pixel value is in the range from 0 to 255, where 256 = 2^8. Therefore, I would write :

x = red<<16 + green<<8 + blue
sort_by(x)

(though in this question, both sorting key lead to the same results)

Do I misunderstand anything? Or "sorted by RGB" is different from sorting coordinates?

@nazywam
Copy link

nazywam commented Sep 20, 2016

Oh yeah, didn't notice that

Yup, the shifts should be by 8bits each

@Lense
Copy link
Author

Lense commented Sep 21, 2016

@BookGin You're right that it should be red<<16 + green<<8 + blue to sort by full values, but I'm abusing the fact that all the RGB values take the format of 0xN0, so a pixel (0x10, 0x20, 0x30) will get converted to 0x1230 this way and 0x102030 the "correct" way. Sorry about that; it really wasn't clear.

To do it without bit manipulation, I probably should have used struct like: pixels.sort(key=lambda p: struct.pack("hhh", p[0], p[1], p[2]))

@BookGin
Copy link

BookGin commented Sep 22, 2016

@Lense Oh, The RGB values are all multiples of 0x10, so that's a correct way as well.

Thank you for clarifying it !

@C0deH4cker
Copy link

You could also have just done pixels.sort(), which uses Python's builtin tuple comparison to fist compare red, then green, etc. That's what I did for this challenge

@Lense
Copy link
Author

Lense commented Sep 23, 2016

@C0deH4cker I didn't do that because that would also sort by alpha value, which isn't what I want. In this case it would have the same result, but semantically it isn't as clear. If the challenge had had multiple pixels with the same RGB but different alpha, the pixels would keep the ordering they had in the image, since the Python sort I used is stable, while sorting with the whole tuple would sort by the alpha value too.

tl;dr yeah that would work here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment