← Back to Writeups
CUSTOMMediumCrypto

Сломанные буквы (Broken Letters)

XESXOR9/2/20263 min read
#crypto#custom#medium#vigenere_decrypt#known_plaintext_attack#key_recovery

Сломанные буквы (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

  1. Known flag format: The flag should start with codeby{, but the encrypted text starts with asdlcm{

  2. Identifying cipher type: Letters are shifted, but not uniformly — this is a sign of polyalphabetic cipher (Vigenere)

  3. Key recovery via known-plaintext attack:

    • c -> a: shift = (0 - 2) mod 26 = 24 = y
    • o -> s: shift = (18 - 14) mod 26 = 4 = e
    • d -> d: shift = (3 - 3) mod 26 = 0 = a
    • e -> l: shift = (11 - 4) mod 26 = 7 = h
    • b -> c: shift = (2 - 1) mod 26 = 1 = b
    • y -> m: shift = (12 - 24) mod 26 = 14 = o ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
  4. Key: yeahbo... — trying yeahboy (7 characters) gives correct decryption

Solution

python
#!/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:

  1. Known-plaintext (as here) — if part of the plaintext is known
  2. Kasiski examination — searching for repeating sequences to determine key length
  3. Friedman test — statistical analysis to determine key length
  4. 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