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
- Participant : Étienne Ducharme
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
- Participant : Christophe St-Georges
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.