Сломанные буквы (Broken Letters)
Сломанные буквы (Broken Letters)
Platform: CUSTOM | Category: Crypto | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: vigenere_decrypt, known_plaintext_attack, key_recovery
Summary
Walkthrough of the CUSTOM Crypto Сломанные буквы (Broken Letters). Given file enc_flag.txt with encrypted text: asdlcm{t1e3rey3_d1df3p_a1ls_bzu4ww_l1c3}
Description
"Что с флагом? Раньше он не был таким..." Формат флага: codeby{flag}
Given file enc_flag.txt with encrypted text:
asdlcm{t1e3rey3_d1df3p_a1ls_bzu4ww_l1c3}
Analysis
-
Known flag format: The flag should start with
codeby{, but the encrypted text starts withasdlcm{ -
Identifying cipher type: Letters are shifted, but not uniformly — this is a sign of polyalphabetic cipher (Vigenere)
-
Key recovery via known-plaintext attack:
c->a: shift = (0 - 2) mod 26 = 24 =yo->s: shift = (18 - 14) mod 26 = 4 =ed->d: shift = (3 - 3) mod 26 = 0 =ae->l: shift = (11 - 4) mod 26 = 7 =hb->c: shift = (2 - 1) mod 26 = 1 =by->m: shift = (12 - 24) mod 26 = 14 =o
-
Key:
yeahbo...— tryingyeahboy(7 characters) gives correct decryption
Solution
python read-only • copy allowed#!/usr/bin/env python3 """ Vigenere cipher decryption with known-plaintext attack Key recovery from known flag format codeby{...} """ from itertools import cycle def vigenere_decrypt(ciphertext, key): """Vigenere cipher decryption""" result = "" key_cycle = cycle(key.lower()) for c in ciphertext: if c.isalpha(): k = next(key_cycle) shift = ord(k) - ord('a') base = ord('a') if c.islower() else ord('A') decrypted = (ord(c) - base - shift) % 26 result += chr(base + decrypted) else: result += c return result def recover_key(plaintext, ciphertext): """Recover key from plaintext/ciphertext pair""" key = "" for p, c in zip(plaintext, ciphertext): if p.isalpha() and c.isalpha(): shift = (ord(c.lower()) - ord(p.lower())) % 26 key += chr(ord('a') + shift) return key # Encrypted text enc = "asdlcm{t1e3rey3_d1df3p_a1ls_bzu4ww_l1c3}" # Recover key from known format known_plain = "codeby" known_cipher = "asdlcm" partial_key = recover_key(known_plain, known_cipher) print(f"Partial key: {partial_key}") # yeahbo # Full key (guessing the last character) key = "yeahboy" # Decryption flag = vigenere_decrypt(enc, key) print(f"Flag: {flag}") # codeby{REDACTED}
Hints from the Task
- "Broken Letters" — letters were "broken" (encrypted with shifts)
- Flag:
REDACTED= "vigenere cipher will always live" — confirms the cipher type - Key:
yeahboy— a meaningful word, typical for CTF
Theory: Vigenere Cipher
Vigenere cipher is a polyalphabetic substitution cipher:
- Each letter is shifted by a value determined by the corresponding key letter
- The key repeats cyclically
- Encryption:
C[i] = (P[i] + K[i mod len(K)]) mod 26 - Decryption:
P[i] = (C[i] - K[i mod len(K)]) mod 26
Attacks on Vigenere:
- Known-plaintext (as here) — if part of the plaintext is known
- Kasiski examination — searching for repeating sequences to determine key length
- Friedman test — statistical analysis to determine key length
- Frequency analysis — after determining key length, frequency analysis for each position
Key Takeaways / Lessons
- Mastering vigenere_decrypt, known_plaintext_attack, key_recovery was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-01-21 • CUSTOM • Сломанные буквы (Broken Letters) • Difficulty: Medium