Bit Mosaic

28 Dec 2025

ASIS CTF Finals 2025 Challenge: Bit Mosaic
Category: Hardware
Flag: ASIS{P!XX3L5}
Description: An mosaic display was tapped mid‑story. All we have are some toggles and a promise that the picture is worth the trouble. Let’s see what beauty was drawn.


This challenge contained a zip with 3 files, a pinmap, a display, and a .sal file.

A quick reverse image search revealed that the display was a SSD1351 OLED, and Google provided some useful information about it.

This matched with the pinmap and it was pretty obvious at this point that the tapped display was a SSD1351 OLED. I analyzed the .sal file a bit more (with the help of ChatGPT :3) revealed that it was using SPI.

I downloaded Saleae’s Logic 2 analyzer and created a SPI analyzer with these configurations:

I then exported it as a txt file.

After doing some more research, I found that 0x5c was the command that writes the ram, and the bytes before that was to be ignored when creating the image. I also found that 0x15 is a command that sets the width of the image, and 0x75 was the command that sets the height of the image.

Using ctrl-f, I found the lines I was looking for in the exported txt file!

6.355588041666667,0,0x15,   
6.355716875000000,0,0x00,   
6.355845708333334,0,0x5F,   
6.355974541666667,0,0x75,   
6.356103333333333,0,0x00,   
6.356232166666667,0,0x3F,   
6.356361000000000,0,0x5C,

The two 0x00s meant that the image started at (0,0), and 0x5F & 0X3F meant that the image’s size was 96x64.

I then used the script below to parse through the txt file and convert the bytes to an image.

from PIL import Image

WIDTH  = 96
HEIGHT = 64

data = []
with open("spi.txt") as f:
    for line in f:
        if "0x" in line:
            try:
                b = int(line.split("0x")[1][:2], 16)
                data.append(b)
            except:
                pass

start = data.index(0x5C) + 1

pixel_bytes = data[start : start + WIDTH * HEIGHT * 2]

pixels = []
for i in range(0, len(pixel_bytes), 2):
    p = (pixel_bytes[i] << 8) | pixel_bytes[i + 1]

    r = ((p >> 11) & 0x1F) * 255 // 31
    g = ((p >> 5)  & 0x3F) * 255 // 63
    b = (p & 0x1F) * 255 // 31

    pixels.append((r, g, b))

img = Image.new("RGB", (WIDTH, HEIGHT))
img.putdata(pixels)
img.save("mosaic.png")

This was the resulting image I got:

Nice! This gives me the flag ASIS{P!XX3L5}.

My team, tjcsc, won 17th place in this ctf, yay!

Thank you for reading my bit-mosaic writeup :D