Взлом

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


На 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 и флаг.

Источник: xakep.ru

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *