"взломщиком", угадает логику его мышления и представит проблемы, с
которыми тот сталкивается. А для этого ему самому нужно побывать в
роли хакера, почти по системе Станиславского.
ОБЫЧНЫЕ ПРОБЛЕМЫ ХАКЕРА
Проще всего разбирать чужую программу, если она уже распечатана на
бумаге на любом языке высокого уровня (Паскаль, Си и т.д.), но в
крайнем случае сгодится и ассемблер (машинные коды заменены их
мнемоническим изображением). Кстати, в качестве универсального
инструмента рекомендуем отладчик Turbo Debugger (TD), имеющий широкий
сервис и удобный интерфейс.
Естественно, автор программы, предусмотрев это, применил
шифрование или ее разновидность - архивацию. А потому непосредственное
дизассемблирование уже не даст верных результатов, если вообще что-то
даст. Опытный хакер быстро поймет, что вместо текста идет "мусор" и
сразу же начнет поиск средств для снятия шифра. Обычно он достигает
цели, так как рано или поздно программа сама производит нужную
операцию (она ведь не подозревает, что ее запустил хакер). Определив
момент завершения дешифрации, можно "снять" в файл содержимое памяти,
занимаемой уже "нормальной" программой, и, прогнав его дизассеблером,
получить желаемый результат.
Даже если используется поэтапная дешифровка (то есть она разнесена
по времени), полной гарантии защиты нет - дизассемблирование лишь
несколько затянется. Впрочем, когда дешифрацией занимается несколько
подпрограмм и каждая является результатом работы предыдущей, хакеру
предстоит очень нудная и кропотливая работа по их анализу.
Отметим, что шифрование, хотя и не гарантирует полной
безопасности программы, но вынуждает хакера запускать ее отдельные
участки (без алгоритмов, разбираясь "на ходу"), таким образом,
разработчику предоставляется возможность активно вмешиваться в процесс
"взлома" (точнее, поручить это своей программе) и, в первую очередь,
отобрать у хакера самый мощный инструмент - пошаговый режим отладки.
В режиме отладки больше всего забот доставляет стек: его
расположение, размер, варианты применения. Достаточно тонкое его
использование зачастую делает невозможным даже запуск стандартных
отладочных средств. Например, назначение в тело выполняемой задачи:
стековый сегмент совпадает с кодовым, а указатель вершины стека SP
указывает на саму программу. Тогда отработка отладчиком хотя бы одного
прерывания (трассировочного) обязательно сотрет участок размером не
менее 3-х слов. Тем более, что популярные отладчики (TD, CodView и
другие) применяют только пользовательский стек, затирая в нашем случае
коды на большую глубину. Кроме того, старые версии TD имеют
принципиальную ошибку - при начальной загрузке совершенно произвольно
уменьшают стартовое значение указателя стека на 2. Более умеренно
работают со стеком отладчики AFD и PERISCOPE. И наиболее выгодно себя
проявляет обычный DEBUG, поставляемый вместе с DOS.
Переназначение стека в свободную область памяти как средство
борьбы с назначением его в тело программы не каждому "по плечу", тем
более если через него передаются массивы данных из модуля в модуль, да
и он сам активно участвует в работе (как, например, в пакете CONVOY
фирмы "Элиас", осуществляющем через стек разархивацию защищенного
файла). В этом случае корректный проход программы возможен только без
трассировки (то есть пошаговый режим исключен).
Не менее важная и такая же сложная проблема, стоящая перед
хакером, - отслеживание прерываний, перехватываемых исследуемой
программой. Суть в следующем. Все стандартные отладчики для нормальной
работы "забирают" первое и третье из них. Первое (трассировочное)
используется для пошагового режима. Третье необходимо для точек
останова программы по заданным адресам. Защитный механизм обязательно
должен их перехватывать, чтобы предотвратить анализ под отладчиком.
Хакер, если он разобрался с замыслом автора, может либо обойти данный
участок (с не малой долей риска, если прерывание выполняет некоторую
"полезную" функцию), либо изменить подпрограмму обработки прерывания
таким образом, чтобы после ее отработки управление передавалось
отладчику (но неумелое ее исправление тоже чревато...).
Вот далеко не полный перечень того, с чем сталкивается "взломщик"
в своем нелегком труде, только при добывании текста.
ОТПОР АНАЛИЗУ:
НА УРОВНЕ ТЕКСТОВ ...
Сформулируем приемы, мешающие анализу: шифрование и архивирование
(как его разновидность); использование самогенерируемых кодов;
изощренный стиль программирования и многое другое, что сможет
придумать автор.
Шифрование исполняемых файлов - наиболее простое средство для
реализации. Достаточно, например, к каждому байту модуля добавить
некоторую константу, чтобы дизассемблер ничего "не понял".
Предварительное архивирование также не представляет особых
затруднений для хакера, но является более эффективным по сравнению с
шифрованием, так как решает сразу две задачи: уменьшает размер
защищаемого модуля и скрывает код от дизассемблера. Как это делается?
Возьмем файл рисунка. Он содержит поточечное описание всех строк
экрана (цифры - это точки определенного цвета, ноль - ее отсутствие).
Например, рисунок красной линии на экране, состоящей из 200 точек,
записывается в файл в виде 200 байт, а значение каждого байта равно 4
(код для красного цвета). Архиватор заменяет перечисление одинаковых
цифр (эти 200 четверок) элементарным шифром - двумя байтами (первый -
количество повторов, второй - значение цвета), и размер файла резко
уменьшается (в примере - с 200 до 2 байт). Для других типов записей
существуют свои методы сжатия. Большинство из них описано в
литературе, поэтому не будем останавливаться.
Метод самогенерируемых кодов наиболее сложен в реализации, но
очень эффективен. Суть его такова. В программу вложен массив данных,
который сам по себе может быть исполняемым кодом (реально получающим
управление) или смысловым текстом, но после некоторых арифметических
или логических операций преобразуемый в участок программы, выполняющий
иные, не менее важные функции.
Нужно быть виртуозом программирования, чтобы заметить
самомодифицирующуюся ловушку. Ведь при анализе листинга и с явными
алгоритмами разбираться сложно (автор комментарии не оставляет), так
что уж говорить о потайном смысле вроде бы расшифрованного участка.
Впрочем, и новичок-разработчик создать такие ловушки не сможет.
Но коль скоро он взялся за написание защитных механизмов, то ему
придется осваивать изощренный стиль программирования, который
запутывает дизассемблер нестандартной интерпретацией некоторых команд
и нарушает общепринятые соглашения. Например, использование необычной
структуры программы (совмещение стекового и кодового сегментов ).
Интеллектуальные дизассемблеры, как правило, это плохо воспринимают. А
перекрестные вызовы процедур, многократные переходы из модуля в модуль
и увеличение количества точек входа в них - не позволяют выявить
блочную структуру программы.
Замена команд переходов, вызовов подпрограмм и прерываний
направляет дизассемблер по ложному следу. Здесь вместо стандартного
оператора вставляется группа других, в конечном счете выполняющих то
же самое. Для неискушенных программистов на рисунке 4.1 (а-д)
приведены такие варианты. Впрочем, для аналогичных эффектов достаточно
в команде перехода изменить значение операнда (примеры е-ж). И еще
проще модифицировать косвенные переходы (з-и).
Кстати, и саму команду можно модифицировать. Например, на рис. 4.1
(к) дизассемблер по адресу m: покажет PUSH AX (запись регистра AX в
стек), поскольку этот байт имеет код 50h (01010000b), но перед ее
выполнением один бит (4-й) меняется, в результате получается INC AX с
кодом 40h (01000000b, увеличение содержимого регистра AX на 1) - то
есть совсем другая команда, не отраженная в листинге.
Кропотливая работа преобразует участок до неузнаваемости. А
"умный" дизассемблер (например, Sourcer), отслеживая "явную" передачу
управления (в другое место), не найдет спрятанный блок и,
следовательно, не будет дизассемблировать его.
... И В РЕЖИМЕ ОТЛАДКИ.
Опытный хакер, если ему не надоест разбираться с защитой, будет
неоднократно прогонять непонятые куски программы отладчиком. Некоторые
приемы борьбы с этим уже рассматривались.
Очень эффективное средство от пошагового выполнения программы -
назначение стека в ее тело. А если там находятся данные для работы, то
режим отладки усложняется. К тому же частое изменение местоположения
стека, поверьте на слово, измотает хакера окончательно.
Напомним, что при пошаговом режиме хакеру рекомендовалось обходить
участки, перехватывающие 1-е прерывание. Так вот, чтобы он не смог
воспользоваться этим советом, нужно поручить подпрограмме обработки
прерывания некоторую полезную функцию. Например, генерацию кодов или
дешифрацию.
Напомним также традиционные методы защиты [1].
Программа должна подсчитывать и проверять контрольные суммы своих
участков для определения "контрольных точек" или "точек останова",
расставленных хакером. Дело в том, что стандартное применение 3-го
прерывания предусматривает запись кода его вызова вместо байта
программы, а это меняет контрольную сумму. Периодическая же проверка
вовремя проинформирует программу о замене "родных" байт "чужими", то
есть о попытке исследования программы под отладчиком.
Известно, что выполнение программы в режиме трассировки
значительно уменьшает ее быстродействие. Поэтому по времени "прохода"
отдельных кусков защищаемого ПО можно определить работу под
отладчиком. По таймеру (например, запустив его 2-й канал) заранее
просчитывают скорость выполнения некоторого участка и сравнивают его
со значением, полученным в ходе работы программы. Если есть
существенное расхождение в полученных результатах, то можно сделать
вывод о выполнении данного участка под контролем. Из недостатков
метода отметим лишь разное быстродействие процессоров на разных ПЭВМ,
которое следует учитывать.
Интересный способ выматывания "исследователя" - применение
достаточно больших процедур, производящих некоторую сложную и, на
первый взгляд, важную работу, но на самом деле, не имеющих никакого
отношения к логике работы программы, так называемых "пустышек". Для
лучшей имитации их важности можно включать в них перехват 13h, 21h,
25h и 26h прерываний (обслуживающих ввод-вывод информации на внешние
устройства), что, безусловно, заинтересует хакера.
Оригинальный способ защиты ПО от исследования, примененный в
пакете COPYLOCK, использует конвейер шины данных микропроцессора. На
рис. 4.2 изображен его фрагмент. (Надеемся, что не навлечем на себя
гнев цивилизованных пользователей, афишируя некоторые тонкости
программы: все равно она безнадежно устарела, да и не вскрыта лишь
самыми ленивыми. А начинающим специалистам рекомендуем ознакомиться с
ее работой. Полный листинг опубликован в электронном журнале "НСК",
N1, 1992 г.). Фрагмент дан со значением смещений относительно кодового
сегмента, чтобы читатель смог увидеть, как команда REP STOSW в
подпрограмме SUBR затирает значением из регистра AX область ОЗУ, в
которой находится и сама подпрограмма. Тем не менее, SUBR нормально
отрабатывает и возвращает управление в точку вызова (но только не в
пошаговом режиме).
Трюк очень прост: так как длина конвейера не менее 4-х байт, то,
очевидно, команды, расположенные за REP STOSW, уже находятся в нем до
ее выполнения, что и обеспечивает нормальную работу подпрограммы даже
после затирания ее кода в ОЗУ. Выполнение же по одному шагу (то есть
по трассировочному прерыванию) нарушает очередность засылки кодов в МП
и приводит к непредсказуемому результату.
Пример на рис. 4.3 демонстрирует более изящное использование
конвейера. Он определяет - идет ли выполнение программы с трассировкой
или нет, и осуществляет ветвление (команда JMP с меткой m:) в