Автор: cuamckuu
Извлечение содержимого карты и работа с EMV-командами может быть интересна не только в исследовательских целях. Существует несколько видов атак на бесконтактные банковские карты, про реализацию которых будет рассказано под катом.
Введение
В этом году я попал на стажировку Summer of Hack 2019 в Digital Security и поработал над темой исследования бесконтактных EMV-карт. В ходе стажировки получилось лучше узнать о том, как работают банковские карты, и создать новую утилиту для работы с бесконтактными картами. С демонстрацией режима считывания данных можно познакомиться здесь.
Виды атак
Карта и терминал общаются, используя стандарт EMV (Europay + MasterCard + VISA), который разрабатывался для повышения безопасности платежей. В связи с этим, существует не так много векторов атак на бесконтактные карты. В статье речь пойдёт о следующих:
- Привязка чужой карты в онлайн-магазине
- Вспомогательный шаг в социальной инженерии
- Бесконтактный DoS, превращающий карту в кирпич
Такие атаки возможны при взаимодействии с картой по EMV-стандарту. Многие карты позволяют без проблем считать приватную информацию пользователя, в частности PAN, срок действия и имя держателя. Считанных данных хватает для привязки карты в некоторых онлайн-магазинах, например, Amazon. А полученные приватные данные можно использовать для более персонализированного фишинга или при рассылке спама.
Похожим способом реализуется DoS-атака. Вызов нужных EMV-команд позволяет переполнить внутренний счётчик транзакций или имитировать неправильный ввод PIN-кода, что приводит к блокировке карты.
Зачем изобретать велосипед?
Перед началом разработки было решено изучить существующие решения. В частности, были рассмотрены: RFIDIOt/ChAP.py, Jaccal, nfcmillionaire, PoC с конференций, “Ruby” script, Android apps/libs.
Большинство из рассмотренных программ пытаются имитировать работу POS-терминала и используют словари с идентификаторами поддерживаемых карт, поэтому часто такие решения будут работать только с Visa и MasterCard.
Другой проблемой является считывание малой части данных вместо снятия полного дампа. Почти все программы пытаются разобрать как можно больше EMV на лету и делают ошибки при разборе.
Если какая-то из проблем отсутствует или не так сильно выражена, то скорее всего речь идёт о приложении/библиотеке для Android, и вам потребуется телефон с NFC-ридером, который пока есть не у всех.
Описанные проблемы можно решить, для этого следует:
- Определять тип карты динамически, вместо использования словарей
- Добавить несколько режимов работы для полного считывания файлов
- Не пытаться разбирать EMV-стандарт на лету
- Использовать дешёвый ридер PN532 (~5$)
Извлекаем данные с карты
На Хабре уже есть статьи, подробно описывающие процесс взаимодействия карты и терминала (раз, два, три), поэтому постараюсь не повторяться и сосредоточиться на практической части.
Для общения с картой будем использовать ридер PN532 из-за приятной цены и библиотеку libnfc. Плавно переходим к делу. Данные на карте организованы следующим образом:
Окружения -> Приложения -> Файлы -> Записи.
Цель считывания – получить данные всех записей, для этого нужно пройти полную цепочку, причём выбор окружения и приложения происходит только один раз в начале общения.
Тот же процесс в терминах EMV-команд:
- SELECT PPSE // Выбираем бесконтактное окружение
- SELECT APPLICATION // Выбираем приложение по AID
- READ RECORD // Указываем номер файла и записи
Рассмотрим пример формирования одной из команд. Первая команда для выбора бесконтактного окружения всегда одинаковая и выглядит следующим образом:
byte_t const command[] = {
0x40, 0x01, // Pn532 InDataExchange
0x00, 0xA4, // SELECT ppse
0x04, 0x00, // P1:By name, P2:_
0x0e, // Lc: Data length
0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, // Data string:
0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, // 2PAY.SYS.DDF01 (PPSE)
0x00 // Le
};
Пример ответа: 6F 23 84 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31 A5 11 BF 0C 0E 61 0C 4F 07 A0 00 00 00 03 10 10 87 01 01 90 00
Для вызова следующей команды нам потребуется разобрать ответ предыдущей команды.
Нас интересует идентификатор приложения AID. Запоминаем его и формируем новый SELECT запрос, но на этот раз передаём команде SELECT полученный AID, а не PPSE, как в первой команде.
Пример: 00 A4 04 00 07 A0 00 00 00 03 10 10 00
Как было сказано ранее, важно получать AID динамически, а не использовать подготовленный словарь, тогда ваше приложение с большой вероятностью сможет считывать разные виды карт, например, карты МИР.
После того, как приложение выбрано, мы можем читать необходимые записи, используя команду READ RECORD и передавая в качестве параметров номер файла и номер записи.
Пример формирования команды: (подробнее об этом можно почитать в стандарте. EMV Book1. 11.2 READ RECORD)
byte_t const sfi_param = (sfi << 3) | (1 << 2);
byte_t const command[] = {
0x40, 0x01, // Pn532 InDataExchange
0x00, 0xB2, // READ RECORD
record_number, sfi_param, // P1:record_number and P2:SFI
0x00 // Le
};
Пример вызова: 00 B2 02 14 00
Заметим, что под номер файла отводится не полный байт, соответственно, всего файлов может быть 31 (2^5 — 1), а записей 255. Файлы с 1-го по 10-й отводятся для хранения внутренних данных, а оставшиеся отводятся под хранение лога транзакций, если карта поддерживает логирование.
Теперь используя два вложенных цикла, мы можем получить данные всех записей карты, вызывая для каждой пары команду READ RECORD. Процесс перебора можно существенно ускорить, если обращать внимание на возвращаемый картой status word (последние два байта ответа). Статус может сказать нам о том, что файла не существует (SW = 0x6A82) или о том, что дальше в этом файле нет записей (SW = 0x6A83).
На практике выяснилось, что имеет смысл рассматривать только случай несуществующего файла, так как иногда встречаются карты, которые некорректно используют статус код для отсутствующих записей. После чтения данных можно отправить их в один из онлайн-парсеров, мне понравился этот.
Фрагмент данных, извлечённых из записей карты:
Считанных данных хватит для привязки карты в некоторых онлайн-магазинах (в основном, в зарубежных), также полученную информацию можно использовать для более персонализированной атаки с применением социальной инженерии.
Организуем бесконтактный DoS
Переходим к следующему виду атаки. Для DoS-a существует, как минимум, 2 способа реализации.
Медленный способ
Для реализации медленного способа (атака потребует около 4 минут) необходимо переполнить внутренний счётчик транзакций карты (ATC). Для этого требуется вызвать:
- SELECT PPSE // Выбираем бесконтактное окружение
- SELECT APPLICATION // Выбираем приложение
- GET DATA // Узнаём, сколько осталось до переполнения ATC (Опционально)
- GET PROCESSING OPTIONS // Начинаем проводить транзакции
- …
- GET PROCESSING OPTIONS // Повторяем транзакции до переполнения
Рассмотрим шаги подробнее. Выбор бесконтактного окружения и приложения аналогичен пункту с извлечением данных, единственным отличием является то, что на этот раз нам потребуется обработать ответ команды SELECT APPLICATION.
Нас интересует поле PDOL, так как оно понадобится для последующего вызова команды GET PROCESSING OPTIONS, которая и будет увеличивать счётчик транзакций при каждом вызове.
В PDOL-е карта просит от нас информацию о терминале и платеже.
В примере выше карта просит у нас:
- 9F66 04 // Terminal transaction qualifier (4 bytes)
- 9F02 06 // Amount (6 bytes)
- 9F37 04 // Unpredictable number (4 bytes)
- 5F2A 02 // Transaction Currency code (2 bytes)
Поскольку мы реализуем DoS, а не реальный платёж, мы можем не заботиться о правильности значений и просто использовать заранее подготовленные значения, которые примет карта. Подробнее о требуемых значениях можно почитать в стандарте. (EMV Book 3. Annex A Data Elements Dictionary).
Для удобства практического использования привожу таблицу:
Тег (Hex) | Описание | Валидное значение (Hex) |
9F59 | Terminal Transaction Information | C8 80 00 |
9F5A | Terminal Transaction Type | 00 |
9F58 | Merchant Type Indicator | 01 |
9F66 | Terminal Transaction Qualifiers | 79 00 40 80 |
9F02 | Amount (authorized) | 00 00 00 10 00 00 |
9F03 | Amount (Other) | 00 00 00 00 00 00 |
9F1A | Terminal Country Code | 01 24 |
5F2A | Transaction Currency Code | 01 24 |
95 | Terminal Verification Results | 00 00 00 00 00 |
9A | Transaction Date | 19 01 01 |
9C | Transaction Type | 00 |
9F37 | Unpredictable Number | 82 3D DE 7A |
Сформировав данные, запрашиваемые картой, мы готовы к вызову GPO.
Пример: 80 A8 00 00 12 83 10 79 00 40 80 00 00 00 10 00 00 82 3D DE 7A 01 24 00
Ответ: 77 4F 82 02 20 00 94 0C 10 02 03 00 18 01 01 00 10 04 04 00 57 13 42 76 55 00 66 83 25 13 D2 00 52 01 14 89 36 20 00 00 1F 5F 20 02 20 2F 9F 10 07 06 01 11 03 80 20 00 9F 6C 02 30 00 9F 26 08 33 33 89 D5 70 A3 DF 37 9F 27 01 00 9F 36 02 02 48 90 00
Повторяем вызов GPO 65 536 раз, и карта блокируется. Можно уменьшить количество требуемых вызовов, если предварительно считать текущее значение ATC с помощью GET DATA.
Быстрый способ
Быстрый способ намного удобнее (и опаснее), так как потребует около 2 секунд для блокировки карты, поэтому подробный разбор деталей реализации останется за кадром.
Метод похож на предыдущий, но на этот раз потребуется понизить счётчик попыток ввода PIN-кода, вместо повышения счётчика транзакций. Значение PIN Try Counter также можно получить через GET DATA, но в данном случае существенного прироста по скорости не будет.
Требуемые операции:
- SELECT PPSE // Выбираем бесконтактное окружение
- SELECT APPLICATION // Выбираем приложение
- GET DATA // Узнаём, сколько есть попыток ввода PIN-а
- GET PROCESSING OPTIONS // Начинаем транзакцию
- VERIFY // И вводим неправильный ПИН
- …
- VERIFY // Пока карта не заблокируется
Итоги
В ходе стажировки получилось создать новый инструмент для работы с бесконтактными банковскими картами и решить несколько проблем присутствующих в существующих решениях, а использование дешёвого ридера значительно понижает стоимость исследовательского набора и может поспособствовать привлечению новых людей к исследованию безопасности бесконтактных банковских карт. Исходный код программы доступен на Github.