Неопознанный объект
Неопознанный объект
Platform: hackerlab | Category: Misc | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2025-12-26 | Status: Solved Techniques: Custom script for texture extraction, File format identification via magic bytes, Visual inspection of extracted images, WAD3 texture format parsing
Summary
Task: Unknown file found on desktop. Solution: Identified WAD3 format (Half-Life/Counter-Strike textures) via magic bytes, wrote Python script to extract textures, found flag on bcontainer2.png texture.
Неопознанный объект — hackerlab
Description
Я нашел этот файл у себя на рабочем столе, но не имею понятия, что он из себя представляет. Поможешь мне узнать это?
Files: task.zip (contains file "file")
Analysis
Step 1: File Format Identification
bash read-only • copy allowed$ file file file: data
The file command did not recognize the format. Let's look at the hex dump:
bash read-only • copy allowed$ xxd file | head -5 00000000: 5741 4433 1600 0000 c0e3 0200 a0e2 0200 WAD3............ 00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
WAD3 is a texture format used in games on the GoldSrc engine (Half-Life, Counter-Strike 1.6).
Step 2: WAD3 Format Structure
A WAD3 file consists of:
-
Header (12 bytes):
- Magic: "WAD3" (4 bytes)
- Number of textures (4 bytes)
- Offset to texture table (4 bytes)
-
Directory entries (32 bytes each):
- Data offset (4 bytes)
- Size in file (4 bytes)
- Size in memory (4 bytes)
- Type (1 byte)
- Compression (1 byte)
- Padding (2 bytes)
- Texture name (16 bytes)
-
Texture data (miptex format):
- Name (16 bytes)
- Width, height (4 bytes each)
- Offsets to 4 mipmap levels (4 bytes each)
- Pixel data (palette indices)
- Palette (256 * 3 = 768 bytes RGB)
Solution
Python Script for Texture Extraction
python read-only • copy allowed#!/usr/bin/env python3 """WAD3 texture extractor for Half-Life/Counter-Strike""" import struct from PIL import Image import os def extract_wad3(wad_path, output_dir="textures"): os.makedirs(output_dir, exist_ok=True) with open(wad_path, 'rb') as f: # Read header magic = f.read(4) if magic != b'WAD3': print(f"Not a WAD3 file: {magic}") return num_textures = struct.unpack('<I', f.read(4))[0] dir_offset = struct.unpack('<I', f.read(4))[0] print(f"WAD3 file with {num_textures} textures") # Read directory f.seek(dir_offset) entries = [] for i in range(num_textures): offset = struct.unpack('<I', f.read(4))[0] disk_size = struct.unpack('<I', f.read(4))[0] mem_size = struct.unpack('<I', f.read(4))[0] tex_type = struct.unpack('<B', f.read(1))[0] compression = struct.unpack('<B', f.read(1))[0] f.read(2) # padding name = f.read(16).rstrip(b'\x00').decode('ascii', errors='ignore') entries.append({ 'name': name, 'offset': offset, 'size': disk_size, 'type': tex_type }) # Extract each texture for entry in entries: f.seek(entry['offset']) # Read miptex header tex_name = f.read(16).rstrip(b'\x00').decode('ascii', errors='ignore') width = struct.unpack('<I', f.read(4))[0] height = struct.unpack('<I', f.read(4))[0] # Mipmap offsets (relative to texture start) mip_offsets = struct.unpack('<4I', f.read(16)) # Read mip0 (full resolution) pixel data f.seek(entry['offset'] + mip_offsets[0]) pixel_data = f.read(width * height) # Calculate palette offset (after all 4 mipmaps + 2 bytes padding) mip_sizes = [ width * height, (width // 2) * (height // 2), (width // 4) * (height // 4), (width // 8) * (height // 8) ] palette_offset = entry['offset'] + mip_offsets[0] + sum(mip_sizes) + 2 # Read palette (256 RGB colors) f.seek(palette_offset) palette_data = f.read(768) if len(palette_data) < 768: print(f" Skipping {entry['name']}: incomplete palette") continue # Build palette palette = [] for i in range(256): r = palette_data[i * 3] g = palette_data[i * 3 + 1] b = palette_data[i * 3 + 2] palette.append((r, g, b)) # Create image img = Image.new('RGB', (width, height)) pixels = img.load() for y in range(height): for x in range(width): idx = pixel_data[y * width + x] pixels[x, y] = palette[idx] # Save output_path = os.path.join(output_dir, f"{entry['name']}.png") img.save(output_path) print(f" Extracted: {entry['name']} ({width}x{height})") if __name__ == "__main__": extract_wad3("file", "extracted_textures")
Extraction Result
WAD3 file with 22 textures
Extracted: c_bldg1 (128x128)
Extracted: c_bldg2 (128x128)
Extracted: c_bldg3 (128x128)
Extracted: c_viewscreen1 (256x256)
Extracted: c_viewscreen2 (256x256)
Extracted: bcontainer1 (256x256)
Extracted: bcontainer2 (256x256) <-- FLAG HERE
Extracted: bcontainer3 (256x256)
... (and other textures)
Step 3: Visual Inspection
When viewing the extracted textures, the flag was found on bcontainer2.png:
The texture shows a blue container with the text CODEBY{REDACTED}
WAD3 Reference
| Field | Size | Description |
|---|---|---|
| Magic | 4 | "WAD3" |
| NumTextures | 4 | Number of textures |
| DirOffset | 4 | Table offset |
| Directory | 32*N | Texture entries |
| Textures | var | Miptex data |
The palette is always located after 4 mipmap levels + 2 bytes padding.