Данная статья посвящена защите исполнимого кода от отладки. Для чего эта защита
необходима? Рассмотрим самый распространённый пример. Вы разработчик
программного обеспечения написали какой-то продукт пользующийся спросом на
рынке, защитили его традиционной комбинацией - имя пользователя, 
идентификационный код. Но уже через неделю - месяц вы будете удивлены
количеством программ в Интернете, написанных с целью снятия вашей защиты,
причём довольно успешно работающих. Чтобы такая ситуация с вами не случилась
необходимо предусмотреть защиту от отладки и дизассемблирования ваших программ,
тогда снятие защиты с вашей программы очень сильно усложнится.
И цель данной статьи защитить ваши программы от взлома.

В начале мы рассмотрим простейшую атакуемую программу и варианты защиты её
от отладчиков.

Рассмотрим пример атакуемой программы:

#include <stdio.h> // Подключаемые библиотеки
#include <string.h>
#include <stdlib.h>

bool test(char *name1,char *pass1) // Функция проверки корректности пароля
{
int a,i,z;
char str1[300]="";
char valid_pass[300]="";

a=strlen(name1);
for (i=0;i<=a-1;i++)
{
z=(int)name1[i];
z=z+33;
itoa(z,str1,10);
strcat(valid_pass,str1);
}
if (strcmp(pass1,valid_pass)==0) // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
return true;
return false;
}
void main() // Основная функция
{
char name[1024];
char pass[1024];

printf("Enter user name: ");
scanf("%50s",name);
printf("Enter registration code: ");
scanf("%250s",pass);

if (!test(name,pass)) // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
{
printf("Invalid user name or registration code 🙁 \n");
exit(0);
}
printf("Registration succesfull 😉 \n");

}

Данная программа в начале получает от пользователя имя пользователя и
регистрационный код. Затем в функции test() происходит преобразование имени
пользователя по определённому алгоритму (к значению символа прибавляется 33).
После преобразования получается строка, которая содержит корректный
регистрационный ключ для данного имени пользователя, в конце функции
проверяется, соответствует ли введённый пользователем регистрационный код только
что сгенерированному, если да, то функция возвращает 1 иначе 0. От результата
функции зависит финальное сообщение, успешно ли прошла регистрация или нет. 

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

Какие существуют возможности защиты от отладчика? Первый и самый простой
способ использовать предоставленную разработчиками Windows функцию.
В операционных системах Windows, начиная с Windows NT существует функция
IsDebuggerPresent. Данная функция аргументов не получает, а в качестве
результата возвращает 1, если отладчик обнаружен и 0 в противном случае.

Использовать её довольно легко:

if (!IsDebuggerPresent()) goto no_debugger
//........................................
no_debugger:

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

mov eax,fs:[018h]
mov eax,[eax+30h]
movzx eax,byte ptr [eax+02]
ret

Для нас существенны только первые три, мы можем из них построить свою функцию,
аналогичную IsDebuggerPresent.

Перейдём к листингу:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

int debug1() // Функция проверки на присутствие
{ // Отладчика, она возвращает 1 если
asm mov eax,fs:[018h] // отладчик есть и 0, если его нет
asm mov eax,[eax+30h]
asm movzx eax,byte ptr [eax+02]
}
bool test(char *name1,char *pass1)
{
int a,i,z;
char str1[300]="";
char valid_pass[300]="";

a=strlen(name1);

for (i=0;i<=a-1;i++)
{
z=(int)name1[i];
z=z+33;
itoa(z,str1,10);
strcat(valid_pass,str1);
}

if ((strcmp(pass1,valid_pass)==0))
return true;
return false;
}
void main()
{
char name[1024];
char pass[1024];
int a;

printf("Enter user name: ");
scanf("%50s",name);
printf("Enter registration code: ");
scanf("%250s",pass);

if (debug1()||!test(name,pass))
{
printf("Invalid user name or registration code 🙁 \n");
exit(0);
}
printf("Registration succesfull 😉 \n");

}

Наша программа была изменена таким образом, что даже при вводе правильного
пароля в контексте отладчика она будет сообщать, что он не верен.
Это будет происходить благодаря нашей функции debug1() и дополнительной
проверке условия.

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

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

(Продолжение следует)

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии