23.3. Защита программ | Телекоммуникации вчера, сегодня, завтра

Последовательность действий при создании объекта радиосвязи

Бланк формы №1 ТАКТИКО-ТЕХНИЧЕСКИЕ ДАННЫЕ РЭС

Поставка оборудования обеспеченного радиочастотами

Витрина



23.3. Защита программ

Полностью защитить программу от несанкционированного тиражирования, применяя только программные решения, невозможно. Если программа может быть запущена, она может быть взломана.

Однако существуют идеи, способные значительно затруднить работу противника. В середине 2000 года в конференции новостей fido7.ra.crypt было опубликовано сообщение, автором которого являлся человек под псевдонимом stpark. В сообщении перечислялось несколько интересных методов, разработанных специалистами по защите и анализу программ для собственных нужд, не получивших открытой реализации и, возможно, именно поэтому не взломанных. Далее приведены три из них:

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

Еще одна идея, которую собирались реализовать (а может быть, уже и реализовала) компания Protection Technology, заключалась в разработке специального компилятора языка С, который бы создавал код, очень сложный для дизассемблирования.

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


int divFn   (int x)   {  return x /   10;   )

Данная функция выполняет одну-единственную операцию — целочисленное
деление аргумента на 10.

Листинг 23.1. Функция целочисленного делении на 10


mov

есх,

х

mov

еах,

66666667h

imul

ecx

 

mov

eax,

edx

sar

eax,

2

mov

ecx,

еах

shr

ecx,

lFh

add

eax,

ecx

retn

 

 

А вот пример другой функции, ассемблерный код которой приведен в листинге 23.2:


int caseFn (int x)    { return x > 100 ? 15 : 25; }

Эта функция в зависимости от значения аргумента возвращает одно из двух возможных значений.

Листин 23.2 Функции выбора результата по значению аргумента


mov

ecx,

[esp+arg_0]

хог

eax,

eax

стпр

ecx,

64h

setXe

al

 

dec

eax

 

and

al,

0F6h

add

eax,

19h

retn

 

 

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

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



Поиск по сайту


Смотрите также