Link to archived challenge

Category Difficulty Solves Author
forensics hard 0 Artemis

Description

This is the last t3l0s microcontroller we found containing secret messages.

We have no idea what’s going on with this one. Good luck.

Note: there are two versions of the exact same recording.
One is in grayscale (-gray), the other in full RGB. You only need one to solve the challenge.

Players are given two files to download: blinkenlights-2.mp4 and blinkenlights-2-gray.mp4.

Analysis

This challenge is similar to Blinkenlights 1 except literally impossible.

We have no idea what’s going on with this one. Good luck.

Turns out that’s not just some flavor text I added, but also an accurate description of our player base.
Blinkenlights 2 earned the dubious accomplishment of getting zero solves during the CTF.

This time, the plaintext has been “split” into two halves which need to be XORed together to recover the flag.

At any point during the video, there is a “primary” and a “secondary” LED:

  • The primary LED stays on most of the time; going low signifies a “0”.
  • The secondary LED stays off most of the time; going high signifies a “1”.

The primary and secondary LEDs swap three times; each primary LED is a different “half” of the plaintext.
The video can be divided into 4 quarters:

  1. Primary: green (1/2)
  2. Primary: red (1/2)
  3. Primary: green (2/2)
  4. Primary: red (2/2)

After extracting the raw bitstreams, we need to:

  1. Concatenate sections 1 & 3 (green) and 2 & 4 (red)
  2. XOR the green and red streams together
  3. Discard the prefix of null bytes

Solution

My full solve script can be viewed here.
It requires Python 3.10+, bitstring, Pillow, and PyAV: pip install bitstring pillow av.

I’ve tuned the thresholds to ensure it works properly on both videos.

Here’s the high-level outline of my script:

  1. Iterate through each video frame and extract the values of a pixel at each LED.
  2. Translate the varying “analog” color intensity values to boolean state transitions (with deadzones for debouncing).
  3. Decode these state transitions into a pair of bitstreams.
    The order of LED state transitions is used to determine where the primary LEDs swap.
  4. XOR the two bitstreams and display the recovered flag.

Conclusion

As I mentioned earlier, this was the only unsolved challenge in our event.
Not that I blame the players: during playtesting, I wasn’t able to solve it without some additional hints from the author.

In hindsight, we probably should have made it easier to follow and less “guessy”.
There are a number of ways we could’ve done this:

  • Provide the source code of the software that was running on the microcontroller.
    Players could then analyze the code to determine how to decode the LED patterns.
  • Alternatively, the compiled software could be provided, involving some binary reverse engineering.
  • Instead of making the encoding harder to understand, make the recording noisier.
    • The camera could move around, the lighting could be worse, etc.
    • A simple error correction system could be involved as well (although this should be obvious to the player to avoid confusion).