In Blog

This post follows up on the solution to the vending machine challenge from NorthSec 2025.

OKIOK participated in the NorthSec 2025 CTF-style cybersecurity competition. OKIOK performed well, finishing 7th out of 93 teams. The competition featured a wide range of challenges, from exploiting web application vulnerabilities to reverse engineering software.

One of the challenges involved a vending machine with several aspects to exploit. The challenge was created by Maxime Nadeau.

The main challenge, which consisted of increasing the balance on an NFC card, is detailed in its own post.

The solutions to the five other vending machine challenges are presented here.

The Colored Triangles

One of the items in the vending machine was a bag of Ruffles chips. Stuck on the bag was a piece of paper with multiple rows of colored triangles.

Since we were looking for a text flag, this was most likely a way to encode it as a character string.

Before diving into internet searches, we managed to analyze the following properties:

  • 17 triangles across 7 rows
  • 8 different triangle colors

A basic search led us to discover that this was the High Capacity Color Barcode (HCCB).

Here’s what we found:

  • HCCB was invented by Microsoft in 2007 to achieve a denser and more recognizable barcode than a QR code
  • It was used by the product Microsoft Tag, launched in 2009
  • It supports either 4 or 8 different colors
  • Microsoft Tag was discontinued in 2015 due to lack of adoption

We explored the web for a decoder.

A GitHub repo seemed promising, but it only handled encoding, not decoding. We attempted to play with the tool and its examples locally, but modifying examples proved painful, especially since it was written in a language we didn’t know (ELM). We abandoned that lead.

Knowing there were 8 colors, we assumed each triangle encoded 3 bits. But which color maps to which bits?

An APK of the Microsoft Tag app, which was the official decoder, was found. Since it dated back to the 2010s, it required an old Android version, which we handled using GenyMotion. We discovered the target Android versions from the APK’s manifest; while it aimed for Android 2, it ran fine up to Android 11.

We created an Android 5.1 virtual machine and managed to install the app with some architecture conversion magic.

The splash screen mentioned HCCB, we were on the right track!

We tried scanning a valid barcode with the app.

Our hope was that decoding would happen locally, but this wasn’t the case. The app communicated with Microsoft Tag servers during decoding. And, since Microsoft Tag died 10 years ago, the servers were no longer reachable, unfortunately for us.

That attempt failed, the path to victory was a dead end. It was time to go back to the drawing board.

We continued our searches and became increasingly convinced that HCCB was the future. I mean, which other standards can claim to be “FUN”?!

In a dream on Saturday night (yep, those legendary CTF dreams), the idea struck: we had 8 unknowns representing a 3-bit encoding each. For example, a mapping might look like this:

If we read the barcode line-by-line, left to right, starting top-left, and if the first few triangles were BLUE, BLACK, YELLOW, GREEN, RED, WHITE, we’d get:

000 001 010 011 100 101 → 000001010011100101

Then convert to ASCII:

00000101 00111001 [...] → 05 35

These hexadecimal characters do not produce anything interesting in ASCII, due to the choice for the example.

However, we must not forget that we know what we are looking for. This is a known-plaintext attack. If the challenge designer is not too mean, the string “FLAG” should be encoded directly in the barcode.

And the beauty of it all is that we only have eight unknown variables. It’s not for nothing that we took the “Probability and Statistics” course! We saw a permutation that we can calculate with:

8! = 40320

That’s a very small number of permutations for a script to try. Combine all permutations, and search for the keyword FLAG in each decoded output.

We translated the full barcode into a color sequence. It could have been done in a more sophisticated manner, but there is a cost to automating a task, and it becomes efficient if the task is repeated, which was not the case for us.

We gave that list to our intern, and he quickly produced the script:

from itertools import permutations

color_palette = [
    "black",
    "yellow",
    "green",
    "red",
    "white",
    "cyan",
    "pink",
    "blue"
]

barcode_colors = [
    "blue", "green", "green", "pink", "white", "pink", "green", "blue",
    "green", "black", "yellow", "cyan", "black", "black", "green", "pink", "red",
    "green", "yellow", "black", "white", "green", "black", "blue", "cyan", "white",
    "blue", "white", "black", "blue", "pink", "red", "cyan", "green", "yellow",
    "yellow", "pink", "green", "black", "green", "cyan", "green", "pink", "cyan",
    "pink", "blue", "cyan", "yellow", "red", "cyan", "black", "pink", "cyan",
    "blue", "pink", "red", "red", "green", "cyan", "yellow", "black", "blue",
    "pink", "cyan", "red", "green", "cyan", "yellow", "white", "green", "blue",
    "white", "red", "blue", "black", "blue", "green", "green", "yellow", "blue",
    "red", "black", "cyan", "pink", "pink", "green", "cyan", "black", "red",
    "yellow", "yellow", "pink", "yellow", "blue", "white", "cyan", "red", "green",
    "cyan", "white", "green", "yellow", "black", "yellow", "yellow", "pink", "pink",
    "cyan", "cyan", "green", "green", "white", "white", "red", "red", "blue", "blue", "black", "black"
]

import binascii

target = b"FLAG"
found_any = False

for perm in permutations(range(8)):
    # Assign a 3-bit code to each color label per permutation
    mapping = {color_palette[i]: format(perm[i], "03b") for i in range(8)}
    bits = ""
    for c in barcode_colors:
        if c not in mapping:
            raise ValueError(f"Unknown color name: {c}")
        bits += mapping
    # Convert bitstream to bytes (8 bits per byte)
    bytes_out = []
    for i in range(0, len(bits), 8):
        chunk = bits[i:i+8]
        if len(chunk) == 8:
            bytes_out.append(int(chunk, 2))
    decoded = bytes(bytes_out)
    idx = decoded.find(target)
    if idx != -1:
        found_any = True
        print(f"\n=== MATCH FOUND ===")
        print(f"Mapping (color: bits):")
       for color, idxmap in zip(color_palette, perm):
            print(f"  {color}: {format(idxmap, '03b')}")
        print(f"FLAG found at offset {idx}:")
        print(f"...{decoded[max(0, idx-10):idx+10]}")
        print(f"Printable: ...{decoded[max(0, idx-10):idx+10].decode('latin1', errors='replace')}")
        print("Full decoded (first 80):", decoded[:80])
        print("===================")
if not found_any:
    print("No FLAG found in any permutation.")

Running the script yielded the correct mapping and decoded the barcode!

Support Number

Participants :

On the front of the vending machine, there was a metal plate with some info.

One of the info was a support number, which contained the string “1337”, confirming it was part of the CTF.

We tried calling to complain that our chocolates that we just bought were stale. What we heard instead were dial-up modem tones, which could correspond to data being sent over the phone line. This data could be decoded to reveal a flag.

We recorded the call. Initially, we wanted to record it cleanly using VoIP and recording tools. But we didn’t trust the sketchy software available. So we recorded the call by placing one phone on speaker and holding a second phone nearby to record.

It gave us a M4A file, which is compressed audio. We converted the file to WAV format, which is uncompressed audio.

ffmpeg -i GiftShop.m4a -ar 44100 -ac 1 GiftShop.wav

We used minimodem, a tool for decoding modem audio into text. We passed it the WAV file and set it as the receiver. After trying a few baud rates, we found the correct one, matching a Bell 103 modem.

The flag was in the final CARRIER block.

The Three Visually Encoded Flags

The side of the vending machine was beautifully designed and included the final three visual flags.

We found a reference document that we used for all three challenges. We referred to this document as the Holy Grail of alphabets for CTFs.

Vertical Line with Notches

This was the Ogham alphabet, a very old Irish script. Each letter is encoded by a pattern of notches across a central line, typically read bottom to top.

Mapping each symbol gave us this flag: FLAGDASHSENDMEAFLAG

Symbols on the Wheel

These were from the Moon script, some of whose symbols are inspired by moon phases which is great fit for the nautical CTF theme!

Decoding it revealed: FLAG ASTROSEAFARING

Semaphore Flags

These were semaphore flags, again very on-theme with maritime communication. Each flag in one hand combined with another in the other hand points to a letter via eight positions around a circle.

Leave a Comment

Start typing and press Enter to search