Импортозамещение (Import Substitution)
Импортозамещение (Import Substitution)
Platform: bug-makers | Category: Crypto | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-05-29 | Status: Solved Techniques: bitstream_regrouping, case_as_bit_flag, custom_alphabet_base64
Summary
Task: 48 mixed-case Cyrillic letters from a custom 'Russian-only' cipher. Solution: it is Base64 with the alphabet replaced by 32 Cyrillic letters (5-bit index) plus letter case as the 6th bit; regroup 6-bit symbols into bytes to recover the flag.
Импортозамещение (Import Substitution) — bug-makers
Challenge
В рамках импортозамещения и запрета иностранных слов в одном мессенджере гонце решили изобрести новый способ шифрования, использующий только русские буквы.
Translation: As part of "import substitution" and a ban on foreign words, a certain messenger decided to invent a new encryption method that uses only Russian letters.
Provided file 126-cypher.txt contains exactly 48 mixed-case Cyrillic letters:
РзХзРхСЖЮчМфЫЦЕщШХэоШЧНфМЗебЬцбгЪГСщШХэВШХйбНгСэ
Goal: recover the hidden flag.
Analysis
Two structural facts stand out:
- The text uses only Russian letters — no Latin characters, no digits, no symbols. The title literally says "import substitution," i.e. a foreign encoding has been re-implemented with domestic (Cyrillic) components.
- The case is seemingly random but mixed. In a normal text mixed case would be noise; here it is the crucial structural hint that case carries information, not aesthetics.
The Russian alphabet without Ё has exactly 32 letters:
АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ
32 = 2^5, so a letter index supplies 5 bits. Adding a 1-bit case flag gives 6 bits per character — exactly one Base64 symbol. The "import substitution" is therefore Base64 with its A-Za-z0-9+/ alphabet replaced by Cyrillic letters, the case acting as the high bit. This is confirmed after decoding: the flag self-references База64 ("the real Base64").
Mapping:
- Letter index 0–31 → low 5 bits.
- UPPERCASE → high bit 0 (value + 0).
- lowercase → high bit 1 (value + 32).
Each character is a 6-bit value; concatenating the bitstream and regrouping into 8-bit bytes yields ASCII.
Solution
python read-only • copy allowed#!/usr/bin/env python3 s = 'РзХзРхСЖЮчМфЫЦЕщШХэоШЧНфМЗебЬцбгЪГСщШХэВШХйбНгСэ' alpha = 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ' # 32 letters, no Ё # 6-bit symbol = 5-bit letter index + 1-bit case flag (lowercase = high bit set) vals = [alpha.index(c.upper()) + (0 if c.isupper() else 32) for c in s] # Regroup the 6-bit stream into 8-bit bytes -> ASCII bits = ''.join(format(v, '06b') for v in vals) out = ''.join(chr(int(bits[j:j+8], 2)) for j in range(0, len(bits), 8)) print(out) # BugCTF{REDACTED}
48 input symbols × 6 bits = 288 bits = 36 bytes of output, which matches the flag length cleanly (no padding needed).
Failed approaches (anti-patterns)
- Russian Caesar / ROT brute-force over all 32 shifts — no readable output. Case structure was ignored.
- ЙЦУКЕН (JCUKEN) keyboard-layout substitution mapping Cyrillic back to QWERTY — produced gibberish.
- Case directly as an 8-bit ASCII bitstream (upper=1/lower=0 and inverse) — near-misses but no valid text.
- Letter-index pairs as base-32 digits forming Unicode codepoints — produced unrelated characters.
- Breakthrough: combine the 5-bit letter index with the 1-bit case flag into a single 6-bit Base64 symbol.
Takeaways
Use this approach when:
- The ciphertext is a clean power-of-two-friendly count of letters from a restricted alphabet (32 Cyrillic letters = 5 bits; 48 chars → tidy byte boundary).
- Mixed case appears "random" — treat case as a carried bit, not noise.
- The challenge title/theme hints at re-implementing a known foreign encoding ("import substitution," "no foreign words") — suspect a custom-alphabet Base64/Base32 rather than a classical cipher.
- 32-letter alphabet + 1 case bit = 6 bits = Base64 symbol; 32 letters alone (no case) = Base32.