Содержание статьи
- Официальные задания
- Как работать с картой
- Training Mission
- Vault Warehouse Management System
- Comparer 2000
- Убежище 42
- SmartCardTanks
- Исследование команд управляющего апплета
- Получение баланса
- Получение статуса выполнения задач на карте
- Получение статуса выполнения логических задач
- Зачисление и снятие OFFCOIN
- Заключение
Чтобы решать задачи, нужно было иметь подходящий картридер. Те, кто пришел на конференцию неподготовленным, имели возможность приобрести картридер в «Лавке старьевщика» за 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»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»