BreizhCTF 2025

WriteUps de challenges du BreizhCTF 2025.

 March 3, 2025 -  4 min read

Challenge details

Event Challenge Category Points Solves
BreizhCTF 2025 Ishihara MISC 369 44

colorblind

Ce serait tellement plus facile si je n’étais pas daltonien !

Auteur : Zeecka

TL;DR

Le challenge est un Test d’Ishihara inversé. L’ajout de filtres de simulation de daltonisme, ou le remapping des couleurs laisse apparaitre les lettres composant le flag.

Méthodologie

Le contenu de l’archive présente un ensemble d’images semblables aux tests de daltonismes (en particulier les tests chromatiques d’Ishihara).

archive_content

Il est cepend très difficile d’identifier un quelconque chiffre parmis les différentes planches.

L’application d’un filtre de simulation de daltonisme de type Protanopie et Deuteranopie permet de mettre en évidence la présence de formes distinctes semblables à des lettres.

Note : Les personnes daltoniennes sont légèrement avantagées dans la découverte de ces formes.

3B

Exemple du filtre lettre “B” (première lettre du format de flag).

Méthode 0 - Super Héro

Cette méthode consiste à trouver un volontaire dans son équipe, de péférence daltonien, pour identifier les lettres de chaque images sans aucune transformation 🤡. Cette personne devra alors passer sa nuit à torturer ses yeux pour la gloire de son équipe.

superman

Méthode 1 - Random colors remapping

L’utilisation du célèbre logiciel StegSolve permet de remapper les couleurs d’une image de manière aléatoire à l’aide de sa vue “Random colour map”.

random_color_map

random_color_map2

Exemple du filtre lettre “B” (première lettre du format de flag).

Compte tenu du caractère aléatoire du remapping, la répétition des tentatives de mapping permet d’aboutir a des lettres facilement perceptibles.

Une fois l’ensemble des lettres décodées, on récupère le flag : BZHCTF{1shih4rA_?never_he4rd_ab0ut!}

Méthode 2 - Custom colors remapping

La méthode la plus adaptée pour résoudre le challenge consiste à identifier les couleurs composant les lettres, et celles composant le fond de planche.

Pour cela, deux solutions. La première repose sur l’utilisation de l’outil pipette (disponible sur paint, navigateur, …) pour récupérer les différents code couleurs.

La deuxième solution consiste à parser les images pixel par pixel, et ajouter chaque nouvelle couleur à une liste. Les couleurs découvertes en premiers correspondent à celles utilisées pour le fond de plance, tandisque les dernières correspondent aux lettres.

On utilisera pour cela le langage python et sa bibliothèque Pillow.

#!/usr/bin/env python3

from PIL import Image
import zipfile

def main():
    archive = zipfile.ZipFile('../dist/Ishihara.zip', 'r')
    flag_size = len(archive.namelist())  # Nombre de lettres

    new_colors = []
    zip_img = archive.open(f"0.png")  # On récupère la première image
    img = Image.open(zip_img)
    img_data = img.getdata()  # On lit ses pixels
    for color in img_data:
        if color not in new_colors:
            new_colors.append(color)
            print(color)  # On affiche les couleurs dans l'ordre de découverte

if __name__ == "__main__":
    main()
(255, 255, 255)
(190, 185, 103)
(232, 14, 38)
(216, 159, 104)
(77, 188, 119)
(252, 138, 39)
(159, 169, 44)
(29, 163, 48)
(167, 127, 65)
(219, 113, 29)
(154, 50, 15)
(228, 177, 150)
(233, 127, 103)
(104, 188, 64)
(118, 98, 87)
(159, 170, 154)
(149, 195, 131)

On identifie ainsi les 11 premières couleurs comme étant les couleurs de fond, et les 6 dernières celles composant les lettres.

On procède ensuite au remapping des couleurs, toujours à l’aide d’un script python et de la bibliothèque Pillow:

#!/usr/bin/env python3

from PIL import Image
import zipfile

# On identifie toutes les couleurs qui sont "autour" des lettres.
BAD_COLORS = [
    (255, 255, 255),
    (190, 185, 103),
    (232, 14, 38),
    (216, 159, 104),
    (77, 188, 119),
    (252, 138, 39),
    (159, 169, 44),
    (29, 163, 48),
    (167, 127, 65),
    (219, 113, 29),
    (154, 50, 15)
]

def main():
    archive = zipfile.ZipFile('../dist/Ishihara.zip', 'r')
    flag_size = len(archive.namelist())  # Nombre de lettres
    flag_image = Image.new('1', (800 * flag_size, 800))  # Image vierge de la taile du flag

    for i in range(flag_size):  # Pour chaque image de l'archive
        zip_img = archive.open(f"{i}.png")
        img = Image.open(zip_img)
        img_data = img.getdata()  # On lit ses pixels
        filtered_image = Image.new('1', img.size)
        filtered_data = []  # On créer son équivalent "filtré"
        for color in img_data:  # On parcour chaque pixels et on ajoute un pixel
            if color not in BAD_COLORS: # noir ou blanc en fonction des couleurs
                filtered_data.append(0)
            else:
                filtered_data.append(1)
        filtered_image.putdata(filtered_data)
        flag_image.paste(filtered_image, (800*i, 0))  # On ajoute la lettre au flag

    flag_image.save("flag.png")
    flag_image.show()

if __name__ == "__main__":
    main()

Le résultat est le suivant :

flag.png

Flag

BZHCTF{1shih4rA_?never_he4rd_ab0ut!}

Notes de l’auteur

La création du challenges est basée sur les références suivantes: