Странное видео (Strange Video)
Странное видео (Strange Video)
Platform: hackerlab | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-06-03 | Status: Solved Techniques: braille_dot_position_decoding, hidden_url_discovery, morse_code_decoding, mp4_atom_parsing, multi_layer_encoding_chain, video_metadata_extraction, whitespace_binary_encoding
Summary
Task: a ZIP with msg.txt containing a hidden Vimeo URL among empty lines, leading to an MP4 with whitespace data appended after video atoms. Solution: 6-layer decode chain — whitespace binary → ASCII → binary bytes → base64 → Morse code (digits 1-6) → Braille dot positions → flag.
Странное видео (Strange Video) — Hackerlab
Description
Странное видео, это точно. Но жутко интересное! Подсказка 1: Иногда даже с помощью пустоты можно записать текст Подсказка 2: В 1838 году появился знаменитый способ кодирования сообщений - это ...
English summary: A "strange video" challenge with two hints — one about writing text with emptiness (whitespace steganography), and one referencing 1838 (Morse code). The task provides a ZIP file containing msg.txt. The goal is to follow a multi-layer trail of hidden data through decoy Rick Rolls to the actual encoded flag.
Analysis
The challenge is a 6-layer steganography puzzle combining misdirection (Rick Roll decoys), whitespace steganography, Morse code, and Braille encoding.
Layer 0 — Hidden URL in msg.txt: The file is 6881 bytes with 6727 lines. Only 2 lines contain visible text: a "no flag here" message and a YouTube Rick Roll link. Hidden at line 3222 (buried among 6725 empty lines) is the real URL: https://vimeo.com/911593386?share=copy.
Layer 1 — Vimeo video metadata: The Vimeo video (title: "Strange", uploader: "Not a strange person") is also a Rick Roll, but its description contains a Mega download link: https://mega.nz/file/JRYFXaSK#_WuZVG_JdKdR_kmocV3_iZ1B0hhEDuwKwE4OPPZuceE. Extracted using yt-dlp --dump-json.
Layer 2 — Appended data in MP4: The downloaded strange.mp4 (1,304,672 bytes) has MP4 atoms totaling only 1,185,883 bytes (ftyp: 24 + moov: 4954 + mdat: 1,180,905). The remaining 118,789 bytes after the video data consist exclusively of three byte values: space (0x20), tab (0x09), and newline (0x0a).
Layers 3-6 — Multi-layer decoding chain: The whitespace data encodes through four successive layers: whitespace binary → ASCII characters → binary bytes → base64 → Morse-encoded digits → Braille letters.
Solution
Step 1: Extract hidden URL from msg.txt
python read-only • copy allowed# msg.txt has 6727 lines, mostly empty. Find non-empty lines: with open('msg.txt') as f: for i, line in enumerate(f, 1): if line.strip(): print(f"Line {i}: {line.strip()}") # Line 1: There is no flag in this file :) # Line 2: But so be it, I'll tell you where he is: https://www.youtube.com/watch?v=QNXvE1BZu8g # Line 3222: https://vimeo.com/911593386?share=copy
The YouTube link is a Rick Roll (decoy). The Vimeo link is the real next step.
Step 2: Extract Mega link from Vimeo description
bash read-only • copy allowedyt-dlp --dump-json "https://vimeo.com/911593386?share=copy" > meta.json # Description contains: "If you want to download: https://mega.nz/file/JRYFXaSK#_WuZVG_JdKdR_kmocV3_iZ1B0hhEDuwKwE4OPPZuceE" megadl "https://mega.nz/file/JRYFXaSK#_WuZVG_JdKdR_kmocV3_iZ1B0hhEDuwKwE4OPPZuceE"
Step 3: Extract whitespace data from MP4
python read-only • copy allowed# Parse MP4 atoms to find where video data ends import struct with open('strange.mp4', 'rb') as f: data = f.read() # Parse atoms: ftyp (24) + moov (4954) + mdat (1180905) = 1185883 bytes offset = 0 while offset < len(data): size = struct.unpack('>I', data[offset:offset+4])[0] atype = data[offset+4:offset+8].decode('ascii', errors='replace') print(f"Atom: {atype} at {offset}, size {size}") if size == 0: break offset += size # Extract trailing data after last atom trailing = data[1185883:] # 118,789 bytes of whitespace
Step 4: Decode whitespace → binary → ASCII → binary bytes → base64
python read-only • copy allowed#!/usr/bin/env python3 """Complete decode chain from whitespace to Morse+Braille digits.""" import base64 with open('strange.mp4', 'rb') as f: raw = f.read() # Extract trailing whitespace data (after MP4 atoms end at 1185883) trailing = raw[1185883:] # Split on tab+newline separators to get data lines lines = trailing.split(b'\t\n') # Layer 3: Each line encodes one ASCII char (space=0, tab=1 → binary → char) chars = [] for line in lines: if not line or line == b'\n': continue line = line.strip(b'\n') if not line: continue binary = '' for b in line: if b == 0x20: # space = 0 binary += '0' elif b == 0x09: # tab = 1 binary += '1' if binary: chars.append(chr(int(binary, 2))) ascii_text = ''.join(chars) # "01001101 01010100 01000001..." # Layer 4: Binary bytes → ASCII → base64 binary_groups = ascii_text.split(' ') raw_bytes = bytes(int(bg, 2) for bg in binary_groups if bg) b64_text = raw_bytes.decode('ascii') # Base64 decode decoded = base64.b64decode(b64_text).decode('ascii') print(decoded) # Output: "10000 01111 00001 01111 00011 00000 ..."
Step 5: Morse code → digits 1-6
The decoded data contains 5-digit binary groups (and one 6-digit group for underscore), with { and } as literal delimiters. Each group uses 0=dot, 1=dash in Morse code:
python read-only • copy allowedMORSE_NUMBERS = { '.----': '1', '..---': '2', '...--': '3', '....-': '4', '.....': '5', '-....': '6', '..--.-': '_', # underscore in Morse } # Parse: 4 spaces separate "words", single space separates groups within a word words = decoded.split(' ') digit_words = [] for word in words: word = word.strip() if word in ['{', '}']: digit_words.append(word) continue groups = word.split(' ') digits = '' for g in groups: morse = g.replace('0', '.').replace('1', '-') digits += MORSE_NUMBERS.get(morse, '?') digit_words.append(digits) # Result: ['6', '14', '135', '145', '15', '12', '13456', '{', # '2345', '125', '24', '234', '_', '24', '234', '_', # '12', '1235', '1', '24', '123', '123', '15', '_', # '1', '1345', '145', '_', '134', '135', '1235', '234', '15', '_', # '134', '234', '1245', '}']
Step 6: Braille dot positions → letters
The digits 1-6 correspond to the 6 dot positions in a Braille cell:
1 4
2 5
3 6
Each "word" specifies which dots are raised:
python read-only • copy allowed#!/usr/bin/env python3 """Braille decoding: digit sequences → raised dot positions → letters.""" BRAILLE = { frozenset([1]): 'A', frozenset([1,2]): 'B', frozenset([1,4]): 'C', frozenset([1,4,5]): 'D', frozenset([1,5]): 'E', frozenset([1,2,4]): 'F', frozenset([1,2,4,5]): 'G', frozenset([1,2,5]): 'H', frozenset([2,4]): 'I', frozenset([2,4,5]): 'J', frozenset([1,3]): 'K', frozenset([1,2,3]): 'L', frozenset([1,3,4]): 'M', frozenset([1,3,4,5]): 'N', frozenset([1,3,5]): 'O', frozenset([1,2,3,4]): 'P', frozenset([1,2,3,4,5]): 'Q', frozenset([1,2,3,5]): 'R', frozenset([2,3,4]): 'S', frozenset([2,3,4,5]): 'T', frozenset([1,3,6]): 'U', frozenset([1,2,3,6]): 'V', frozenset([2,4,5,6]): 'W', frozenset([1,3,4,6]): 'X', frozenset([1,3,4,5,6]): 'Y', frozenset([1,3,5,6]): 'Z', frozenset([6]): 'CAP', # Capital letter indicator (⠠) } words = ["6", "14", "135", "145", "15", "12", "13456", "{", "2345", "125", "24", "234", "_", "24", "234", "_", "12", "1235", "1", "24", "123", "123", "15", "_", "1", "1345", "145", "_", "134", "135", "1235", "234", "15", "_", "134", "234", "1245", "}"] result = '' for w in words: if w in ['{', '}', '_']: result += w continue dots = frozenset(int(d) for d in w) char = BRAILLE.get(dots, '?') if char == 'CAP': continue # Capital indicator, skip result += char print(f"Flag: {result}") # Flag: CODEBY{REDACTED}
Decoding table:
| Digit word | Dots raised | Braille | Letter |
|---|---|---|---|
| 6 | {6} | ⠠ | (capital indicator) |
| 14 | {1,4} | ⠉ | C |
| 135 | {1,3,5} | ⠕ | O |
| 145 | {1,4,5} | ⠙ | D |
| 15 | {1,5} | ⠑ | E |
| 12 | {1,2} | ⠃ | B |
| 13456 | {1,3,4,5,6} | ⠽ | Y |
| 2345 | {2,3,4,5} | ⠞ | T |
| 125 | {1,2,5} | ⠓ | H |
| 24 | {2,4} | ⠊ | I |
| 234 | {2,3,4} | ⠎ | S |
| 1235 | {1,2,3,5} | ⠗ | R |
| 1 | {1} | ⠁ | A |
| 123 | {1,2,3} | ⠇ | L |
| 1345 | {1,3,4,5} | ⠝ | N |
| 134 | {1,3,4} | ⠍ | M |
| 1245 | {1,2,4,5} | ⠛ | G |
| |