По крупицам (Bit by Bit)
По крупицам (Bit by Bit)
Platform: hackerlab | Category: Misc | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2025-12-25 | Status: Solved Techniques: empty_file_encoding, multiple_zip_parsing, png_trailer_extraction, qr_code_generation
Summary
Task: PNG file with hidden data after IEND chunk containing 7744 concatenated ZIP archives. Solution: Extract trailer data, parse ZIPs, interpret empty files as white pixels and non-empty as black to reconstruct a QR code.
По крупицам — hackerlab
Description
Освободите память. Похоже, тут очень много всего
File: banner.png
Analysis
1. Discovering hidden data
bash read-only • copy allowedexiftool banner.png # Warning: [minor] Trailer data after PNG IEND chunk
The PNG file contains data after the end-of-file marker (IEND chunk).
2. Extracting trailer data
python read-only • copy allowedwith open('banner.png', 'rb') as f: data = f.read() # PNG IEND chunk: 00 00 00 00 49 45 4E 44 AE 42 60 82 iend_marker = b'\x00\x00\x00\x00IEND\xae\x42\x60\x82' iend_pos = data.find(iend_marker) hidden_data = data[iend_pos + len(iend_marker):] # ~1.3MB of hidden data
3. Analyzing hidden data structure
The data contains 7744 small ZIP archives concatenated together. Each ZIP contains one file:
1.txt,2.txt, ...,7744.txt- Content: a single ASCII-art character (
@,#,%,=,-,.) or an empty file
4. Key discovery
Hint in the description: "Free memory" (Освободите память)
- Empty file = white pixel (free memory)
- Non-empty file = black pixel (occupied memory)
Width is determined by positions of \n characters — they appear every 123 files.
Solution
python read-only • copy allowed#!/usr/bin/env python3 import zipfile import io from PIL import Image from pyzbar.pyzbar import decode # Read PNG and extract trailer data with open('banner.png', 'rb') as f: data = f.read() iend_marker = b'\x00\x00\x00\x00IEND\xae\x42\x60\x82' iend_pos = data.find(iend_marker) hidden_data = data[iend_pos + len(iend_marker):] # Parse all ZIP archives extracted = {} offset = 0 while offset < len(hidden_data): # Look for ZIP start (PK\x03\x04) if hidden_data[offset:offset+4] != b'PK\x03\x04': offset += 1 continue # Look for ZIP end (PK\x05\x06 + 18 bytes) end_marker = hidden_data.find(b'PK\x05\x06', offset) if end_marker == -1: break zip_end = end_marker + 22 # End of central directory record zip_data = hidden_data[offset:zip_end] try: with zipfile.ZipFile(io.BytesIO(zip_data)) as zf: for name in zf.namelist(): num = int(name.replace('.txt', '')) content = zf.read(name).decode('utf-8', errors='ignore') extracted[num] = content except: pass offset = zip_end # Create QR code: empty file = white, non-empty = black width = 123 height = (len(extracted) + width - 1) // width img = Image.new('1', (width, height), 1) # White background pixels = img.load() for num in range(1, 7745): content = extracted.get(num, '') if content and content.strip(): # Non-empty file = black pixel x = (num - 1) % width y = (num - 1) // width pixels[x, y] = 0 # Decode QR code decoded = decode(img) if decoded: print(f"Flag: {decoded[0].data.decode()}") else: # Save for manual inspection img.save('qr_result.png')
Result
Flag: CODEBY{REDACTED}
Alternative Approaches
- ASCII-art: Characters
@#%=-.\nform a RAM module image, but this is a red herring - Different sizes: Try different widths (88, 121, 123, 242...) to find the pattern
- Inversion: If QR doesn't scan, try inverting colors