Как стать автором
Обновить

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

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) ни разу не повторяются.

Надеюсь, статья будет Вам полезна в столь не легком деле.
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.