На 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. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    2 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии