J E9 EC EF JMP F125

2298;0139j F2 REPNE

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

Вся загвоздка именно в "рассеянии" инструкций защиты среди не относящих­ся к ней команд. Рассмотрим подробнее следующий фрагмент:

PUSHF

MOV AX,2577h

MOV ВР,SР

LEA DX,NewlntOx77

INC Byte ptr [BP+I]

INT 21h

POPF

Выделим следующую последовательность инструкций:

POSHF

MOV BP,SP

INС Byte ptr [BP+I]

POPF

Нетрудно видеть, что она устанавливает флаг трассировки. Однако это неочевид­но для отладчика, особенно если учесть, что эти четыре команды могут быть разбросаны по сотне-другой килобайт кода. Требуется чертовски нетривиальный алгоритм, который мог бы отследить значения всех регистров в произвольной точке. К тому же флаг трассировки можно установить десятками способов. Например, при выходе из прерывания, используя сохраненный в стеке регистр флагов.

Кроме того, ни в коем случае не надо делать тривиальную проверку типа

TEST [ВР+11,1

JHZ under_debuger

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

Как с этим жить хакеру? Ответ все тот же: используйте дизассемблеры и эмулирующие отладчики. И все усилия разработчиков защиты пойдут прахом. Сегодня можно с уверенностью сказать, что все антиотладочные приемы стали неактуальны. И просто непостижимо то упорство, с которыми некоторые разра­ботчики тратят свое время и силы на борьбу с отладчиками реального режима, которые уже давно никто не использует.

Кроме того, нормальная операционная система просто не позволит прикладной программе манипулировать с флагом трассировки. Совсем недавно это было не так. Защита могла не только читать, но и сбрасывать флаг трассировки, тем самым выходя из-под контроля отладчика. Заслуживает уважения, что разработ­чики без всякой поддержки со стороны процессора все же научились защищаться от агрессивных приложений. Однако все же редкие отладчики позволяют про­грамме трассировать саму себя. А защиты это могут активно использовать. Например, для расшифровщика или интерпретатора. К последнему мы еще вернемся, а пока рассмотрим следующую программу (crackl6):

Repeat:

LODSW

MOV [SI-2] ,BX

LOOP Repeat

He правда ли, малопонятный цикл? На самом деле это часть расшифровщика. А другая его часть хитро спрятана в обработчике Int 0х1:

NewIntOlh:

XOR AX,9FADh

MOV BX,AX

IRET

T.e. на самом деле расшифровщик полностью выглядит так:

Repeat:

XOR AX,9FRDh

MOV BX,AX

LODSW

XOR AX,9FADh

MOV BX,AX

MOV {SI-2],BX

XOR AX,9FADh

MOV BX,AX

LOOP Repeat

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


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

Рассмотрим некоторые из них. Неочевидно с первого взгляда, но практически все отладчики перехватывают часть исключений с целью предотвращения некор­ректной работы программ. Это в первую очередь int 0х6 (неверный опкод) и int 0х0 (деление на нуль или переполнение). Ничего не стоит построить защиту, активно использующую эти ресурсы. Например, для расширения существующих команд микропроцессора. При этом не будет существовать никакого способа заставить эту защиту работать под отладчиком, который самостоятельно перехва­тывает эту исключительную ситуацию и блокирует работу.

Однако не стоит принимать вышеизложенное как руководство к написанию защит подобного типа. Напротив, я всячески призываю этого не делать. Ведь не только отладчик перехватывает исключение "неверный опкод", но и менеджеры расширенной памяти (ernm386, qemm), операционная система Windows, да мало ли еще кто. В любом случае вашему клиенту не понравится, если программу придется запускать в "голой" MS-DOS или отказываться от использования Windows и драйверов расширенной памяти.

Гораздо больше подходит на эту роль переполнение при делении. Эта исклю­чительная ситуация признана довольно рядовой, и любая операционная система позволяет приложениям обрабатывать ее самостоятельно. В самом деле, иначе было бы невозможно существование математических программ. Как бы вам понравилось, если бы при попытке деления на ноль в электронной таблице Windows, сообщив о некорректном поведении приложения, закрывала бы его? Вообще непонятно, почему отладчики "взъелись" на это исключение и принялись обрабатывать его независимо от приложений. Самое интересное, что этим грешат даже некоторые эмулирующие отладчики!

Самое сложное в использовании этого приема — найти такой алгоритм, который не позволял бы обойти его непосредственным вызовом int 0. Т.е. если в теле защиты есть некая инструкция DIV, которая вызывает обработчик исключе­ния, то ничего не стоит заменить ее на непосредственный вызов прерывания int хх, предварительно переустановив обработчик на любое свободное значение.

Рассмотрим следующий пример, где подобная замена просто невозможна (взят из реальной защиты):

NewintOOh:

ADD SI,AX

CBW

ADD SP,6

Repeat:

LODSW

DIV AН

STOSB

LOOP Repeat

Зашифрованный фрагмент содержал помимо всего прочего инструкции деко­дирования. Изучите защиту внимательно еще раз, и вы поймете, что это так. Внешне расшифровщик очень прост. Пара чисел а и b расшифровывается как целая часть от а/Ь. Но если b равно нулю, тогда а интерпретируется как указатель на следующую расшифровываемую инструкцию. Т.е. декодер может "прыгать блохой" и одновременно разжимать текст, фактически реализуя LZ-pac-паковку. Дешифровщик и распаковщик в одном флаконе и в семи ассемблерных командах — не правда ли, результат, которым можно гордиться?

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

Замечу, что приведенный фрагмент защиты был разработан специально для Win32 и успешно работал в Windows NT- Под NT существует немного отладчиков и все известные мне (по крайней мере на момент написания книги) перехватывали исключение деления на нуль и блокировали работу программы,

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