Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #48, НОЯБРЬ 2004 г.

Немой укор за компьютерный хардкор

Крис Касперски

Спецвыпуск: Хакер, номер #048, стр. 048-058-3


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

Принципы построения самомодифицирующегося кода

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

При прогоне программы на живом процессоре инструкция INC AL заменяется NOP’ом, однако, поскольку INC AL уже находится на конвейере, регистр AL все-таки увеличивается на единицу. Пошаговая трассировка программы ведет себя иначе. Отладочное исключение, сгенерированное непосредственно после выполнения инструкции STOSB, очищает конвейер, и управление получает уже не INC AL, а NOP, вследствие чего увеличения регистра AL не происходит! Если значение AL используется для расшифровки программы, то отладчик скажет хакеру: [censored]! Процессоры семейства Pentium отслеживают модификацию команд, уже находящихся на конвейере, и потому программная длина конвейера равна нулю. Как следствие, защитные механизмы конвейерного типа, попав на Pentium, ошибочно полагают, что всегда исполняются под отладчиком. Это вполне документированная особенность поведения процессора, которая сохранится и в последующих моделях. Использование самомодифицирующегося кода с формальной точки зрения вполне законно. Однако следует помнить, что злоупотребление им отрицательно влияет на производительность защищаемого приложения. Кодовый кэш первого уровня доступен только на чтение, и прямая запись в него невозможна. При модификации машинных команд в памяти, в действительности модифицируется кэш данных! Затем происходят экстренный сброс кодового кэша и перезагрузка измененных кэш-линеек, на что расходуется достаточно большое количество процессорных тактов. Никогда не выполняй самомодифицирующийся код в глубоко вложенном цикле, если конечно, ты не хочешь затормозить скорость своей программы до скорости асфальтового катка.

Ходят слухи, что самомодифицирующийся код возможен только в MS-DOS, а в Windows - нет. Доля истины в этом есть, но при желании мы можем обойти все запреты и ограничения. Прежде всего, разберемся с атрибутами доступа к страницам и сегментам. Процессоры x86 поддерживают три атрибута для доступа к сегментам (чтение, запись и исполнение) и два – к страницам (доступ и запись). Операционные системы семейства Windows совмещают кодовый сегмент с сегментом данных в едином адресном пространстве, а потому атрибуты чтения и исполнения для них полностью эквивалентны.

Исполняемый код может быть расположен в любой доступной области памяти – стеке, куче, области глобальных переменных и т.д. Стек с кучей по умолчанию доступны для записи и вполне пригодны для размещения самомодифицирующегося кода. Константные глобальные и статические переменные обычно размещаются в секции .rdata, доступной только на чтение (и, разумеется, на исполнение), и всякая попытка их модификации завершается исключением.

Назад на стр. 048-058-2  Содержание  Вперед на стр. 048-058-4