← Back to Writeups
CUSTOMEasyMisc

По крупицам (Bit by Bit)

XESXOR9/2/20263 min read
#misc#custom#easy#empty_file_encoding#multiple_zip_parsing#png_trailer_extraction#qr_code_generation#linux

По крупицам (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
exiftool 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
with 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
#!/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

  1. ASCII-art: Characters @#%=-.\n form a RAM module image, but this is a red herring
  2. Different sizes: Try different widths (88, 121, 123, 242...) to find the pattern
  3. Inversion: If QR doesn't scan, try inverting colors ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍