Pico-8 et la stéganographie

En parcourant un article de Scott Hanselman sur la console virtuelle Pico-8 ( https://www.hanselman.com/blog/ThePICO8VirtualFantasyConsoleIsAnIdealizedConstrainedModernDayGameMaker.aspx) , un paragraphe sur la cartouche de jeu à particulièrement attiré mon attention.

One of the many genius parts of the PICO-8 is that the “Cartridges” are actually PNG pictures of cartridges. Drink that in for a second. They save a screenshot of the game while the cart is running, then they hide the actual code in a steganographic process

Pour résumé, le screenshot du jeu est enregistré dans une image en même temps que le code binaire et l’état du jeu. Image et code sont donc mélangés pour ne former qu’une seule image sauvée au format PNG. Mais comment est-ce possible ?

Mixer Image et données

On va simplement utiliser une particularité de l’oeil humain qui a du mal à distinguer les teintes très proches. L’oeil humain est capable de distinguer 200 teintes différentes mais par sur tous les canaux de couleurs (en RGB) soit 8 millions de couleurs maximum pour une personne entraîné. Mais en réalité 300.000 couleurs différentes semble un chiffre plus réaliste (64 teintes par canaux). Pour plus d’informations vous pouvez consultez cet article très intéressant :

https://www.guide-gestion-des-couleurs.com/oeil-perception-couleurs.html

Maintenant que l’on connait les limite de l’oeil humain voyons quelles sont celle de la machine. Une pixel au format ARGB8 est capable d’afficher 16 millions de couleurs soit 256 teintes par canal (1 octet). C’est beaucoup plus que l’oeil humain n’est capable de percevoir.

Si l’on reprend les 64 teintes par canaux perçues par l’oeil humain cela correspond à 6 bits sur un octet. Il reste donc 2 bits sur chaque canal dont on peut se servir pour encoder des données. Bien sur on utilisera les 2 bits les plus bas (bit 1-0) pour ne pas que les données puissent influencer de manière significatives les teintes des pixels.

Les 2 bits les plus bas des canaux sont utilisés pour former une donnée d’un octet

En associant les canaux RGB et Alpha (A) (et oui cela fonctionne également sur l’opacité !) on dispose de 4 canaux dont les 2 bits peuvent former les parties d’un octet de donnée (4 * 2 bits = 8 bits = 1 octet). On peut donc considérer qu’un octet égale à une pixel.

Les images de cartouches de Pico-8 ont une dimension de 160×205 pixels. C’est à dire 32800 pixels soit 32K de données (code ou ressource). Impressionnant !

Les images sont au format PNG car il donne la possibilité de sauver des images de 16 millions de couleurs, compressés sans perte de données. C’est essentiel pour ne pas perdre les données mélangées à l’image. En revanche, le format JPG n’est pas adapté à notre usage car son format de compression, très performant, autorise, en contre partie, une perte de données sur l’image.

La cartouche Pico-8 du jeu “FEZ”
En zoomant la cartouche on peut voir des teintes légèrement différentes dans les couleurs (bleu notamment) signe de données cachés.

Le processus permettant de cacher des données dans un autre flux de données s’appelle la Stéganographie. Nous avons évoqué ici seulement la possibilité de cacher ses données dans une image mais potentiellement il est possible de le faire avec du son ou d’autres types de flux.

Algorithme simple de stéganographie

Si vous souhaitez coder votre propre intégration d’une stéganographie à la Pico-8 cela passera nécessairement par une bonne connaissance du binaire et de ses opérateurs.

Concrètement, on récupère les pixels d’une image auxquelles on applique un masque sur chaque canal pour que les deux derniers bits passent à zéro. On récupère ensuite 2 bits de données que l’on fusionnera avec le canal en cours. Pour un canal cela donne un code de ce type :

byte data = 0xFF; // la data à écrire

byte B6bits = B8bits & ~0x03; // on nettoie les 2 derniers bits du canal B (blue)
byte data2bits = data & 0x03; // on conserve les deux derniers bits de data
byte RData8bits = R6bits | data2bits;

data = data >> 2; // on prépare la data pour le prochain canal puis on passe au canal G (Green)

Librairie de stéganographie

Heureusement si vous n’êtes pas à l’aise avec ce type de développement bas-niveau vous pouvez utilisez des librairies déjà toutes faites.

Par exemple en C# : https://github.com/paw3lx/StegoCore qui vous permet d’écrire du code de ce type :

byte[] secretData = System.IO.File.ReadAllBytes("secret.data");
using(var stego = new Stego("someimage.jpg"))
{
    stego.SetSecretData(fileBytes);
    Image<Rgba32> secretImage = stego.Embed(AlgorithmEnum.Lsb);
}

Facile et intuitif !

Référence

Plus d’information sur le format de la cartouche Pico-8 ?

https://pico-8.fandom.com/wiki/P8PNGFileFormat

Une liste de cartouche PICO-8 jouables :

http://picoscope101.fr/