Stickers (9ff6401f-2c68-4b01-a557-5d01fe1e3954)
Stickers (9ff6401f-2c68-4b01-a557-5d01fe1e3954)
Platform: hackerlab | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2025-12-23 | Status: Solved Techniques: Color channel separation (green channel), Frame-by-frame resolution analysis, GIF header repair (corrupted magic bytes), Hidden canvas area extraction (beyond visible dimensions)
Summary
Task: GIF file with corrupted header (first 6 bytes zeroed) and hidden sticker pack. Solution: Restore GIF89a header, extract frames, discover frames 10-12 have larger dimensions (600x600 vs 512x512), extract flag from green channel in the hidden canvas area below 512px.
Stickers — hackerlab.pro
Description
Недавно мы выпустили свой собственный стикерпак. Я немного изменил то, что прислал мне дизайнер. Пожалуйста, оцените его и не упускайте ничего из виду
File: stickers.zip contains Stickers.gif
Analysis
Step 1: Corrupted GIF Header
The GIF file doesn't open in standard viewers. Checking the header:
bash read-only • copy allowedxxd Stickers.gif | head -1 # 00000000: 0000 0000 0000 0002 0002 f70f 00ff ffff ................
The first 6 bytes are zeroed! Should be GIF89a (47 49 46 38 39 61).
This is the "modification" mentioned in the task description.
Step 2: Header Restoration
bash read-only • copy allowedprintf 'GIF89a' | dd of=fixed.gif bs=1 conv=notrunc dd if=Stickers.gif of=fixed.gif bs=1 skip=6 seek=6
Or via Python:
python read-only • copy allowedwith open('Stickers.gif', 'rb') as f: data = bytearray(f.read()) data[0:6] = b'GIF89a' with open('fixed.gif', 'wb') as f: f.write(data)
Step 3: Frame Analysis
After restoration, extract the frames:
bash read-only • copy allowedconvert fixed.gif -coalesce frames/frame_%03d.png
Analyzing dimensions:
python read-only • copy allowedfrom PIL import Image import glob for f in sorted(glob.glob("frames/frame_*.png")): img = Image.open(f) print(f"{f}: {img.size}")
Key discovery:
- Frames 0-9: 512x512 pixels
- Frames 10-12: 600x600 pixels (larger!)
Step 4: Investigating the Extended Area
Frames 10-12 have an additional 88 pixels in height and width. When viewing the GIF normally, this area is cropped to the first frame's dimensions (512x512).
python read-only • copy allowedfrom PIL import Image def analyze_extra_area(frame_path): img = Image.open(frame_path).convert("RGBA") width, height = img.size # Check the area below 512 pixels bottom_area = [] for y in range(512, height): for x in range(width): pixel = img.getpixel((x, y)) if pixel[3] > 0: # Non-transparent bottom_area.append((x, y, pixel)) return bottom_area # Frame 10 contains data in the bottom area!
Step 5: Extracting the Flag from the Green Channel
The flag is hidden in the green channel of the area from Y=512 to Y=600:
python read-only • copy allowedfrom PIL import Image img = Image.open("frames/frame_010.png").convert("RGBA") # Extract the bottom area (rows 512-600) bottom_img = Image.new("L", (600, 88)) for y in range(512, 600): for x in range(600): pixel = img.getpixel((x, y)) # Use the green channel bottom_img.putpixel((x, y - 512), pixel[1]) bottom_img.save("frame10_bottom_green.png") # Visualize the text for y in range(73, 84): line = "" for x in range(600): p = bottom_img.getpixel((x, y)) if p > 200: line += "#" else: line += "." print(line)
The result shows the flag text in ASCII-art format.
Solution
Complete script:
python read-only • copy allowed#!/usr/bin/env python3 from PIL import Image import os # Step 1: Fix corrupted GIF header with open('Stickers.gif', 'rb') as f: data = bytearray(f.read()) data[0:6] = b'GIF89a' with open('fixed.gif', 'wb') as f: f.write(data) # Step 2: Extract frames using ImageMagick os.system('convert fixed.gif -coalesce frames/frame_%03d.png') # Step 3: Analyze frame 10's extended area img = Image.open("frames/frame_010.png").convert("RGBA") width, height = img.size print(f"Frame 10 size: {width}x{height}") # Step 4: Extract green channel from bottom area bottom_img = Image.new("L", (600, 88)) for y in range(512, 600): for x in range(600): pixel = img.getpixel((x, y)) bottom_img.putpixel((x, y - 512), pixel[1]) bottom_img.save("frame10_bottom_green.png") # Step 5: Visualize the flag print("\nFlag area (rows 73-84):") for y in range(73, 84): line = "" for x in range(600): p = bottom_img.getpixel((x, y)) line += "#" if p > 200 else " " print(line.strip())
Interesting Details
- The flag uses leetspeak:
spl1t(split),g1f(gif),ch4ng3(change),r3s(res) - The flag itself is a hint to the solution: "split gif or change res?" — you needed to split the GIF into frames AND notice the resolution change
- The green channel was chosen because it has the highest sensitivity for the human eye (and is often used for hiding data)
Alternative Approaches
- GIMP — open as layers, check the dimensions of each layer
- ImageMagick identify —
identify -verbose fixed.gifwill show dimensions of all frames - Python imageio —
imageio.mimread()for reading all frames with metadata