Визуальный код (Visual Code)
Визуальный код (Visual Code)
Platform: hackerlab | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-06 | Status: Solved Techniques: frame_extraction, gif_header_analysis, multi_part_flag, qr_decoding
Summary
Task: corrupted GIF file that does not open. Solution: repair GIF header (byte 5), extract frames from animated GIF, scan QR codes from each frame, assemble multi-part flag.
Визуальный код (Visual Code) — HackerLab
Description
Вам кажется, что это сигнал? Может какой-то код? Вы правы.
(You think this is a signal? Maybe some code? You're right.)
Files: visual_code.zip containing file "Visual Code"
Analysis
1. Initial File Analysis
After extracting the archive, we get a file without extension. Check its type:
bash read-only • copy allowed$ file "Visual Code" Visual Code: data
The file is not recognized. Let's look at the header:
bash read-only • copy allowed$ xxd "Visual Code" | head -3 00000000: 4749 4638 3916 0800 0800 f700 00ff ffff GIF89........... 00000010: 0000 001a 1a1a e2e2 e201 0101 0202 0203 ................ 00000020: 0303 0404 0405 0505 0606 0608 0808 0909 ................
We see GIF89 — this is a GIF file, but the header is corrupted!
2. Corruption Analysis
Standard GIF header: GIF89a (6 bytes: 47 49 46 38 39 61)
In our file: GIF89\x16 — the 6th byte is 0x16 instead of 0x61 ('a')
Position: 0 1 2 3 4 5
Expected: 47 49 46 38 39 61 (GIF89a)
Actual: 47 49 46 38 39 16 (GIF89.)
3. Header Repair
python read-only • copy allowedwith open('visual_code.gif', 'rb') as f: data = bytearray(f.read()) # Fix the 6th byte (index 5) data[5] = 0x61 # 'a' with open('visual_code_fixed.gif', 'wb') as f: f.write(data)
After fixing:
bash read-only • copy allowed$ file visual_code_fixed.gif visual_code_fixed.gif: GIF image data, version 89a, 229 x 229
4. GIF Analysis
bash read-only • copy allowed$ identify visual_code_fixed.gif visual_code_fixed.gif[0] GIF 229x229 229x229+0+0 8-bit sRGB 256c 22399B 0.000u 0:00.000 visual_code_fixed.gif[1] GIF 229x229 229x229+0+0 8-bit sRGB 256c 22399B 0.000u 0:00.000 ... visual_code_fixed.gif[7] GIF 229x229 229x229+0+0 8-bit sRGB 256c 22399B 0.000u 0:00.000
Animated GIF with 8 frames (229x229 pixels).
Solution
Step 1: Frame Extraction
python read-only • copy allowedfrom PIL import Image gif = Image.open('visual_code_fixed.gif') frame_num = 0 while True: try: gif.seek(frame_num) # Convert to RGB for full display frame = gif.convert('RGB') frame.save(f'frame_{frame_num}.png') frame_num += 1 except EOFError: break print(f"Extracted {frame_num} frames")
Or using ImageMagick:
bash read-only • copy allowedmagick visual_code_fixed.gif -coalesce frame_%d.png
Step 2: Scanning QR Codes
bash read-only • copy allowedfor i in {0..7}; do echo "Frame $i:" zbarimg -q frame_$i.png 2>/dev/null || echo " (no QR code)" done
Result:
Frame 0: (empty white frame)
Frame 1: QR-Code:CODEBY{
Frame 2: QR-Code:b1p_
Frame 3: QR-Code:bip_
Frame 4: QR-Code:1ts
Frame 5: QR-Code:_qr
Frame 6: QR-Code:_c0d3
Frame 7: QR-Code:_l000l}
Step 3: Assembling the Flag
python read-only • copy allowedimport subprocess flag_parts = [] for i in range(8): result = subprocess.run( ['zbarimg', '-q', f'frame_{i}.png'], capture_output=True, text=True ) if result.stdout.strip(): # Format: "QR-Code:DATA" data = result.stdout.strip().split(':', 1)[1] flag_parts.append(data) flag = ''.join(flag_parts) print(f"Flag: {flag}")
Complete Solution Script
python read-only • copy allowed#!/usr/bin/env python3 """ Visual Code - HackerLab Forensics Solution: fix GIF header + extract QR codes from frames """ from PIL import Image import subprocess import os # 1. Fix the GIF header with open('visual_code.gif', 'rb') as f: data = bytearray(f.read()) # GIF89a - 6th byte should be 0x61 ('a') if data[5] != 0x61: print(f"[*] Corrupted header: byte 5 = 0x{data[5]:02x}, fixing to 0x61") data[5] = 0x61 with open('visual_code_fixed.gif', 'wb') as f: f.write(data) # 2. Extract frames gif = Image.open('visual_code_fixed.gif') frames = [] frame_num = 0 while True: try: gif.seek(frame_num) frame = gif.convert('RGB') filename = f'frame_{frame_num}.png' frame.save(filename) frames.append(filename) frame_num += 1 except EOFError: break print(f"[*] Extracted {frame_num} frames") # 3. Scan QR codes flag_parts = [] for i, frame_file in enumerate(frames): result = subprocess.run( ['zbarimg', '-q', frame_file], capture_output=True, text=True ) output = result.stdout.strip() if output: # QR-Code:DATA data = output.split(':', 1)[1] if ':' in output else output flag_parts.append(data) print(f" Frame {i}: {data}") else: print(f" Frame {i}: (empty)") # 4. Assemble the flag flag = ''.join(flag_parts) print(f"\n[+] Flag: {flag}")
GIF Header Structure
Offset Size Description
0 3 Signature: "GIF"
3 3 Version: "87a" or "89a"
6 2 Logical Screen Width
8 2 Logical Screen Height
10 1 Packed Fields
11 1 Background Color Index
12 1 Pixel Aspect Ratio
Corrupting the version byte (position 5) is a common CTF technique for disguising GIF files.