logo
PASOIB

3.1. Использование недокументированных команд и недокументированных возможностей процессора

Один из методов, используемый для затруднения отладки и дизассемблировании программ, заключается в привлечении редко используемых инструкций процессора, недокументированных инструкций, или инструкций имеющих скрытый результат. В данном случае, не все злоумышленники хорошо знакомы с такого рода командами и скрытыми возможностями процессора. Недостаток данных методов – жесткая привязка к процессору. Кроме этого, не гарантируется поддержка недокументированных инструкций в будущих модулях процессорах, а значит и совместимость с ними разработанных защит.В качестве примеров «сокрытия» реальных инструкций можно привести примеры, приведенные в таблице 2.1.Рассмотрим особенности записи кодов инструкций в процессоре INTEL.

Реализация процессора INTEL предполагает следующий формат инструкций для него:

Префикс | Опкод | ModR / M | SIB | Смещение | Непосредственный операнд

Детальное рассмотрение данной структуры выходит за пределы данного пособия. Остановимся только на механизмах, которые могут быть использованы при защите ПО.Префиксы в кодах инструкций делятся на следующие типы.

Префиксы блокировки и повторения – говорят о том, что код инструкции относится к действиям блокировки или повторения.

Префиксы переопределения сегмента. В данном случае, префикс указывает на то, с каким сегментом должна осуществляться работа команды. Соответствия между значениями префиксов и сегментами приведены в таблице 2.2.

Таблица 2.2. Соответствия префиксов и кодируемых ими сегментов

Значение префикса

Сегмент

2Eh

CS

36h

SS

3Eh

DS

26h

ES

64h

FS

65h

GS

3. Префиксы переопределения размеров операндов (префикс 66h). Данный префикс используется в 16-разрядном режиме для манипулирования с 32-битными операндами и наоборот.

4. Префиксы переопределения размеров адреса (префикс 67h).

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

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

1. В качестве одной из недокументированных возможностей процессора INTEL можно использовать недокументированную возможность использования префиксов, например, префикса переопределения размеров операндов. Согласно стандарту, он используется только при наличии в команде каких-либо операндов. Однако, на практике для реального процессора, данный префикс может быть поставлен совершенно перед любой командой, и это будет работать на реальном процессоре. Например, как это ни странно, реальный процессор воспримет инструкцию 0x66 CLI (использование префикса перед оператором запрещения прерываний). В связи с тем, что данная особенность не документирована (предполагается, что никому в голову писать подобные вещи), , то как правило, отладчики и дизассемблеры не воспринимают подобные инструкции и отказываются корректно интерпретировать данный код.

Тоже самое следует сказать и про префиксы переопределения размеров адреса. Данные префиксы согласно документации используются только в командах, оперирующих с адресами памяти. Их использование в других командах не документировано, но процессор будет эти команды выполнять. В качестве примера такой инструкции можно привести инструкцию 0x67 STI.

Приведенные методы позволяют противостоять, также, и виртуальным эмуляторам процессоров (см. п. 4).

2. Использование префикса переопределения размеров операндов совместно с инструкцией RETN

Достаточно мощным приемом противодействия отладчикам и дизассемблерам, основанным на использовании недокументированных возможностей, является использование префикса переопределения размеров операндов совместно с командой RETN. Казалось бы, раз команда RETN не имеет операндов, то префикс 66 процессор игнорирует, но это не так. Дело в том, что RETN работает с неявным операндом-регистром ip/eip. Именно этот операнд и изменяет префикс. В реальном и 16-разрядном режиме указатель команд всегда обрезается до 16 бит и на первый взгляд все сработает корректно, однако, при записи префикса, стек окажется несбалансированным. Из него вместо одного слова возьмется два. Как правило, это приводит к возникновению исключительной ситуации 0Ch – исчерпание стека. Это приводит к зависанию большинства отладчиков, а дизассемблеры на смогут отследить стек. Однако, данный пример будет работать только в реальном режиме. Под Windows перехватить прерывание 0Ch не представляется возможным.

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

4. Использование дублирующих префиксов, то есть записи префиксов вида 0x66,0x66 непосредственно перед командой. Хотя фирма INTEL не гарантирует корректную работу своих процессоров при обнаружении такого рода инструкций, но фактически все процессоры правильно интерпретируют данные ситуации. Иное дело – отладчики и дизассемблеры, которые спотыкаются и начинают некорректно вести себя.

Следует отметить, также, что процессором INTEL корректно выполняются и инструкции вида DS:FS:CS:Mov ax, [100] (последний префикс перекрывает все остальные), а отладчики и дизассемблеры сбиваются при их анализе. Данный пример хорошо работает под Windows и другими операционными системами.

5. Обращение к недокументированным регистрам. В процессорах INTEL регистры в настоящее время кодируются 3-мя битами следующим образом (таблица 2.3).

Таблица 2.3. Кодирование регистров в инструкциях

Код

Инструкция

000

ES

001

CS

010

SS

011

DS

100

FS

101

GS

110

Зарезервирован

111

Зарезервирован

Две последние кодовые комбинации (110 и 111) в настоящее время зарезервированы и не используются. При попытке их использования вызывается исключительная ситуация 06h, которую можно перехватить (под ДОС). Отладчики же и дизассемблеры при встрече с такого рода инструкциями начинают вести себя странно и непредсказуемо. Одни не генерируют при этом прерывания, чем и выдают себя, другие начинают некорректно работать. Поведение де дизассемблеров в этом случае тоже разнообразно. Ниже приведен пример того, как различные дизассемблера воспринимают такого рода инструкции.

HIEW

8E ???

F8 clc

C3 retn

Qview

8EF8 mov !s, ax

C3 ret

IDA Pro

Db 8E

Db 0F8

DB C3

Несуществующие регистры можно эмулировать в обработчике прерывания int 06h, однако данная защита не будет работать под Win32.

6. Изменение длины команды.

Данный прием направлен на противодействие отладчикам и основан на том, что реальный процессор при выполнении команд вычисляет адрес следующей до выполнения предыдущей. Многие же отладчики, вычисляют адрес следующей команды после выполнения предыдущей (это проще реализуемо). Модифицировав программу в процессе работы (работает только в реальном режиме), можно воспользоваться данной особенностью для реализации защиты.

00000100:

810600010200

add word ptr [0100],01

00000106:

B406

mov ah,06

00000108:

B207

mov dl,07

0000010A:

CD21

int 21

0000010C:

C3

Ret

Эта защита основана на том, что после отработки первой команды, она станет сразу же другой, и, при этом, на байт короче. Реальный процессор, в отличие от многих отладчиков, перейдет не на ячейку 106, а на ячейку 105 и будет выполнять следующий код:

00000100:

810600010200

add word ptr [0100],01

00000105:

00B406B2

add [si] [0B206],dh

С помощью данного способа можно строить защиты на эмуляторы процессоров (см. п. 4).