Challenge Description
You can’t see him, but can you see the flag?
http://us-east-1.linodeobjects.com/wpictf-challenge-files/braille.png
made by: ollien, with a little help from acurless
Braille
The first clue for this challenge is a URL to a PNG image. This is how it looks:
Apparently, it’s something written in Braille. We need to do an “OCR” of this picture first, and then decode it.
According to Wikipedia, 6-dot Braille patterns are coded in Unicode as this:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
U+280x | ⠀ | ⠁ | ⠂ | ⠃ | ⠄ | ⠅ | ⠆ | ⠇ | ⠈ | ⠉ | ⠊ | ⠋ | ⠌ | ⠍ | ⠎ | ⠏ |
U+281x | ⠐ | ⠑ | ⠒ | ⠓ | ⠔ | ⠕ | ⠖ | ⠗ | ⠘ | ⠙ | ⠚ | ⠛ | ⠜ | ⠝ | ⠞ | ⠟ |
U+282x | ⠠ | ⠡ | ⠢ | ⠣ | ⠤ | ⠥ | ⠦ | ⠧ | ⠨ | ⠩ | ⠪ | ⠫ | ⠬ | ⠭ | ⠮ | ⠯ |
U+283x | ⠰ | ⠱ | ⠲ | ⠳ | ⠴ | ⠵ | ⠶ | ⠷ | ⠸ | ⠹ | ⠺ | ⠻ | ⠼ | ⠽ | ⠾ | ⠿ |
So these characters are like dot-represeted binary numbers, and their Unicode values are their binary values plus 0x2800.
To extract the characters, I wrote some Python code. The way I determine the borders of each character is simply assume every character has the same width and height, and divide the picture evenly. Thus you need to crop off some black margin of the picture if you want to use this code. You can employ a more sophisticated algorithm of course.
import numpy as np
from PIL import Image
def greyscale_to_braille(mat):
ver1 = round(mat.shape[0] / 3.0)
ver2 = round(mat.shape[0] * 2 / 3.0)
hor = round(mat.shape[1] / 2.0)
dots = [
mat[:ver1, :hor],
mat[ver1:ver2, :hor],
mat[ver2:mat.shape[0] - 1, :hor],
mat[:ver1, hor:mat.shape[1] - 1],
mat[ver1:ver2, hor:mat.shape[1] - 1],
mat[ver2:mat.shape[0] - 1, hor:mat.shape[1] - 1]]
weight = np.array([1, 2, 4, 8, 16, 32])
digits = np.array(list(map(lambda x: x.sum() > 2000, dots)))
offset = digits.dot(weight)
return chr(0x2800 + offset)
def char_at(bmp, row, col):
# Uncomment lines below if you want to check the border of each character is correct.
# It generates a picture file for every cropped character named after the row and col.
# img.crop((round(col * char_width),
# round(row * char_height),
# round((col + 1) * char_width),
# round((row + 1) * char_height))).save('braille-img/r%dc%d.png' % (row, col))
mat = bmp[round(row * char_height): round((row + 1) * char_height),
round(col * char_width): round((col + 1) * char_width)]
return greyscale_to_braille(mat)
img = Image.open('braille.png').convert('LA')
bmp = np.array(img)[:,:,0]
char_height = bmp.shape[0] / 57.0
char_width = bmp.shape[1] / 33.0
braille_chars = []
for row in range(57):
for col in range(33):
braille_chars.append(char_at(bmp, row, col))
braille_chars.append('\n')
f = open('braille.txt', 'w')
f.write(''.join(braille_chars))
f.close()
After running this code, the braille.txt
contains:
⠼⠛⠋⠼⠙⠼⠑⠼⠙⠉⠼⠙⠼⠋⠼⠚⠼⠃⠼⠚⠼⠁⠼⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠃
⠼⠚⠼⠚⠼⠉⠑⠼⠚⠼⠚⠼⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠓⠼⠚⠼⠚
⠼⠚⠼⠙⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠙⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠑⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠙⠼⠚⠼⠚⠼⠚⠼⠉⠼⠓⠼⠚⠼⠚⠼⠚⠼⠁⠼⠚⠼⠚⠼⠙⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠙⠼⠚⠼⠚⠼⠚⠼⠉⠼⠚⠼⠚⠼⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠛⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠓⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠓⠼⠚⠼⠚⠼⠚⠼⠙⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠓⠼⠚⠼⠚⠼⠚⠼⠙⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠙⠼⠊⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠙⠼⠊⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠃⠼⠓⠼⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠃
⠋⠼⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠃⠑⠃⠼⠙⠼⠚⠼⠚⠼⠙⠼⠚⠼⠚⠼
⠚⠼⠉⠼⠁⠉⠼⠊⠼⠋⠼⠛⠼⠓⠃⠼⠁⠼⠙⠼⠚⠑⠼⠓⠼⠉⠉⠼⠃⠼⠉⠼⠁
⠼⠋⠼⠛⠼⠓⠼⠊⠼⠁⠼⠙⠼⠚⠑⠋⠋⠉⠼⠁⠼⠓⠼⠉⠋⠼⠊⠼⠁⠼⠑⠼⠛
⠼⠑⠑⠑⠃⠁⠼⠁⠼⠑⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠋⠼⠚⠼⠑⠃⠼⠓⠼
⠉⠉⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠉⠼⠁⠋⠋⠼⠚⠋⠼⠚⠼⠑⠼⠚⠼⠚⠼⠃
⠼⠋⠼⠁⠋⠼⠁⠼⠓⠼⠙⠁⠼⠉⠃⠼⠚⠼⠉⠼⠙⠼⠃⠼⠚⠼⠚⠼⠙⠼⠛⠼⠚
⠁⠼⠚⠼⠚⠼⠙⠼⠃⠼⠚⠼⠉⠼⠃⠑⠼⠚⠼⠙⠼⠉⠼⠃⠼⠙⠼⠁⠼⠙⠼⠙⠼
⠉⠼⠁⠼⠚⠼⠙⠼⠙⠉⠼⠚⠼⠚⠼⠃⠑⠼⠛⠼⠉⠼⠋⠼⠓⠼⠛⠼⠉⠼⠛⠼⠙
⠼⠛⠼⠃⠼⠛⠼⠙⠼⠋⠼⠁⠼⠋⠼⠃⠼⠚⠼⠚⠼⠃⠑⠼⠛⠼⠙⠼⠋⠼⠑⠼⠛
⠼⠓⠼⠛⠼⠙⠼⠚⠼⠚⠼⠃⠑⠼⠋⠼⠙⠼⠋⠼⠁⠼⠛⠼⠙⠼⠋⠼⠁⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠃⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠛⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠓⠼⠚⠼⠚⠼⠚⠼⠙⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠓⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠉⠼⠉⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠁⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠉⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠃⠼⠙⠼⠚⠼⠚⠼⠙⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠃⠼⠙⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠁⠼⠑⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠙⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠉⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠚⠉⠼⠊⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠁⠼⠛⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼
⠚⠼⠁⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚
⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠼⠚⠀⠀⠀
Decode
As I read from Wikipedia, many different Latin alphabets of different languages are encoded into this set of Braille characters, and even non-Latin writing systems. Since this CTF activity is English based, I’ll try English first.
Googling ‘braille to english’ and I found a website: dcode.fr (This website is awesome. It can even decode Sheikah symbols from Legend of Zelda: Breath of the Wild). So I pasted extracted Braille above, but doesn’t seem right.
I noticed that in this result about half of the lines end with an undecoded ⠼ character. Can it be this character should be decoded with the following character, but saw a line wrap instead? It’s worth a try to decode without LF characters.
This time it seems to make much better sense. These are hexadecimal numbers. So let’s try further decoding this.
First step is always to check if it contains printable characters.
It shows ‘ELF’ at the head of this byte strings. Very good chance it’s a executable file on Linux! Definitely I should save this to a file and try running it.
f = open('braille.bin', 'wb')
f.write(a.to_bytes(3840//8, 'big'))
f.close()
chmod +x braille.bin
./braille.bin
That’s the flag!