/Разборы задач
1 ноября 2026
Райтап на задачу «Анализ трафика»
Находим отладочный запрос с флагом в сетевом трафике с помощью Wireshark.5
5

Forensic
Задача «Секреты мертвого шпиона»
Скачаем файлы, необходимые для решения задания.
Сначала определимся, что у нас вообще за файлы. hard_drive.qcow2 — образ диска в формате qcow для виртуальных машин на основе QEMU/KVM (Linux). Загуглим, что такое tpm2-00.permall файл.
По публично доступной информации понимаем, что это файл состояний TPM. Смонтируем диск и посмотрим, что он из себя представляет. Для этого подключим модуль ядра nbd (network block device) и используем qemu-nbd.
У нас есть 3 раздела. Смонтируем первый и посмотрим, что там.
Мы видим, что это EFI-раздел для Ubuntu, тут лежат всякие загрузчики, необходимые для загрузки системы.
Теперь посмотрим, что у нас на втором разделе. Тут у нас лежит раздел /boot, именно тут находится ядро и initramfs.
Третий раздел смонтировать просто так не получится. Он зашифрован с помощью LUKS2. Посмотрим метаданные раздела с помощью cryptsetup luksDump.
Посмотрим внимательно вывод этой команды. LUKS поддерживает несколько паролей для разблокирования, они называются слоты. У нас есть два keyslot: 0) argon2id с тяжёлыми параметрами 1) pbkdf-sha512 с тысячей итераций. Первый слот, вероятно, предназначен для человеческого пароля, а второй — для systemd-токена, о котором ниже.
Видим, что у нас есть токен для разблокировки диска с TPM2+PIN (tpm2-pin: true). Что вообще из себя модуль TPM? Это криптографический чип, который отвечает за безопасность компьютера или сервера. Делает он это посредством того, что является изолированным «криптопроцессором» для защиты ключей. Выдаёт он ключи только при определённом условии, например верный PIN-код или у системы правильные значения PCR (Platform Configuration Registers). В этой задаче PCR не рассматривается.
В параметрах видим keyslot: 1, это и есть тот слот с pbkdf-sha512 1000.
Попробуем загрузить этот диск на виртуальной машине virt-manager с EFI.
Действительно, для загрузки необходим PIN-код.
Какой же вектор атаки выбрать? У нас стоит какой-то неизвестный PIN-код для расшифровки диска, который проверяется виртуальным TPM. Можно попробовать его брутить, и это даже возможно, когда у тебя локально есть дамп, но вдруг автор поставил пароль в 32+ символа?
Если вы не знакомы с внутренней кухней LUKS, TPM 2.0 и systemd с линуксами, то можно просто загуглить, что можно сделать, когда TPM скомпрометирован и используется TPM2+PIN.
Результату нейронки не доверяем, открываем первые попавшиеся GitHub issues.
Тут говорится, что при использовании LUKS с TPM2+PIN (systemd-cryptenroll) при компрометации TPM можно взломать шифрование, не зная PIN-кода. PIN-код нужен только для проверки на стороне TPM. В issue это рассматривается на реальном примере с уязвимостью faulTPM (Ryzen 1000–5000). Получается, с этим таском мы могли бы быть на этапе пост-эксплуатации faulTPM, когда имеем расшифрованный дамп. Кто знает, может, криминалисты и использовали эту уязвимость, чтобы сделать дамп fTPM этого шпиона.
(пример атаки faulTPM)
Теперь мы знаем, что при использовании схемы TPM+PIN, если мы имеем полный доступ к TPM, мы можем расшифровать диск. Теперь надо понять, как оно работает под капотом. Для этого придётся читать исходники swtpm, libtpms (на чём основывается swtpm) и systemd. Если вы захотите прорешать с нуля ручками, то в таблице указаны функции и константы, которые объясняют, как работает процесс запечатывания (seal) и распечатывания (unseal).
По-человечески, когда вы используете systemd-cryptenroll, чтобы привязать TPM к LUKS, то оно работает следующим образом:
systemd-cryptenroll получает мастер-ключ LUKS после ввода пароля пользователем.
Генерирует случайный 32-байтовый секрет.
Берёт/создаёт Storage Root Key (SRK) в TPM по адресу 0x81000001. SRK — это главный «родительский» ключ, под которым TPM хранит и защищает ключи и различные секреты.
Запечатывает секрет в TPM под SRK с определённой политикой, например, использование PIN-кода. TPM возвращает TPM2B_PRIVATE и TPM2B_PUBLIC. TPM2B_PRIVATE всегда покидаёт TPM только в зашифрованном виде.
TPM2B_PRIVATE состоит из трёх блоков: integrity (HMAC), IV (вектор инициализации для AES) и ciphertext (AES-CFB). Если расшифровать ciphertext, мы получим структуру TPMT_SENSITIVE, внутри которой находится: sensitiveType — тип секретных данных (keyedhash), authValue — хеш PIN-кода, seedValue — случайное число для генерации внутренних ключей и sensitive.data — сам секрет для разблокировки диска.
Обратим внимание, что authValue хранится отдельно от sensitive.data. PIN-код используется только для удовлетворения политики доступа TPM и никак не используется в шифровании. Если TPM скомпрометирован, то ничто не мешает получить секрет как есть (до версии systemd 262, но это уже отдельная тема).
Полученные структуры склеивает в tpm2-blob и записывает LUKS JSON-токен. Его мы и видели на этапе разведки в cryptsetup luksDump.
Кодирует тот же 32-байтовый секрет в base64 и использует как пароль нового LUKS-keyslot.
LUKS2 создаёт keyslot из base64-пароля через KDF с минимальными параметрами, получает keyslot-ключ и шифрует им volume key. Используется самый простой pbkdf2-sha512, потому что нет необходимости в ресурсоёмкой функции формирования ключа, так как это случайные 32 байта.
Пора писать скрипты. Весь исходный код каждого этапа можете посмотреть на GitHub (https://github.com/kolbanidze/swtpm-poc).
1) Извлечём systemd-токен из LUKS2-заголовка.
2) Распарсим файл состояния swtpm — tpm2-00.permall, вытащим из него все объекты.
3) Мы помним, что TPM не хранит сам секрет — все делается с помощью уникального SRK, который никогда его не покидаёт в открытом виде (по задумке). Сам SRK тоже, как tpm2-blob, состоит из TPMT_PUBLIC и TPMT_SENSITIVE. Публичная часть нас не особо интересует, нам интересна закрытая часть, в которой хранится seedValue, с помощью которого TPM выводит ключи для шифрования дочерних объектов. Примечание: не путайте с seedValue в TPM2B_PRIVATE, где хранится ключ для разблокировки диска.
Когда TPM создаёт дочерний объект из-под SRK, приватная часть шифруется ключом:
KDFa(SHA256, SRK.seedValue, "STORAGE", child.Name, 128)KDFa — это специальная функция формирования ключа по спецификации TPM 2.0. Child.Name — это хеш от публичной части, склеенный с названием алгоритма.
Теперь мы понимаем, как зашифрован blob и что надо для извлечения секрета. Надо извлечь из SRK его seedValue, с помощью KDFa и публичных данных сформировать ключ, расшифровать blob и извлечь из него секрет.
Приватный ключ SRK нам не понадобится, он нужен для других целей.
4) Теперь только остаётся восстановить логику KDFa и расшифровать TPM2B_PRIVATE.
5) Отлично, мы получили Base64-строку (именно она и является ключом к слоту). Остаётся только расшифровать диск.
При попытке смонтировать диск понимаем, что внутри LUKS находится не сразу ext4 раздел, а LVM (Logical Volume Manager). Поэтому активируем volume group (vg) и монтируем раздел. По умолчанию Ubuntu всегда использует LUKS+LVM.
Флаг находится в /root/flag.txt.
Разумеется, у задачи есть несколько способов решения. Можно запустить локальный эмулятор (сервер) swtpm и уже с помощью gdb или чего-то похожего вырезать проверку authValue и штатным образом сделать unseal().