Danish Christmas CTF 2021 by NC3/Police – Intermediary – Bitmap Cache

So with the beginner-category done, the "Øvet" (intermediary) category is the next category I'm going to tackle. Now, with larger write-ups for these, they will get their own blog-posts. This one is for the first challenge in the category (as I started very late, this is the first, though it didn't get released until December 9th).

NC3 Challenge: Bitmap Cache

In the challenge we are provided with a .zip-file and the text "Do you know that when you have a bunch of small images, that combined could reveal a larger image?". First things first - download the archive and unpack it, and we are indeed provided 32 individual .bmp-images.

But as always in CTF's, we do not trust the file-extension and doing a quick file * for the files shows that this is actually PNG-images.

Bitmap Cache - PNG-images

To quickly rename all the images, we can use the mmv-command. This command is mostly like the normal mv "move"-command in Bash, but provides us with the ability to rename all the files.

Bitmap Cache - MMV command

A quick look, and yes - we now see individual images of characters. The challenge now is "just" to combine the images into one single image in the correct order. Being an avid Python-programmer, I of cause are going to use Python to help us here.

We have all the images in one single contained directory, so firstly we loop through all the image-files, use the library PIL to open each image and save them to memory using a list. Then we extract the width of each image and combine the total width - we already have the height from the earlier file-command (64 pixels).
We the use the Image.new()-function from the PIL-library to initiate a new image with the correct size. Then we just have to loop through our list of image-objects and paste them in with a increasing horizontal offset. All left, is saving the new concatenated image to disk.

Bitmap Cache - first py

And voila! We have a combined image of all the individual images we were provided. Though it seems odd, and no flag is "visible" for us.

Bitmap Cache - concatenated_img_first_test

Okay - the right characters are present (nc3{}), so we can be fairly sure this is the right path for the challenge. We just have to figure out what sorting we need to use.

As we saw earlier, all the files have some weird numbering in the filename. Maybe that is a key for the sorting? Lets try that. In the Python-code we then change the sorting before populating the list. This is done by using the .sort()-function with a "lambda"-key using regex to get the digits of the filenames.

Bitmap Cache - second py

Again we are seeing a wrong sorting in the image. And it is not just a quick reverse.

Bitmap Cache - concatenated_img_second_test

This then got me thinking - because what is up with the background-colors? It almost seems like they are all different shades of grey. Could that be our sorting-key?

I then tried in Bash to extract the "mean"-color (like a number of all colors in the image combined) via the identify-command and a bit of for-loop trickery:

for f in *.png; do identify -format '%[mean] %f\n' "$f"; done | sort

But looking at the image for the starting n-character, it was not the first or last in the spectrum. This is probably because of the different amounts of black from the characters in the images. So to extract the background-color and use the value for sorting we are once again heading to Python-land.

In our code from before, we then remove all the old sorting so we are again just reading all the file-objects into our list. But! We are changing up the format, because we now want one more slice of data - meaning that instead of just adding the image-objects directly into the list, we now insert it into a tuple and though create a list of tuples.
The second value, we want for the sorting, is at the same time extracted by getting just one pixel from the image at the position (1,1) - a place the character never is. We then take that pixel, extract the RGBA-values and simply create a sum on that numbers. After the list of tuples is populated we then sort the list (reversed to ensure we go from "most white" to "most grey") and generate our new flag-image.

#!/usr/bin/env python3
import os
from PIL import Image

images = []
dir_path = 'bitmap_cache/png'

for img in os.listdir(dir_path):
    img_path = os.path.join(dir_path, img)
    with Image.open(img_path) as open_img:
        images.append((sum(list(open_img.getpixel((1,1)))), Image.open(img_path)))

images.sort(reverse=True)

concatenated_img = Image.new('RGB', (32 * 32, 64))

x_offset = 0

for img in images:
    concatenated_img.paste(img[1], (x_offset, 0))
    x_offset += img[1].size[0]

concatenated_img.save('concatenated_img.png')

Generated image and our flag for the challenge:

Bitmap Cache - concatenated_img

Leave a Reply

Your email address will not be published. Required fields are marked *