You are here:
  • Increase font size
  • Default font size
  • Decrease font size

Diakom-Auto

Ремонт и настройка диагностических приборов.

Как нужно и не нужно защищать программы (на примере базы Wise Mercedes).

PDF
(на примере базы Wise Mercedes)

 

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

Пример экрана установки.

Как быть, базу хочется, а денег на ключ нет. Решено было посмотреть, что там за защита.

Посмотрели оказалось, что интерпретатор выполняет команды записанные в файле настроек. Начали с простейшего дизасемблировали инсталяшку. Два часа шел процесс на P166. Затем начали искать. И нашли функцию, которая переваривает введенное число и выдает правильный ключ или нет. Раз нашли значит надо патчить и запатчили - любое число стало правильным.

Поставили, запустили - ругается, говорит мол ключ неверный. Хорошо стали смотреть дальше уже основную программу. Опять длительный процесс дизасемблирования - ищем, где же она проверяет правильность. Нашли опять запачили. Запустили, показывает, но не все. Где проблема ?:.

Решили заняться детальным анализом кода проверки. Что же выяснилось. Ключ проверяется примерно таким кодом:

void getdatafromkey()

{

int i,i1;

expandkey();

// тут происходит превращение введенного ключа в 28 байтную

// последовательность

for(i=0;i<28;i++)

key1[i]=key1[i]^0xAA;

key1[28]=key1[28]^0x2A;

// Ну это тривиальный способ спрятать данные.

copy();

// А вот тут происходит перемешивание. Создалось впечатление, что кто-то начитался книжек по основам криптографии и пытался, чего-то наваять.

i=checkkey();

i1=(key1[33]<<8)+key1[32];

// это контрольная сумма ключа (попытки реализовать CRC ?)

if (i==i1) printf("Check OK! \n");

else printf("Check Error! (%04X/%04X) \n",i,i1);

// Вот таки образом проверялся ключ в инсталяшке.

// Этого кода уже не было в инсталяционной программе.

copy2data();

printf("App ID: %04X \n", (data[1]<<8)+data[0]); // Тут должно быть 1 или 3

printf("Type : %04X \n", (data[0x15]<<8)+data[0x14]); // тут разрешенные языки

printf("Count : %04X \n", (data[0x19]<<8)+data[0x18]);

printf("Left : %04X \n", (data[0x19]<<8)+data[0x18]);

// эти два поля в проге одинаковые, но менеджер лицензий понимает их по-разному. Общее количество лицензий и оставшееся количество лицензий.

printf("Exp.D.: %04X \n", (data[0x25]<<8)+data[0x24]);

// ну а это вообще прозрачно - дата окончания работы, количество дней от 1970 года.

printf("Lan ID: 0800 %04X %04X \n",(data[0x21]<<8)+data[0x20],(data[0x1D]<<8)+data[0x1C]);

// А вот это самое интересное - вот это и есть то самое число, которое показывается как LanID в стартовом окне. А точнее его младшие 8 цифр. Так как 4 старших, постоянны.

}

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

Смотрим, что делается expandkey();

void expandkey()

{

int i,i1;

int ch;

int len=0;

memset(arr,'0',232);

for(i=0;i<46;i++)

{

ch=code(key[i]);

// тут происходит обычная подстановка по статической таблице.

if (ch==-1) ch=0;

to5(ch);

for(i1=0;i1<5;i1++,len++)

arr[i*5+i1]=digi5[i1];

}

for(i=0;i<36;i++)

key1[i]=from8(&arr[i*8]);

}

Процедура to5 представляет число в пяти разрядный двоичный код. А процедура from8 наоборот из текстового представления преобразует в 8 разрядный двоичный код. Этим они уменьшают избыточность. К слову похожий алгоритм используется в популярных программах uuencode.

Процедура copy();

void copy()

{

int i,i1;

char tkey[37];

memset(tkey,0,36);

for(i=0;i<11;i++)

memcpy(&tkey[dst[i]],&key1[src[i]],src[i+1]-src[i]);

memset(key1,0,36);

memcpy(&key1[4],&tkey[4],4);

memcpy(&key1[12],&tkey[12],4);

memcpy(&key1[20],&tkey[20],4);

memcpy(&key1[28],&tkey[28],4);

key1[0]=tkey[0];

key1[1]=tkey[1];

key1[8]=tkey[9];

key1[9]=tkey[8];

key1[16]=tkey[17];

key1[17]=tkey[16];

key1[24]=tkey[25];

key1[25]=tkey[24];

key1[26]=tkey[27];

key1[27]=tkey[26];

key1[32]=tkey[33];

key1[33]=tkey[32];

}

Тут они делают перемешивание. Сходу становится видно, что всего 12 байт из 36 важны для ключа, а остальные представляют собой обычный мусор.

Далее они проверяют правильность полученных данных.

int checkkey()

{

int i;

int t=0;

unsigned int t1;

for(i=0;i<(30*8);i++)

{

t1=1<<(i%8);

if ((key1[i>>3]&t1)!=0) t++;

}

return(t);

}

Легко понять, что это не самый оптимальный алгоритм проверки. Так как возможно много вариантов изменения содержимого, при неизменности контрольной суммы.

В вот теперь начинается самое интересное. Начали все это обращать. Результатом реверсного программирования явилась программка, которая может по заданным параметрам генерирует правильный ключ. Программку вы можете взять у нас на сайте здесь, а полный исходный текст со всякими разными извратами - здесь.

Теперь рассмотрим основные ошибки защитников J

  1. И самый главный не было сделано, ни одной попытки как либо спрятать код программы, защитив, хотя бы от дизасемблирования, не говоря уже о том, что бы защититься от отладки.
  2. Создалась впечатление, что использовался стандартный модуль лицензирования, что-то типа FLEX. Это позволит создавать валидные ключики и на следующие версии программы.
  3. В блоке лицензирования использовались примитивные алгоритмы, допускающие обращение. Сейчас перспективными являются системы с открытым ключом длиной хотя бы 64-128 бита.
  4. Слабая проверка целостности ключа.

Список можно продолжать. Но если у вас появилось желание защитить свою программу надежно, лучше не использовать модуль лицензирования использованный в этой программе.

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