Pull to refresh

Аппаратная защита программ — это же просто

imageПеред многими разработчиками программного обеспечения возникает вопрос защиты своего труда. Поиск в сети на эту тему выдает кучу информации, но практических примеров и здравых описаний нет. Поэтому в качестве примера построения защиты расскажу о привязке программы к USB ключу.

Ключ

imageДля защиты я выбрал USB ключ с HID интерфейсом и с открытым кодом программного модуля защиты. Такой HID-ключ позволяет обойтись без установки драйверов ключа, что очень удобно для конечного пользователя. Также HID интерфейс прекрасно работает в «SafeMode» режимах загрузки операционных систем.
Открытый код модуля защиты – не нужно линковать библиотеки и прилагать к защищаемой программе чужие *.dll файлы.
Сам ключ содержит 5 участков памяти по 2 кБайта и RTC (часы с батарейкой), каждый участок защищен ПИН-кодом из 16 байт.

Цель защиты

— не запускать приложение при отсутствии ключа защиты;
— предупреждать и закрывать приложение при удалении ключа защиты.
Ограничивать время использования программы будем в следующий раз. Для построения защиты буду использовать один защищенный участок памяти в ключе.

Защищаемый объект

imageДля примера создадим самую простую программу: диалоговое окно MFC, воспользовавшись волшебником visual studio. В наше приложение, кроме потока окна, добавим поток, который в основном окне печатает текущее время.

Подготовка ключа

imageПрежде чем использовать ключ его необходимо сконфигурировать с помощью утилиты из SDK ключа:
— установить на защищенный участок памяти свой секретный ПИН код (заводские значения: {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}).
— заполнить необходимыми данными сам участок памяти.

ПИН я сменил на: {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f}.
Такой же последовательностью заполнил первые 16 байт в памяти ключа.

Установка защиты

imageДля внедрения программного модуля защиты в созданный проект нужно подключить 2 файла: «HID_1.cpp» и «HID_1.h». В них находится исходный код класса, который позволяет обращаться к ключу.
imageВ файле «HID_1.h» можно изменить настройки модуля, и параметры шины USB ключа — оставим все по умолчанию. Компилируем проект.

После успешной компиляции и запуска всё это выглядит так:


image

Теперь все готово для работы с ключом

В коде, где создается само окно приложения, файл «Example_1.cpp», добавляем обращение к ключу:

...
#include "HID_1.h"
...
BYTE my_main_pin[16] = {0x00, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
//ВНИМАНИЕ. Не храните ПИН-код в открытом виде. В примере это сделано для наглядности.
...
CExample_1Dlg dlg;
INT_PTR nResponse;
// -======== Проверка ключа ========-
// Создание класса для работы с ключем.
HID_bridge my_hid(my_main_pin, //Указатель на массив ПИН-кода.
HID_DEFAULT_PROFILE_ID); //Устанвка номера профиля ( профиль 0 )
BYTE my_buf[16]; //буфер, в который будут считаны данные из ключа
int answ = my_hid.read_closed_data_ex(
imagemy_buf, // Указатель на буфер, в который сохранятся принятые данные профиля.
image 0, // Адрес в профиле, с которого начнется чтение, в данном случае чтение с самого начала профиля (с 0-го байта).
image sizeof(my_buf)); // Количество байт, которое нужно прочесть. Размер буфера.
int result = -1;
if(answ == HID_KEY_OK) { // анализируем ответ
image // Если ключ найден и данные считаны - анализируем эти данные
image // Данные, которые должны быть записаны в ключе:
image BYTE data_in_key[16] = {0x03, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
image result = memcmp(data_in_key, my_buf, sizeof(my_buf)); // Сравниваем полученные данные
}
if(!result) {
imagenResponse = dlg.DoModal();
} else {
imagenResponse = NULL;
imageAfxMessageBox("Программа не запускается.");
}
// -======== Проверка ключа END ========-
if (nResponse == IDOK)
{



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

imageДля детектирования отсутствия ключа в системе необходимо периодически обращаться к нему.
Опрос ключа я организовал в отдельном потоке. Поток, при обнаружении отсутствия ключа, устанавливает глобальный флаг, по которому все остальные потоки завершают свою работу. В основном окне запущен таймер, по которому опрашивается глобальный флаг. При установленном флаге окно завершает работу. Пользователь получает сообщение о завершении работы.

Код потока, следящего за ключом

...
volatile bool glob_exit_f = false;
...
DWORD WINAPI myThread_watchdog(LPVOID lParam)
{
imageBYTE my_main_pin[16] = {0x00, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
imageBYTE data_in_key[16] = {0x03, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
imageHID_bridge my_hid(
imagemy_main_pin, //Указатель на массив ПИН-кода.
imageHID_DEFAULT_PROFILE_ID); //Устанвка номера профиля.
imageBYTE my_buf[16]; //буфер, в который будут считаны данные из ключа
imageint result = -1;
imageint answ = -1;
imagewhile(!glob_exit_f) {
imageansw = my_hid.read_closed_data_ex(
imagemy_buf, // Указатель на буфер, в который сохранятся принятые данные профиля.
image0, // Адрес в профиле, с которого начнется чтение, в данном случае чтение с самого начала профиля (с 0-го байта).
imagesizeof(my_buf)); // Количество байт, которое нужно прочесть. Размер буфера.
imageresult = -1;
imageif(answ == HID_KEY_OK) { // анализируем ответ
image// Если ключ найден и данные считаны - анализируем эти данные
image// Данные, которые должны быть записаны в ключе:
imageBYTE data_in_key[16] = {0x00, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
imageresult = memcmp(data_in_key, my_buf, sizeof(my_buf)); // Сравниваем полученные данные
image}
imageif(result) {
imageglob_exit_f = true;
image} else {
imageSleep(WATCHDOG_SLEEP_TIME);
image}
image}
imagereturn 0;
}


В итоге

imageПоставленная задача защиты решена. Обращаю Ваше внимание, что это пример, в котором всё упрощено. Строить настоящую защиту на одном флаге, конечно же, не стоит. А хранить ПИН-код доступа как в примере — вообще преступление.
АПИ ключа намного шире, в данном примере я использовал лишь малую часть всех возможностей.

А что же происходит на шине USB?


imageЛюбой USB сниффер покажет какие устройства подключены к шине данных и чем они обмениваются с компьютером.

image

imageДля меня важным показателем является время ответа ключа на команду. На рисунке «черным» выделена команда к ключу, «синим» — ответ ключа. В среднем время отклика составляет 32 мс (+31… +32 ...). Т.е. за это время ключ обработал команду и вернул ответ. Больших задержек это не вносит.

Криптографическая защита протокола обмена


image

imageHID пакеты на шине USB имеют длину 64 байта. Как сказано в описании ключа первые 32 байта этого пакета технологические, остальные 32 байта – шифрованные алгоритмом RC6, причем для каждой пары пакетов (TX-RX) ключ шифрования выбирается случайно. Этот временный ключ шифруется ключом, созданным на основе ПИН-кода, того самого в 16 байт, о котором написано выше.
Получается, что передача одной и той же команды ключу и получение одного и того же ответа приводит к абсолютно не связанным транзакциям на шине USB. Если кто-то решит анализировать трафик с целью написания табличного эмулятора, то он будет разочарован. Так как пары — (TX-RX) ни разу не повторяются.

Надеюсь, статья будет Вам полезна в столь не легком деле.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.