
Ce billet fait suite à la solution du défi de la distributrice de NorthSec 2025.
OKIOK a participé à la compétition de cybersécurité NorthSec 2025, de type CTF. OKIOK s’y est bien classée, terminant 7e sur 93 équipes. La compétition proposait un large éventail de défis, de l’exploitation de vulnérabilités d’applications web à la rétro-ingénierie de logiciels.
L’un des défis reposait sur une distributrice comportant plusieurs aspects à exploiter. Le défi a été conçu par Maxime Nadeau.
Le défi principal, qui consistait à augmenter le solde d’une carte NFC, est détaillé dans son propre billet.
Les solutions des cinq autres défis de la distributrice sont présentées ici.
Les triangles colorés
- Participant: Étienne Ducharme
L’un des articles de la distributrice était un sac de croustilles Ruffles. Sur le sac était collé un bout de papier portant plusieurs rangées de triangles colorés.

Comme nous cherchions un drapeau textuel, il s’agissait fort probablement d’une façon de l’encoder sous forme de chaîne de caractères.
Avant de nous lancer dans les recherches sur Internet, nous avons pu relever les propriétés suivantes:
- 17 triangles répartis sur 7 rangées
- 8 couleurs de triangles différentes
Une recherche de base nous a permis de découvrir qu’il s’agissait du High Capacity Color Barcode (HCCB).
Voici ce que nous avons trouvé:
- Le HCCB a été inventé par Microsoft en 2007 pour obtenir un code-barres plus dense et plus reconnaissable qu’un code QR
- Il a été utilisé par le produit Microsoft Tag, lancé en 2009
- Il prend en charge 4 ou 8 couleurs différentes
- Microsoft Tag a été abandonné en 2015, faute d’adoption
Nous avons cherché un décodeur sur le web.
Un dépôt GitHub semblait prometteur, mais il ne gérait que l’encodage, pas le décodage. Nous avons tenté de jouer avec l’outil et ses exemples localement, mais modifier les exemples s’est révélé pénible, d’autant plus qu’il était écrit dans un langage que nous ne connaissions pas (ELM). Nous avons abandonné cette piste.
Sachant qu’il y avait 8 couleurs, nous avons supposé que chaque triangle encodait 3 bits. Mais quelle couleur correspond à quels bits?
Nous avons trouvé un APK de l’application Microsoft Tag, le décodeur officiel. Comme il datait des années 2010, il exigeait une vieille version d’Android, ce que nous avons réglé avec GenyMotion. Nous avons découvert les versions d’Android visées dans le manifeste de l’APK: il ciblait Android 2, mais fonctionnait bien jusqu’à Android 11.
Nous avons créé une machine virtuelle Android 5.1 et réussi à installer l’application grâce à un peu de magie de conversion d’architecture.

L’écran d’accueil mentionnait le HCCB: nous étions sur la bonne voie!
Nous avons essayé de scanner un code-barres valide avec l’application.

Nous espérions que le décodage se ferait localement, mais ce n’était pas le cas. L’application communiquait avec les serveurs de Microsoft Tag pendant le décodage. Et comme Microsoft Tag est mort il y a 10 ans, les serveurs n’étaient plus joignables, malheureusement pour nous.

Cette tentative a échoué, le chemin vers la victoire était une impasse. Il fallait retourner à la planche à dessin.
Nous avons poursuivi nos recherches et sommes devenus de plus en plus convaincus que le HCCB était l’avenir. Franchement, quel autre standard peut prétendre être « FUN »?!

Dans un rêve, le samedi soir (eh oui, ces légendaires rêves de CTF), l’idée a jailli: nous avions 8 inconnues représentant chacune un encodage de 3 bits. Une correspondance pourrait par exemple ressembler à ceci:

Si nous lisons le code-barres ligne par ligne, de gauche à droite, en partant du coin supérieur gauche, et si les premiers triangles sont BLEU, NOIR, JAUNE, VERT, ROUGE, BLANC, nous obtenons:
000 001 010 011 100 101 → 000001010011100101
Puis, en convertissant en ASCII:
00000101 00111001 […] → 05 35
Ces caractères hexadécimaux ne donnent rien d’intéressant en ASCII, en raison du choix fait pour l’exemple.
Il ne faut cependant pas oublier que nous savons ce que nous cherchons. C’est une attaque à texte clair connu. Si le concepteur du défi n’est pas trop méchant, la chaîne « FLAG » devrait être encodée directement dans le code-barres.
Et la beauté de la chose, c’est que nous n’avons que huit variables inconnues. Ce n’est pas pour rien que nous avons suivi le cours « Probabilités et statistiques »! Nous avions affaire à une permutation, que l’on calcule ainsi:
8! = 40320
C’est un très petit nombre de permutations à essayer pour un script. On combine toutes les permutations et on cherche le mot-clé FLAG dans chaque sortie décodée.
Nous avons traduit le code-barres complet en une séquence de couleurs. Nous aurions pu procéder de façon plus sophistiquée, mais automatiser une tâche a un coût, et ça devient rentable quand la tâche est répétée, ce qui n’était pas notre cas.
Nous avons donné cette liste à notre stagiaire, qui a rapidement produit le 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.")
L’exécution du script a donné la bonne correspondance et décodé le code-barres!

Le numéro de soutien
Participants:
À l’avant de la distributrice se trouvait une plaque de métal portant quelques informations.
L’une d’elles était un numéro de soutien contenant la chaîne « 1337 », ce qui confirmait qu’il faisait partie du CTF.

Nous avons essayé d’appeler pour nous plaindre que les chocolats que nous venions d’acheter étaient rassis. Ce que nous avons entendu, ce sont plutôt des tonalités de modem téléphonique, qui pouvaient correspondre à des données transmises sur la ligne. Ces données pouvaient être décodées pour révéler un drapeau.
Nous avons enregistré l’appel. Au départ, nous voulions l’enregistrer proprement au moyen de la VoIP et d’outils d’enregistrement, mais nous n’avions pas confiance dans les logiciels douteux disponibles. Nous avons donc enregistré l’appel en mettant un téléphone sur haut-parleur et en tenant un second téléphone à proximité pour l’enregistrement.
Nous avons obtenu un fichier M4A, de l’audio compressé. Nous l’avons converti en format WAV, de l’audio non compressé.
ffmpeg -i GiftShop.m4a -ar 44100 -ac 1 GiftShop.wav
Nous avons utilisé minimodem, un outil qui décode l’audio de modem en texte. Nous lui avons fourni le fichier WAV en le configurant en récepteur. Après quelques essais de débits en bauds, nous avons trouvé le bon, correspondant à un modem Bell 103.
Le drapeau se trouvait dans le dernier bloc CARRIER.

Les trois drapeaux encodés visuellement
- Participant: Christophe St-Georges
Le côté de la distributrice était magnifiquement conçu et portait les trois derniers drapeaux visuels.

Nous avons trouvé un document de référence qui nous a servi pour les trois défis. Nous appelions ce document le Saint Graal des alphabets pour CTF.
Ligne verticale à encoches
C’était l’alphabet ogham, une très ancienne écriture irlandaise. Chaque lettre est encodée par un motif d’encoches de part et d’autre d’une ligne centrale, généralement lu de bas en haut.

La correspondance de chaque symbole nous a donné ce drapeau: FLAGDASHSENDMEAFLAG
Symboles sur la roue
Ils provenaient de l’écriture Moon, dont certains symboles s’inspirent des phases de la lune, ce qui collait parfaitement au thème nautique du CTF!

Le décodage a révélé: FLAG ASTROSEAFARING
Pavillons du sémaphore

C’étaient des pavillons du code sémaphore, encore une fois tout à fait dans le thème de la communication maritime. Chaque pavillon tenu dans une main, combiné à un autre dans l’autre main, désigne une lettre parmi huit positions autour d’un cercle.
