← Back to Writeups
CUSTOMEasyMisc

Закодированный мопс (Encoded Pug)

XESXOR9/2/20264 min read
#misc#custom#easy#image-steganography-via-lsb-xo#multi-layer-encoding-chain-(ba#png-file-structure-analysis-(i#visual-steganography-(flag-vis#linux

Закодированный мопс (Encoded Pug)

Platform: hackerlab | Category: Misc | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2025-12-24 | Status: Solved Techniques: Image steganography via LSB XOR difference, Multi-layer encoding chain (Base64 → Binary → ASCII → Octal → ZIP → HEX → PNG), PNG file structure analysis (IEND chunk trailer detection), Visual steganography (flag visible in image difference)

Summary

Task: Decode a 111MB file with multi-layer encoding to find a hidden flag. Solution: Sequential decoding (Base64 → Binary → Octal → ZIP → HEX → PNG), then extract hidden PNG after IEND chunk and XOR both images to reveal LSB-hidden flag.

Закодированный мопс (Encoded Pug) — HackerLab

Description

Сможете ли вы получить мопса и флаг из этого файла на 110 метров? Зависит от вас ;)

(Can you get the pug and flag from this 110-meter file? It depends on you ;))

File: task_pug.zip → task_pug.dat (111MB ASCII)

Analysis

This task is a classic multi-layer encoding challenge. The name "Encoded Pug" directly indicates multi-layer encoding. The hint "110 meters" refers to the file size (111MB), and "depends on you" suggests the need for sequential decoding. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Step 1: Base64 → Binary strings

bash
# Extract the archive
unzip task_pug.zip
# Get task_pug.dat (111MB ASCII file)

# Check the contents
head -c 1000 task_pug.dat
# See Base64-encoded data
python
import base64

with open('task_pug.dat', 'r') as f:
    data = f.read()

# Decode Base64
decoded = base64.b64decode(data)
print(decoded[:200])
# See binary strings: 00110001 00110010 00110000 ...

Step 2: Binary → ASCII (decimal numbers)

python
# Split into 8-bit groups and convert to characters
binary_str = decoded.decode('ascii')
binary_groups = binary_str.split()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

ascii_chars = ''.join(chr(int(b, 2)) for b in binary_groups)
print(ascii_chars[:100])
# See numbers: 120 113 3 4 24 0 0 0 ...

Step 3: ASCII numbers → Octal → Bytes

python
# Numbers turn out to be octal values
numbers = ascii_chars.split()
bytes_data = bytes(int(n, 8) for n in numbers)

# Check the signature
print(bytes_data[:4])
# b'PK\x03\x04' - this is a ZIP archive!

Step 4: ZIP → file.txt (HEX data)

python
# Save as ZIP
with open('extracted.zip', 'wb') as f:
    f.write(bytes_data)

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bash
unzip extracted.zip
# Get file.txt with hex data

Step 5: HEX → PNG image

python
with open('file.txt', 'r') as f:
    hex_data = f.read().strip()

png_data = bytes.fromhex(hex_data)

with open('pug.png', 'wb') as f:
    f.write(png_data)

Step 6: Detecting hidden PNG

bash
exiftool pug.png
# Warning: Trailer data after PNG IEND chunk
# There's data after the PNG end!

Step 7: Extracting both PNGs

python
# PNG signature and IEND chunk
PNG_SIGNATURE = b'\x89PNG\r\n\x1a\n'
IEND_CHUNK = b'IEND\xaeB`\x82'
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

with open('pug.png', 'rb') as f:
    data = f.read()

# Find all PNG images
images = []
start = 0
while True:
    sig_pos = data.find(PNG_SIGNATURE, start)
    if sig_pos == -1:
        break
    
    end_pos = data.find(IEND_CHUNK, sig_pos)
    if end_pos == -1:
        break
    
    end_pos += len(IEND_CHUNK)
    images.append(data[sig_pos:end_pos])
    start = end_pos

# Save both images
for i, img in enumerate(images):
    with open(f'extracted_{i}.png', 'wb') as f:
        f.write(img)

print(f"Extracted {len(images)} images")
# Extracted 2 images

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 8: XOR two images to reveal the flag

python
from PIL import Image
import numpy as np

# Load both images
img1 = np.array(Image.open('extracted_0.png'))
img2 = np.array(Image.open('extracted_1.png'))

# XOR images
diff = np.bitwise_xor(img1, img2)

# Enhance differences for visualization
# LSB differences will be very weak (values 0 or 1)
diff_enhanced = (diff > 0).astype(np.uint8) * 255

# Save the result
Image.fromarray(diff_enhanced).save('flag_revealed.png')

Result

XOR of two images reveals the flag text hidden in LSB (Least Significant Bit) differences between the images: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

CODEBY{REDACTED}

Full Solution Script

python
#!/usr/bin/env python3
"""
Encoded Pug - Multi-layer decoding challenge
"""

import base64
import zipfile
import io
from PIL import Image
import numpy as np

def solve():
    # Step 1: Read and decode Base64
    with open('task_pug.dat', 'r') as f:
        data = f.read()
    decoded = base64.b64decode(data)
    
    # Step 2: Binary strings to ASCII
    binary_str = decoded.decode('ascii')
    binary_groups = binary_str.split()
    ascii_chars = ''.join(chr(int(b, 2)) for b in binary_groups)
    
    # Step 3: ASCII (octal) to bytes
    numbers = ascii_chars.split()
    zip_data = bytes(int(n, 8) for n in numbers)
    
    # Step 4: Extract ZIP
    with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
        hex_data = zf.read('file.txt').decode('ascii').strip()
    
    # Step 5: HEX to PNG
    png_data = bytes.fromhex(hex_data)
    
    # Step 6-7: Extract both PNGs
    PNG_SIG = b'\x89PNG\r\n\x1a\n'
    IEND = b'IEND\xaeB`\x82'
    
    images = []
    start = 0
    while True:
        sig = png_data.find(PNG_SIG, start)
        if sig == -1:
            break
        end = png_data.find(IEND, sig) + len(IEND)
        images.append(Image.open(io.BytesIO(png_data[sig:end])))
        start = end
    
    # Step 8: XOR images
    arr1 = np.array(images[0])
    arr2 = np.array(images[1])
    diff = np.bitwise_xor(arr1, arr2)
    
    # Enhance LSB differences
    result = (diff > 0).astype(np.uint8) * 255
    Image.fromarray(result).save('flag.png')
    print("Flag saved to flag.png")
    print("Flag: CODEBY{REDACTED}")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

if __name__ == '__main__':
    solve()

Decoding Chain

task_pug.dat (111MB Base64)
    ↓ base64 decode
Binary strings (00110001 00110010...)
    ↓ binary to ASCII
Decimal numbers (120 113 3 4...)
    ↓ interpret as octal
ZIP archive (PK header)
    ↓ unzip
file.txt (HEX data)
    ↓ hex decode
PNG with hidden PNG after IEND
    ↓ extract both PNGs
Two identical pug images
    ↓ XOR / LSB difference
Flag revealed: CODEBY{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍