128 кассир (128 Cashier)
128 кассир (128 Cashier)
Platform: hackerlab | Category: Forensics | Type: Challenge | Difficulty: Medium | OS: Windows | Author: XESXOR | Date: 2026-01-06 | Status: Solved Techniques: barcode_decoding, bmp_header_analysis, color_inversion, dimension_calculation, rle8_decoding
Summary
Task: BMP file with corrupted header and wrong dimensions in metadata. Solution: Repair BMP signature, decode RLE8 data, calculate real dimensions from pixel count, invert colors and decode Code 128 barcode.
128 кассир (128 Cashier) - Hackerlab
Description
Черно-белый или бело-чёрный? Может быть всё наоборот? Хотя какая разница, Вы ведь этого не видите... Позовите кассира!
Files: task.zip (contains task.bmp)
Analysis
Step 1: Initial Inspection
bash read-only • copy allowedunzip task.zip file task.bmp # task.bmp: data
The file is identified as "data" instead of BMP - the header is corrupted.
Step 2: Hex Dump Analysis
bash read-only • copy allowedxxd task.bmp | head -5 # 00000000: 004d 3e00 0000 0000 0000 3604 0000 2800 .M>...........(.
The first byte is 0x00 instead of 0x42 ('B'). BMP files must start with "BM" (0x42 0x4D).
Step 3: Header Repair
bash read-only • copy allowedprintf '\x42' | dd of=task.bmp bs=1 count=1 conv=notrunc file task.bmp # task.bmp: PC bitmap, Windows 3.x format, 100 x 20 x 8, image size 15878, resolution 2835 x 2835 px/m, 256 important colors, RLE 8-bit
Header restored:
- Size: 100 x 20 pixels
- Depth: 8 bit (256 colors)
- Compression: RLE8 (compression=1)
Step 4: Dimension Problem
When opening the image, we only see a black rectangle 100x20. But the RLE8 data decodes to a much larger volume!
Step 5: RLE8 Decoding and Real Dimension Calculation
python read-only • copy allowed# RLE8 decoding yields 247040 pixels # 100 x 20 = 2000 pixels - doesn't match! # Factorization of 247040: # 247040 = 1280 × 193
Real image dimensions: 1280 x 193 pixels.
Solution
python read-only • copy allowed#!/usr/bin/env python3 """ 128 кассир (128 Cashier) - BMP Header Repair + RLE8 Decode + Code128 Barcode """ from PIL import Image from pyzbar.pyzbar import decode import struct def decode_rle8(data): """Decode RLE8 compressed data""" pixels = [] i = 0 while i < len(data): count = data[i] value = data[i + 1] i += 2 if count == 0: if value == 0: # End of line continue elif value == 1: # End of bitmap break elif value == 2: # Delta i += 2 # Skip delta bytes else: # Absolute mode pixels.extend(data[i:i + value]) i += value if value % 2: # Padding i += 1 else: pixels.extend([value] * count) return pixels # Read BMP file with open('task.bmp', 'rb') as f: bmp_data = f.read() # Fix header if needed (first byte should be 0x42 'B') if bmp_data[0] == 0x00: bmp_data = b'\x42' + bmp_data[1:] with open('task_fixed.bmp', 'wb') as f: f.write(bmp_data) # Parse BMP header data_offset = struct.unpack('<I', bmp_data[10:14])[0] width = struct.unpack('<i', bmp_data[18:22])[0] height = struct.unpack('<i', bmp_data[22:26])[0] compression = struct.unpack('<I', bmp_data[30:34])[0] print(f"Header dimensions: {width}x{height}") print(f"Compression: {compression} (1=RLE8)") print(f"Data offset: {data_offset}") # Extract palette (256 colors, 4 bytes each) palette = [] for i in range(256): offset = 54 + i * 4 b, g, r, _ = bmp_data[offset:offset + 4] palette.append((r, g, b)) # Decode RLE8 data rle_data = bmp_data[data_offset:] pixels = decode_rle8(rle_data) print(f"Decoded pixels: {len(pixels)}") # Calculate real dimensions # 247040 = 1280 * 193 real_width = 1280 real_height = len(pixels) // real_width print(f"Real dimensions: {real_width}x{real_height}") # Create image with correct dimensions img = Image.new('RGB', (real_width, real_height)) for y in range(real_height): for x in range(real_width): idx = y * real_width + x if idx < len(pixels): color_idx = pixels[idx] img.putpixel((x, real_height - 1 - y), palette[color_idx]) # BMP is bottom-up img.save('barcode.png') # Invert colors (as hinted in description) from PIL import ImageOps img_inverted = ImageOps.invert(img.convert('RGB')) img_inverted.save('barcode_inverted.png') # Decode barcode barcodes = decode(img_inverted) for barcode in barcodes: print(f"Type: {barcode.type}") print(f"Data: {barcode.data.decode()}")
Result
Header dimensions: 100x20
Compression: 1 (RLE8)
Decoded pixels: 247040
Real dimensions: 1280x193
Type: CODE128
Data: CODEBY{REDACTED}
Techniques
| Technique | Description |
|---|---|
| BMP Header Repair | Restoring the "BM" signature (0x42 0x4D) |
| RLE8 Decoding | Manual decoding of Run-Length Encoding |
| Dimension Calculation | Calculating real dimensions from pixel count |
| Color Inversion | Inverting colors for barcode readability |
| Barcode Decoding | Decoding Code 128 using pyzbar |
Useful Commands
bash read-only • copy allowed# Check BMP signature xxd file.bmp | head -1 # Fix first byte printf '\x42' | dd of=file.bmp bs=1 count=1 conv=notrunc # BMP information identify -verbose file.bmp # Decode barcode python3 -c "from pyzbar.pyzbar import decode; from PIL import Image; print(decode(Image.open('barcode.png')))"
References
- BMP File Format
- RLE8 Compression
- Code 128 Barcode
- pyzbar Library