Извлечение контейнера закрытого ключа из APDU-трафика

Извлечение контейнера закрытого ключа из APDU-трафика
Уже скоро год с момента доклада , однако интересующиеся до сих пор пишут вопросы, что и послужило причиной написания этого поста.

Проблема с которой ко мне обратились - утилита dumpContfromAPDU.pl не дампит контейнер из правильно созданного с помощью Smartcard Sniffer -а лога команд и ответов APDU.
Причин такой неработы dumpContfromAPDU.pl могут быть две:
1.  используется отличная от имеющейся у меня на момент исследования модель токена - разные модели токенов имеют несколько разный набор команд APDU, поэтому скрипт не находит нужные регексы;
2. дамп не содержит пересылки контейнера.
Вообще, скрипт писался на скорую руку в качестве PoC исключительно для целей разовой демонстрации и делать из него полноценный инструмент "на все (ну, или, по крайней мере, на многие) случаи жизни" не планирую по нескольким причинам:
а)  наверно, это своего рода "хак" (реализация неавторизованного доступа), а распространять инструменты для неавторизованного доступа - неправильно;
б) лень.

В качестве компромисса - просто расскажу как извлечь контейнер из трафика APDU вручную и предположу почему это работает.

Как это сделать.
1. Получаем дамп Smartcard Sniffer-а, при выполнении операции Проверки (можно выполнить любую другую операцию, провоцирующую передачу ключа в приложение, например, вычисление электронной подписи), как это показано в видео .
2. Далее, как выше отмечено, скрипт dumpContfromAPDU.pl не работает, поэтому что-то запустить и получить контейнер - не получится :), но для анализа, традиционно на скорую руку, я написал другой скрипт - writeBinaryAPDU.pl ,  который делает элементарную вещь: дамп Smartcard Sniffer-а записывает в виде бинарных файлов диалога APDU между приложением и карточкой (токеном). Понятно, что диалог такой есть всегда, поэтому этот скрипт работает с любым токеном. В результате его работы появляется дериктория с именем эквивалентному времени ее создания, в которой лежат файлы вида "001_out(4)39_6A_42_32", где
001 - идентификатор последовательности запрос-ответ;
out - означает, что эти байты передавались из карточки в приложение, соответственно, от приложения в карточку - будет "in";
(4) - количество переданныхполученных байтов;
39_6A_42_32 - первые 4 байта.
3. Такие файлики (001_out(4)39_6A_42_32) далее смотрим глазами иили в шестрнадцатиричном редакторе. Кто такого редактора не имеет и никогда не планирует - можно использовать FAR .
4. Опытным путем установлено, что контейнер секретного ключа представляет из себя набор файлов с расширением .key (см. слайд 1 1 ):
  • primary.key - должен иметь размер 36 байт, начало - 30_22_04_20, поэтому в нашей директории ищем файл следующего вида: №_out(36)30_22_04_20. Скорее всего такой файл будет один - сохраняем его с именем primary.key
  • masks.key - имеет размер 56 байт и начало - 30_36_04_20, соответствующий файл в нашем бинарном дампе - №_out(56)30_36_04_20. Также, скорее, этот файл будет один - это искомый masks.key
  • header.key - файл, похоже, со всяким описанием, однако его целостность как-то контролируется, поэтому если его собрать не полностьюнеправильно контейнер работать не будет. О нем известно, что он содержит много информации, причем, в основном текстовой, через APDU передается порциями по 256 байт. Соответственно, в нашем дампе собираем в отдельную папочку все файлы вида №_out(256?)какие-то_байты. Я написал 256?не случайно, поскольку видел, как header.key передавался порциями по 253 байта, какая там логика выбора размера порций - не известно, но в любом случае эти порции будут радикально отличаться по размеру от всего остального трафика из карточки. Дополнительно к этим файлам надо обязательно добавить предыдущий порциям из 256 байтов ответ от карточки длиной 10 байт и с началом 30_82, а также, возможно, оставшийся "хвостик", следующий за порциями по 256 байт (или 253 или еще сколько, но обычно - больше всех остальных, и равных).  Все эти файлы надо слить в один в порядке следования (поскольку вначале названия файла стоит номер последовательности - упорядочивание по алфавиту дает порядок следования) - это можно сделать утилиткой cat .
  • name.key - содержит имя контейнера, передаваемое обычно одной порцией (в рамках исследования я делал контейнер с очень длинным именем, это видно на видео, чтобы спровоцировать передачу имени контейнера в нескольких порциях - это удалось, передача проводилась аналогично передаче header.key: сначала первые 10 байт, затем - все остальное), искомый файл - следующего вида №_out(длина имени)30_длина имени_16_длина имени - 2. Интересен тот факт, что содержимое этого файла, в целом, не важно, поэтому можно сгенерить свой файл name.key, с соблюдением формата его первых четырех байт.

В тех случаях, что я видел, можно игнорировать запросы к карточке №_in(размер)00_04*  и ответы на них. В некоторых случаях можно игнорировать и другие "стандартные" команды и ответы на них из спецификации GlobalPlatform .

Итак, собрав все 4 файлика, копируем их на флешку и проверяем контейнер, выполняя операцию "Проверка", аналогично той, что делали в момент работы Smartcard sniffer-а (при этом уже никакой пароль на контейнер спрашиваться не будет). Если проверка не прошла, то надо попробовать различные "оконечные условия" для файла header.key - если после одинаковых порций вы добавляли "хвостик", с порцией меньше предыдущих - попробуйте без него - если проверка снова неуспешна, добавьте еще следующую порцию (еще один "хвостик") - в общем, поупражняйтесь с разными комбинациями порций файлов №_out(размер)* для сбора header.key.

Почему это работает (предположения).
Криптопровайдер ( CSP ) - это не только как реализована математика для выполнения различных криптографических преобразований, но и как ключи хранятся. Именно поэтому для хранения ключей на токене мы используем специальный "токенный" провайдер и т.п. 

Криптопровайдер КриптоПРО CSP (не КриптороПРО eToken CSP и не КриптоПРО Rutoken CSP!! - предположительно, на них не пройдет описываемая атака, но надо поисследовать...), умеет хранить хранить ключи в файликах, указанных выше, причем, не важно в какой среде - реестр ли Windows (посмотрите внимательно видео в районе 2:28, где видно как устроено хранилище в реестре :) и сравните с упомянутым выше слайдом 11), флешка с файловой системой или токен используется. Работа с ключами происходит в приложении (криптопровайдере), а, следовательно, они туда передаются - поэтому они дампятся из трафика APDU.

Что же делать?
Да, как видите, недостаточно иметь токен, даже с отечественной криптографией на борту, надо еще иметь криптопровайдер, который для операций с ключом будет использовать не свои стеки и кучи, а бортовую криптографию токена, что прогарантирует выполнение вычислений с ключом внутри токена, что, в свою очередь, обеспечит отсутствие передачи ключа из токена, соответственно, его нельзя будет перехватить из трафика APDU.

Alt text

Мир сходит с ума и грянет киберапокалипсис. Подпишись на наш Телеграм канал, чтобы узнать первым, как выжить в цифровом кошмаре!

Сергей Солдатов

REPLY-TO-ALL is a double language blog (English/Russian) run by three information security practitioners. Want to discuss information security problems? This is the place.