
Ключ

Открытый код модуля защиты – не нужно линковать библиотеки и прилагать к защищаемой программе чужие *.dll файлы.
Сам ключ содержит 5 участков памяти по 2 кБайта и RTC (часы с батарейкой), каждый участок защищен ПИН-кодом из 16 байт.
Цель защиты
— не запускать приложение при отсутствии ключа защиты;
— предупреждать и закрывать приложение при удалении ключа защиты.
Ограничивать время использования программы будем в следующий раз. Для построения защиты буду использовать один защищенный участок памяти в ключе.
Защищаемый объект

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

— установить на защищенный участок памяти свой секретный ПИН код (заводские значения:
{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 байт в памяти ключа.
Установка защиты


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

Теперь все готово для работы с ключом
В коде, где создается само окно приложения, файл «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(
my_buf, // Указатель на буфер, в который сохранятся принятые данные профиля.
0, // Адрес в профиле, с которого начнется чтение, в данном случае чтение с самого начала профиля (с 0-го байта).
sizeof(my_buf)); // Количество байт, которое нужно прочесть. Размер буфера.
int result = -1;
if(answ == HID_KEY_OK) { // анализируем ответ
// Если ключ найден и данные считаны - анализируем эти данные
// Данные, которые должны быть записаны в ключе:
BYTE data_in_key[16] = {0x03, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
result = memcmp(data_in_key, my_buf, sizeof(my_buf)); // Сравниваем полученные данные
}
if(!result) {
nResponse = dlg.DoModal();
} else {
nResponse = NULL;
AfxMessageBox("Программа не запускается.");
}
// -======== Проверка ключа END ========-
if (nResponse == IDOK)
{
…

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

Опрос ключа я организовал в отдельном потоке. Поток, при обнаружении отсутствия ключа, устанавливает глобальный флаг, по которому все остальные потоки завершают свою работу. В основном окне запущен таймер, по которому опрашивается глобальный флаг. При установленном флаге окно завершает работу. Пользователь получает сообщение о завершении работы.
Код потока, следящего за ключом
...
volatile bool glob_exit_f = false;
...
DWORD WINAPI myThread_watchdog(LPVOID lParam)
{
BYTE my_main_pin[16] = {0x00, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
BYTE data_in_key[16] = {0x03, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
HID_bridge my_hid(
my_main_pin, //Указатель на массив ПИН-кода.
HID_DEFAULT_PROFILE_ID); //Устанвка номера профиля.
BYTE my_buf[16]; //буфер, в который будут считаны данные из ключа
int result = -1;
int answ = -1;
while(!glob_exit_f) {
answ = my_hid.read_closed_data_ex(
my_buf, // Указатель на буфер, в который сохранятся принятые данные профиля.
0, // Адрес в профиле, с которого начнется чтение, в данном случае чтение с самого начала профиля (с 0-го байта).
sizeof(my_buf)); // Количество байт, которое нужно прочесть. Размер буфера.
result = -1;
if(answ == HID_KEY_OK) { // анализируем ответ
// Если ключ найден и данные считаны - анализируем эти данные
// Данные, которые должны быть записаны в ключе:
BYTE data_in_key[16] = {0x00, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
result = memcmp(data_in_key, my_buf, sizeof(my_buf)); // Сравниваем полученные данные
}
if(result) {
glob_exit_f = true;
} else {
Sleep(WATCHDOG_SLEEP_TIME);
}
}
return 0;
}
В итоге

АПИ ключа намного шире, в данном примере я использовал лишь малую часть всех возможностей.
А что же происходит на шине USB?



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


Получается, что передача одной и той же команды ключу и получение одного и того же ответа приводит к абсолютно не связанным транзакциям на шине USB. Если кто-то решит анализировать трафик с целью написания табличного эмулятора, то он будет разочарован. Так как пары — (TX-RX) ни разу не повторяются.
Надеюсь, статья будет Вам полезна в столь не легком деле.