← Back to Writeups
CUSTOMMediumForensics

Мертвое изображение (Dead Image)

XESXOR9/2/20266 min read
#forensics#custom#medium#bmp-file-header-manual-reconst#image-dimension-calculation-vi#raw-pixel-data-analysis#rgba-pixel-format-identificati#linux

Мертвое изображение (Dead Image)

Platform: hackerlab | Category: Forensics | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-01-05 | Status: Solved Techniques: BMP file header manual reconstruction, Image dimension calculation via factorization, Raw pixel data analysis, RGBA pixel format identification

Summary

Task: Recover an image from raw pixel data after BMP header was lost during PNG to BMP conversion. Solution: Calculate image dimensions via factorization of pixel count, manually reconstruct BMP file header and BITMAPINFOHEADER.

Мертвое изображение (Dead Image) — Hackerlab

Description

Во время конвертации картинки из PNG в BMP затерлась вся структура файла. Поможешь восстановить?

File: task.zip → task

Analysis

1. Initial File Analysis

bash
$ file task
task: ISO-8859 text, with very long lines (65536), with no line terminators

$ ls -la task
-rw-r--r--  1 user  staff  95528572 Jan  5 12:00 task

The file is detected as text, not an image — the header is missing.

2. Hex Analysis

bash
$ xxd task | head -20
00000000: ffff ffff ffff ffff ffff ffff ffff ffff  ................
00000010: ffff ffff ffff ffff ffff ffff ffff ffff  ................
...

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

The file starts with 0xFF bytes — these are white pixels in RGBA format (255, 255, 255, 255).

3. Searching for Image Data

bash
$ xxd task | grep -v "ffff ffff ffff ffff ffff ffff ffff ffff" | head -20

Non-white bytes were found in the middle of the file — this is the actual image content.

4. Determining Pixel Format

The task description mentions PNG → BMP conversion. PNG uses RGBA (4 bytes per pixel), BMP can store 32-bit data (also 4 bytes).

Verification:

  • File size: 95,528,572 bytes
  • Bytes per pixel: 4 (RGBA)
  • Total pixels: 95,528,572 / 4 = 23,882,143 ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

5. Calculating Image Dimensions

Need to factorize the pixel count to determine width × height:

python
import sympy
n = 23882143
factors = sympy.factorint(n)
# {5987: 1, 3989: 1}
# 23,882,143 = 5987 × 3989

Image dimensions: 5987 × 3989 pixels

Solution

Step 1: Creating BMP Header

A BMP file consists of:

  1. BMP File Header (14 bytes)
  2. BITMAPINFOHEADER (40 bytes)
  3. Pixel Data (raw data)
python
#!/usr/bin/env python3
import struct

# Image parameters
width = 5987
height = 3989
bits_per_pixel = 32  # RGBA
pixel_data_size = width * height * 4
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# BMP File Header (14 bytes)
bmp_signature = b'BM'
file_size = 14 + 40 + pixel_data_size  # header + DIB header + pixels
reserved = 0
pixel_data_offset = 14 + 40  # after both headers

bmp_file_header = struct.pack('<2sIHHI',
    bmp_signature,      # 'BM'
    file_size,          # file size
    reserved,           # reserved1
    reserved,           # reserved2
    pixel_data_offset   # offset to pixel data
)

# BITMAPINFOHEADER (40 bytes)
dib_header_size = 40
color_planes = 1
compression = 0  # BI_RGB (no compression)
image_size = 0   # can be 0 for BI_RGB
x_pixels_per_meter = 2835  # 72 DPI
y_pixels_per_meter = 2835
colors_used = 0
important_colors = 0
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

bitmapinfoheader = struct.pack('<IiiHHIIiiII',
    dib_header_size,
    width,
    height,             # positive = bottom-up
    color_planes,
    bits_per_pixel,
    compression,
    image_size,
    x_pixels_per_meter,
    y_pixels_per_meter,
    colors_used,
    important_colors
)

# Read raw pixel data
with open('task', 'rb') as f:
    pixel_data = f.read()

# Assemble BMP file
with open('restored.bmp', 'wb') as f:
    f.write(bmp_file_header)
    f.write(bitmapinfoheader)
    f.write(pixel_data)
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

print(f"Created restored.bmp ({file_size} bytes)")

Step 2: Convert to PNG (optional)

bash
convert restored.bmp restored.png

Step 3: View the Image

Open restored.bmp or restored.png — the flag is visible in the image.

Theory

BMP File Structure

+------------------+
| BMP File Header  |  14 bytes
+------------------+
| DIB Header       |  40+ bytes (BITMAPINFOHEADER)
+------------------+
| Color Table      |  optional (for indexed color)
+------------------+
| Pixel Data       |  raw pixels
+------------------+

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

BMP File Header (14 bytes)

OffsetSizeDescription
0x002Signature ('BM')
0x024File size
0x062Reserved
0x082Reserved
0x0A4Pixel data offset

BITMAPINFOHEADER (40 bytes)

OffsetSizeDescription
0x0E4Header size (40)
0x124Width (signed)
0x164Height (signed, negative = top-down)
0x1A2Color planes (1)
0x1C2Bits per pixel (1/4/8/16/24/32)
0x1E4Compression (0 = BI_RGB)
0x224Image size
0x264X pixels per meter
0x2A4Y pixels per meter
0x2E4Colors used
0x324Important colors
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Pixel Formats

BPPFormatBytes/pixel
24BGR3
32BGRA4

Important: BMP stores pixels in BGR(A) order, not RGB(A)!

Calculating Image Dimensions

With known file size and pixel format:

python
total_pixels = file_size // bytes_per_pixel
# Factorization to find width × height

For CTF tasks, "nice" dimensions or simple factors are typically used.

Full Solution Script

python
#!/usr/bin/env python3
"""
Dead Image - BMP Header Reconstruction
"""
import struct
import sympy
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

def factorize_pixels(n):
    """Find image dimensions from pixel count"""
    factors = sympy.factorint(n)
    # For two prime factors, they are width and height
    primes = list(factors.keys())
    if len(primes) == 2:
        return max(primes), min(primes)  # width, height
    return None, None

def create_bmp_header(width, height, bpp=32):
    """Create BMP file header + BITMAPINFOHEADER"""
    pixel_data_size = width * height * (bpp // 8)
    file_size = 14 + 40 + pixel_data_size
    
    # BMP File Header
    bmp_header = struct.pack('<2sIHHI',
        b'BM', file_size, 0, 0, 54)
    
    # BITMAPINFOHEADER
    dib_header = struct.pack('<IiiHHIIiiII',
        40, width, height, 1, bpp, 0, 0, 2835, 2835, 0, 0)
    
    return bmp_header + dib_header
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

def main():
    # Read raw pixel data
    with open('task', 'rb') as f:
        raw_data = f.read()
    
    # Calculate dimensions
    bytes_per_pixel = 4  # RGBA
    total_pixels = len(raw_data) // bytes_per_pixel
    width, height = factorize_pixels(total_pixels)
    
    print(f"File size: {len(raw_data)} bytes")
    print(f"Total pixels: {total_pixels}")
    print(f"Dimensions: {width} x {height}")
    
    # Create BMP
    header = create_bmp_header(width, height)
    
    with open('restored.bmp', 'wb') as f:
        f.write(header)
        f.write(raw_data)
    
    print("Created restored.bmp")
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

if __name__ == '__main__':
    main()

Alternative Approaches

Using PIL/Pillow

python
from PIL import Image

width, height = 5987, 3989
with open('task', 'rb') as f:
    raw_data = f.read()

# Create image from raw RGBA data
img = Image.frombytes('RGBA', (width, height), raw_data)
img.save('restored.png')

Using ImageMagick

bash
# If dimensions are known
convert -size 5987x3989 -depth 8 rgba:task restored.png

Related Techniques

  • PNG header reconstruction — similar task for PNG
  • JPEG header recovery — recovering JPEG from raw data
  • File carving — extracting files from memory/disk dumps
  • Steganography — hidden data in images ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍