Skip to main content

Какой ты: writeup

Ссылка на задание http://f8tasks.ru/challenges#%D0%90%20%D0%BA%D0%B0%D0%BA%D0%BE%D0%B9%20%D1%81%D0%B5%D0%B3%D0%BE%D0%B4%D0%BD%D1%8F%20%D1%82%D1%8B?-33

Какой ты

CategoryCrypto
DifficultyEasy
Cipher / VulnerabilityNibble substitution (hex encoding)
Flagflag{emoji_to_ascii}

Recon

А какой сегодня ты?

😀😀 😀😂 😀🤩 😀😌 😌😈 😀😬 😀😭 😀🥴 😀🤬 😀🤠 😬🥴 😌😡 😀🥴 😬🥴 😀🤩 😌🥺 😀🥺 😀🤠 😀🤠 😌😭

Артефакт — строка из 20 токенов, каждый ровно 2 эмодзи, разделены пробелами. Ключевые наблюдения:

  • в первой позиции — только 3 разных символа (😀 😌 😬), во второй — 12
  • асимметрия «мало / много» при длине пары → nibble-split: старший полубайт (0–7) и младший (0–f)
  • 20 токенов = 20 байт — типичная длина CTF-флага

Nibble Substitution

Каждый byte флага записан в hex как два полубайта; каждый полубайт заменён эмодзи из независимой таблицы.

Формула: emoji_hi + emoji_lo → high_nibble || low_nibble → byte

Восстановление через Known-plaintext (префикс flag{):

Первые 5 байт в hex: 66 6c 61 67 7b.

Токенhex-байтemoji_hi → nibbleemoji_lo → nibble
😀😀66😀 → 6😀 → 6
😀😂6c😀 → 6😂 → c
😀🤩61😀 → 6🤩 → 1
😀😌67😀 → 6😌 → 7
😌😈7b😌 → 7😈 → b

5 токенов дают 😀 → 6, 😌 → 7 для HIGH_NIBBLE и 5 значений LOW_NIBBLE — достаточно для гипотезы. Оставшиеся значения добираются из следующих токенов без противоречий.

Итоговые таблицы:

HIGH_NIBBLE:

Эмодзиnibble
😀6
😌7
😬5

LOW_NIBBLE:

ЭмодзиnibbleЭмодзиnibble
😀6😭d
😂c🥴f
🤩1🤬a
😌7🤠9
😈b😡4
😬5🥺3

Decryption

ШагТокенhexASCIIШагТокенhexASCII
1😀😀66f11😬🥴5f_
2😀😂6cl12😌😡74t
3😀🤩61a13😀🥴6fo
4😀😌67g14😬🥴5f_
5😌😈7b{15😀🤩61a
6😀😬65e16😌🥺73s
7😀😭6dm17😀🥺63c
8😀🥴6fo18😀🤠69i
9😀🤬6aj19😀🤠69i
10😀🤠69i20😌😭7d}

Hex-строка: 666c61677b656d6f6a695f746f5f61736369697dflag{emoji_to_ascii}


Automation

TOKENS = "😀😀 😀😂 😀🤩 😀😌 😌😈 😀😬 😀😭 😀🥴 😀🤬 😀🤠 😬🥴 😌😡 😀🥴 😬🥴 😀🤩 😌🥺 😀🥺 😀🤠 😀🤠 😌😭".split()


HIGH_NIBBLE = {
    "😀": "6",
    "😌": "7",
    "😬": "5",
}


LOW_NIBBLE = {
    "😀": "6",
    "😂": "c",
    "🤩": "1",
    "😌": "7",
    "😈": "b",
    "😬": "5",
    "😭": "d",
    "🥴": "f",
    "🤬": "a",
    "🤠": "9",
    "😡": "4",
    "🥺": "3",
}


def decode(tokens: list[str]) -> str:
    hex_bytes = "".join(HIGH_NIBBLE[first] + LOW_NIBBLE[second] for first, second in tokens)
    return bytes.fromhex(hex_bytes).decode()


if __name__ == "__main__":
    print(decode(TOKENS))
python3 solve.py
# flag{emoji_to_ascii}

Key Takeaways

  1. Nibble substitution. Один байт кодируется двумя символами из независимых алфавитов. Признак — асимметрия количества уникальных символов в первой и второй позиции пары.
  2. Known-plaintext attack. Стандартный префикс flag{ (5 байт) немедленно восстанавливает большую часть подстановочной таблицы без перебора.
  3. Structural analysis. Длина токена, счётчик уникальных символов по позиции и сравнение с hex/base64/morse — стандартный первый шаг при анализе нестандартных артефактов.