Deep Filter

Flag: HACKDAY{S0_MuCh_D4T4_!N_PnG}

Challenge

file-image
8KB
circle-info

Description


Yo, listen up. We've got a fresh drop from a former Tecktonik Krew member. He was apparently also a low-level data courier for a shadow organization, and he's vanished. All we've got is this strangly compressed PNG, his last known upload.

Flag Format: HACKDAY{flag}

Solution

Pour optimiser la compression, les PNG utilisent un filtre pour chaque ligne de pixel. Ces filtre font 1 octet et vont du type 0 à 4.

Ici, les filtres de l'image sont tous 1 ou 2, ce qui évoque du binaire. Il faut donc récupérer les filtres pour les représenter sous forme de bits et les décoder en ASCII.

Pour ça, on va devoir lire le fichier à la main et parser les chunks pour décompresser les IDAT. Ensuite, on lit le premier octet de chaque ligne pour avoir la valeur du filtre.

import struct
import zlib
from PIL import Image
from bitarray import bitarray

path = "logo.png"

img = Image.open(path)          # Pour récupérer les infos du PNG
png = open(path, "rb").read()   # Pour lire le PNG en brut

# Parse des chunks
i = 8
idat = b""
while i < len(png):
    length = struct.unpack(">I", png[i:i+4])[0]
    ctype = png[i+4:i+8]
    data  = png[i+8:i+8+length]
    i += 12 + length
    if ctype == b"IDAT":
        idat += data
    if ctype == b"IEND":
        break
raw = zlib.decompress(idat)

# Octets par ligne = pixels * canaux (RGB) + 1 (filtre)
rowlen = 1 + img.width * len(img.getbands())
# Le premier octet est le filtre
filters = [raw[r*rowlen] for r in range(img.height)]

# On map: 1 -> 0 et 2 -> 1
bits = [(0 if f == 1 else 1) for f in filters]

# Transformation bits -> bytes
print(bitarray(bits).tobytes().strip().decode())

Mis à jour