Следующая новость
Предыдущая новость

Поиграем в карты? Полное прохождение заданий со смарт-картами OFFZONE Badge Challenge

20.12.2018 13:32

Содержание статьи

  • Официальные задания
  • Как работать с картой
  • Training Mission
  • Vault Warehouse Management System
  • Comparer 2000
  • Убежище 42
  • SmartCardTanks
  • Исследование команд управляющего апплета
  • Получение баланса
  • Получение статуса выполнения задач на карте
  • Получение статуса выполнения логических задач
  • Зачисление и снятие OFFCOIN
  • Заключение

На OFFZONE была серия задач на смарт-карте с интерфейсом ISO/IEC 7816. Особо внимательных даже предупредили заранее. Тут опубликовали фотографию бейджа и прямо заявили, что «бейдж можно будет взломать, если вам это будет по силам». Нам оказалось это по силам, и из этой статьи ты узнаешь — как. 🙂

Чтобы решать задачи, нужно было иметь подходящий картридер. Те, кто пришел на конференцию неподготовленным, имели возможность приобрести картридер в «Лавке старьевщика» за OFFCOIN или российские рубли. Правда, продавалось всего 30 ридеров, и их быстро разобрали.

Описание задач было приведено на этой странице.

Официальные задания

Авторы заданий сделали очень хорошее «краткое введение» в терминологию, базовые принципы и популярные инструменты.

Как работать с картой

Общение со смарт-картами ведется при помощи USB-картридеров, которые можно найти в GAME.ZONE, а также купить в «Лавке старьевщика».
APDU-команда

Пакеты, которые воспринимает карта, называются APDU-командами. APDU-команда представляет собой последовательность 4-байтового заголовка и данных команды.

Общий формат APDU-команды:

  • [CLA INS P1 P2 Lc DATA Le]
  • CLA (Instruction class) определяет тип посылаемой команды.
  • INS (Instruction code) определяет конкретную команду внутри класса.
  • P1, P2 (Parameter 1/2) являются аргументами команды.
  • Lc (Length of command data) определяет размер данных в поле DATA.
  • DATA (?) содержит в себе Lc байт данных команды.
  • Le (Length of expected data) определяет размер данных, которые ожидается получить в ответе карты. Команда обязательно содержит 4 первых байта, остальные байты опциональны.

В ответ мы получаем код ошибки, состоящий из 2 байт (SW1, SW2), и опциональное поле данных размера <= Le байт.

Для отправки APDU-команд удобно пользоваться библиотекой pyscard для Python. Например, так выглядит код для получения серийного номера Java-карты:

from smartcard.System import readers from smartcard.util import * r = readers() reader = r[0x00] # Let's assume that we only have one reader connection = reader.createConnection() connection.connect() SELECT_MANAGEMENT = [0x00, 0xA4, 0x04, 0x00, 0x08] + [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00] (data, sw1, sw2) = connection.transmit(SELECT_MANAGEMENT) GET_CPLC_DATA = [0x80, 0xCA, 0x9F, 0x7F, 0x00] (data, sw1, sw2) = connection.transmit(GET_CPLC_DATA) print data 

Если давать высокоуровневое описание Java-карты, то она представляет собой совокупность Java-апплетов, которые выполняют определенные задачи. Апплеты идентифицируются с помощью уникального Applet Identifier (AID), задаваемого при разработке апплета. По умолчанию Java-карта должна содержать Manager-апплет (AID = [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00]), который используется для управления другими апплетами на карте. Задания также представляют собой отдельные апплеты. Перед началом работы с апплетом нужно выполнить команду SELECT и AID апплета (AID=[…]; SELECT_APPLET = [0x00, 0xA4, 0x04, 0x00] + [len(AID)] + AID).

В качестве основного способа общения с картой предполагалось использовать язык Python и библиотеку pyscard. Небольшая сложность, с которой лично я столкнулся на начальном этапе, была в том, что я не нашел готового инсталлятора и не справился со сборкой pyscard под Python 3.7. А после установки старой версии pyscard 1.7.0 под Python 2.7 выяснилось, что простейшие примеры из документации pyscard падают с ошибкой. Зато связка pyscard 1.9.3 и Python 3.6.7 x64 заработала сразу и без нареканий.

Так как во всех заданиях требовалось выполнять однотипные действия (инициализация, отсылка APDU-команд и получение результатов), мне показалось правильным реализовать базовые функции в виде класса.

from smartcard.System import readers  def hx(ab): return ".".join("%02X" % v for v in ab) # Convert bytes to hex def sx(ab): return "".join(chr(v) for v in ab) # Convert bytes to string  SELECT_APPLET = [0x00, 0xA4, 0x04, 0x00]  class OFFZONE(object):   dCmds = { # List of known commands (for debugging)     hx(SELECT_APPLET): "SELECT_APPLET",   }    def __init__(self, ind=0):     self.DBG = False     self.r = readers()     if self.DBG:        if self.r:         print("Readers:")         for i, v in enumerate(self.r):           print("%3d: %s" % (i, v))       else: print("No readers")      self.conn = self.r[ind].createConnection()     self.conn.connect()    def exch(self, cmd, arg=None): # Perform APDU command exchange     if self.DBG:       msg = [self.dCmds.get(hx(cmd), hx(cmd))]       if arg is not None: msg.append(hx(arg))       print("Send: %s" % " + ".join(msg))     ext = [] if arg is None else [len(arg)] + list(arg)     data, sw1, sw2 = self.conn.transmit(cmd + ext)     if self.DBG:       print("Recv: %02X.%02X + [%s]" % (sw1, sw2, hx(data)))     return data, sw1, sw2    def select(self, AID): # Select applet by AID or task index     if isinstance(AID, int): # Task index provided       AID = [0x4F, 0x46, 0x46, 0x5A, 0x4F, 0x4E, 0x45, 0x30+AID, 0x10, 0x01]     return self.exch(SELECT_APPLET, AID)    def command(self, cla, ins, arg=None): # Execute command     cmd = [cla, ins, 0, 0]     return self.exch(cmd, arg)  def main():   oz=OFFZONE() ##  Task1(oz) ##  Task2(oz) ##  Task3(oz) ##  Task4(oz) 

Training Mission

Получите основные навыки для работы с Java-картой.
Похоже, при переносе описания задания затерлась часть информации, и мы потеряли корректный номер INS.

AID: [0x4f, 0x46, 0x46, 0x5a, 0x4f, 0x4e, 0x45, 0x31, 0x10, 0x01] CLA: 0x10 INS:     0x??: getFlag: [0x10, 0x??, 0x00, 0x00]     0xE0: checkFlag(flag): [0x10, 0xE0, 0x00, 0x00, 0xNN] + flag (0xNN bytes of flag) 

В первой задаче требовалось найти однобайтовое значение INS, и очевидно, что проще всего это было сделать перебором 256 возможных вариантов.

Решение

def Task1(oz):   print("nTask #1 (Training Mission)")   oz.select(1)   CLA = 0x10   INS_CheckFlag = 0xE0    for INS_GetFlag in range(0x100):     data, sw1, sw2 = oz.command(CLA, INS_GetFlag)     if 0x90 == sw1 and 0x00 == sw2 and data:       print("INS_GetFlag=0x%02X, Flag: %s" % (INS_GetFlag, repr(sx(data))))       data, sw1, sw2 = oz.command(CLA, INS_CheckFlag, data)       print("CheckFlag: %02X.%02X + [%s]" % (sw1, sw2, hx(data)))       break   else:     print("INS_GetFlag value not found") 

Легко заметить, что на большинство запросов (с неподдерживаемыми значениями INS) в качестве статуса ответа (sw1 и sw2) возвращаются значения 0x6D и 0x00. Если свериться с таблицей кодов APDU-ответов, становится понятно, что 6D 00 соответствует ошибке Instruction code not supported or invalid. Для правильного запроса возвращается статус 90 00 и флаг.

Продолжение доступно только подписчикам

Материалы из последних выпусков можно покупать отдельно только через два месяца после публикации. Чтобы продолжить чтение, необходимо купить подписку.

Подпишись на «Хакер» по выгодной цене!

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

1 год

7190 р.

Экономия 1400 рублей!

1 месяц

720 р.

25-30 статей в месяц

Уже подписан?

Источник

Последние новости