Swiss Hacking Challenge 2023 - ezsignal

Posted on Apr 26, 2023

Information

Challenge category: misc

Challenge Description

More Hardware stuff. Can you SEE the flag?

Files

We are given an ezsignal.zip file containing EZsignal.sal file. It’s a Saleae Logic file, see my writeup of bbsignal for more information on it.

Analysis

Overview

The file opened in Logic looks like the following:

The capture opened in Saleae Logic

We see three channels with very similar signals and another channel with a much slower signal that switches between the blocks of data in the other ones.

Based off the description “Can you SEE the flag?” the first three channels might be R/G/B and the third line means a newline aka. HSYNC in VGA terms.

Exporting the data

Logic sadly has no “VGA emulator” so I had to hack something together myself. We can hit File > Export Data--- to generate a CSV file. I used the following settings:

  • All channels selected
  • Time Range: All Time
  • Export Format: CSV
  • Use ISO8601 timestamps: no

Parsing the data

The python script I wrote will do the following:

  1. Read the CSV
  2. Loop through all of the CSV entries and split the data by rows if the fourth channel (3) is low
  3. Go through every row per line and draw pixels on an image (multiplied based on the timing in the first row (0))
  4. Save the image and open it

Here’s the code:

#!/usr/bin/python3

# Image generation
from PIL import Image, ImageDraw

import os

# Load the csv file into a dict of lists
def load_csv(filename):
    with open(filename) as f:
        data = f.readlines()
    data = [row.strip().split(",") for row in data]
    return data

# Open digital.csv
data = load_csv("digital.csv")
# Rows of image
pixelrows = []
# For each row in the csv file
current = []

# The image + draw object
img = Image.new("RGB", (1000, 1000), color="white")
draw = ImageDraw.Draw(img)

# remove header
data = data[1:]


for row in data:
    # If the third channel is not 0 aka. next line
    if row[2] == "1":
        # Add the current row to the list
        current.append(row)
    # line break!
    else:
        # Add the current row to the list
        pixelrows.append(current)
        # Reset the current list
        current = []

# we need to keep track of the position
pos = (0, 0)
# loop through rows
for row in pixelrows:
    for pixel in row:
        # print as many as time delay to previous one divided by 20ns
        self_time = float(pixel[0])
        try:
            next_time = float(row[row.index(pixel) + 1][0])
            delay = next_time - self_time
            # delay is in seconds, so divide by 19ns (smallest time unit)
            delay = delay / 0.000000019
        except IndexError:
            delay = 1
        if pixel[1] == "1":
            # create a line from pos + delay
            draw.line((pos[0], pos[1], pos[0] + delay, pos[1]), fill="black", width=1)
        # space to the next pixel
        pos = (pos[0] + delay, pos[1])
    # new line
    pos = (0, pos[1] + 1)

# save the image
img.save("ezsignal.png")
# open the image
os.system("xdg-open ezsignal.png")

Flag

The generated image contains the flag written out:

The flag

Conclusion

Figuring out the timing thing was what cost me the most time. I also had issues identifying the signals in the beginning, was a very fun challenge tough and I learned some things about VGA.

References

  1. http://javiervalcarce.eu/html/vga-signal-format-timming-specs-en.html