Post

The Sun's Language

Extraire un message caché via des micro-variations du canal rouge entre deux images quasi identiques.

The Sun's Language

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{...}

📦
1.png
PNG • 414 KB
📦
2.png
PNG • 452 KB
Image de référence
Image modifiée

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?}

This post is licensed under CC BY 4.0 by the author.