The Sun's Language
Extraire un message caché via des micro-variations du canal rouge entre deux images quasi identiques.
Catégorie : Stéganographie
Difficulté : Médium
Flag : CYBN{DoYouL1keTh3rmalSt3gan0?}
Énoncé du challenge
The Sun’s Language
N’avez-vous pas un peu chaud ?
Ces deux images presque identiques cachent un message dans d’infimes variations imperceptibles à l’œil nu.Inspiré de la stéganographie thermique, le procédé joue sur des écarts minimes dans les canaux.
Analysez ces images de manière différentielle et précise pour révéler la séquence dissimulée.
Format du flag :
CYBN{...}


Démarche Stéganographie
Concept
- Image 1 : image de référence (originale)
- Image 2 : image modifiée
- À l’œil nu, les deux images sont identiques
- Le message est caché via des micro-variations du canal rouge (R)
On peut voir cette technique comme :
- une variante du LSB
- combinée à une analyse différentielle
- inspirée du principe de stéganographie thermique
Mécanisme d’encodage
Le message est encodé bit par bit dans les pixels :
- Bit
0→ canal rouge diminué par rapport à l’image de référence - Bit
1→ canal rouge augmenté par rapport à l’image de référence
Les variations sont très faibles :
- ±1 ou ±2 sur une échelle de 0 à 255
- Invisibles pour l’œil humain
- Détectables par script
⚠️ zsteg ne fonctionne pas ici
Il recherche des patterns LSB classiques dans une seule image, alors que ce challenge nécessite la comparaison de deux PNG.
Script de résolution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from PIL import Image
image_ref = Image.open("../dist/1.png")
image_modified = Image.open("../dist/2.png")
finale = ""
# Conversion binaire → bytes
# https://stackoverflow.com/questions/32675679/convert-binary-string-to-bytearray-in-python-3
def bitstring_to_bytes(s):
v = int(s, 2)
b = bytearray()
while v:
b.append(v & 0xff)
v >>= 8
return bytes(b[::-1])
width, height = image_ref.size
for w in range(width):
for h in range(height):
if image_modified.getpixel((w, h))[0] < image_ref.getpixel((w, h))[0]:
finale += '0'
continue
if image_modified.getpixel((w, h))[0] > image_ref.getpixel((w, h))[0]:
finale += '1'
print(finale)
print(bitstring_to_bytes(finale).decode("utf-8"))
Flag
CYBN{DoYouL1keTh3rmalSt3gan0?}
