← Back to Writeups
CUSTOMMediumMisc

A?

XESXOR9/2/20263 min read
#misc#custom#medium#combining_mark_substitution#known_plaintext_attack#leet_speak_recognition#unicode_nfd_decomposition

A?

Platform: TaipanByte | Category: Misc | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-02-14 | Status: Solved Techniques: combining_mark_substitution, known_plaintext_attack, leet_speak_recognition, unicode_nfd_decomposition

Summary

The task provides a string of Unicode characters where each character is the letter 'A' with various diacritical marks:

A? — TaipanByte

Description

The task provides a string of Unicode characters where each character is the letter 'A' with various diacritical marks:

ĀȦ{ÃA̧Ȃ3@Ǎ_A̧1A̯A̰3Ȃ_A̱3A̧0A̱3Ȃ}

The task name "A?" hints that all characters are related to the letter A.

Flag format: TB{...}

Analysis

1. NFD Decomposition

Each modified character under NFD decomposition (Normalization Form Decomposition) breaks down into:

  • Base character: A (U+0041)
  • Combining diacritical mark: a character from the U+0300–U+036F range ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Literal characters ({, }, _, 3, @, 1, 0) remain unchanged — they don't contain combining marks.

2. Substitution Cipher

Each combining mark encodes a specific letter. This is a classic substitution cipher where the key is the mapping table "combining mark → letter".

3. Known-Plaintext Attack

Knowing the flag format TB{...}, the first two characters give us:

  • Ā = A + U+0304 (COMBINING MACRON) → T
  • Ȧ = A + U+0307 (COMBINING DOT ABOVE) → B

4. Mapping Table

SymbolNFDCombining MarkCodepointNameLetter
ĀA + ◌̄COMBINING MACRONU+0304MACRONT
ȦA + ◌̇COMBINING DOT ABOVEU+0307DOT ABOVEB
ÃA + ◌̃COMBINING TILDEU+0303TILDEs
A̧A + ◌̧COMBINING CEDILLAU+0327CEDILLAc
ȂA + ◌̑COMBINING INVERTED BREVEU+0311INVERTED BREVEr
ǍA + ◌̌COMBINING CARONU+030CCARONm
A̯A + ◌̯COMBINING INVERTED BREVE BELOWU+032FINVERTED BREVE BELOWp
A̰A + ◌̰COMBINING TILDE BELOWU+0330TILDE BELOWh
A̱A + ◌̱COMBINING MACRON BELOWU+0331MACRON BELOWd
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

5. Decoding Step by Step

The string splits into three words (separated by _):

  1. ÃA̧Ȃ3@Ǎ → s-c-r-3-@-m → scr3@m (scream)
  2. A̧1A̯A̰3Ȃ → c-1-p-h-3-r → c1ph3r (cipher)
  3. A̱3A̧0A̱3Ȃ → d-3-c-0-d-3-r → d3c0d3r (decoder)

Cross-validation: the same combining mark always yields the same letter:

  • CEDILLA (U+0327) → c — appears in scr3@m, c1ph3r, d3c0d3r ✓
  • INVERTED BREVE (U+0311) → r — appears in scr3@m, c1ph3r, d3c0d3r ✓
  • MACRON BELOW (U+0331) → d — appears in d3c0d3r (twice) ✓ ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

python
#!/usr/bin/env python3
"""
A? — TaipanByte CTF
Unicode combining diacritical marks substitution cipher decoder.
"""
import unicodedata

# Encrypted string
encoded = "ĀȦ{ÃA̧Ȃ3@Ǎ_A̧1A̯A̰3Ȃ_A̱3A̧0A̱3Ȃ}"

# Substitution table: combining mark codepoint → letter
# Derived through known-plaintext (TB{...}) and word analysis
substitution = {
    0x0304: 'T',  # COMBINING MACRON
    0x0307: 'B',  # COMBINING DOT ABOVE
    0x0303: 's',  # COMBINING TILDE
    0x0327: 'c',  # COMBINING CEDILLA
    0x0311: 'r',  # COMBINING INVERTED BREVE
    0x030C: 'm',  # COMBINING CARON
    0x032F: 'p',  # COMBINING INVERTED BREVE BELOW
    0x0330: 'h',  # COMBINING TILDE BELOW
    0x0331: 'd',  # COMBINING MACRON BELOW
}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# NFD decomposition and decoding
nfd = unicodedata.normalize('NFD', encoded)
result = []
skip_next = False

for i, ch in enumerate(nfd):
    if skip_next:
        skip_next = False
        continue
    
    cp = ord(ch)
    
    # If this is a combining mark (U+0300-U+036F) — substitute the letter
    if 0x0300 <= cp <= 0x036F:
        if cp in substitution:
            result.append(substitution[cp])
        else:
            result.append(f'[U+{cp:04X}]')
    # If this is base 'A' and next char is a combining mark, skip 'A'
    elif ch == 'A' and i + 1 < len(nfd) and 0x0300 <= ord(nfd[i + 1]) <= 0x036F:
        continue  # 'A' is the base character, will be processed via combining mark
    else:
        # Literal character (digits, brackets, underscores)
        result.append(ch)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

flag = ''.join(result)
print(f"Flag: {flag}")
# Flag: TB{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍