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

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

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

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

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

2 комментария

  1. Vindwo

    21.12.2018 at 17:01

    У всех есть папочка, которая начинается с !!!__ на компе 🙂

  2. Vindwo

    21.12.2018 at 17:03

    А что там за ридер был? Мне мужик в лавке сказал, что какой-то супер-пупер хацкерский. Но, к сожалению, в тот момент их уже разобрали. Даже взглянуть не успел.

Оставить мнение

Check Also

Рекламную сеть скомпрометировали ради MageCart-атак на онлайновые магазины

Эксперты обнаружили появление новой хак-группы, практикующей атаки MageCart. Группировка с…