Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР 130, ОКТЯБРЬ 2009 г.

Энциклопедия антиотладочных приемов

Леонид «Cr@wler» Исупов (crawler@xakep.ru)

Процесс отладки защищенной программы порой напоминает прохождение хитроумного квеста. Одно из самых больших удовольствий для реверсера (иногда - и большая головная боль) - заниматься преодолением защит, которые «завязаны» на обработке исключений. Если ты еще не знаком с принципом защитных механизмов, построенных на его основе, все, что изложено ниже, тебе очень пригодится. «На закуску» - еще один защитный трюк: использование одной интересной особенности работы функции vsprintf (), входящей в msvcrt, которая буквально «убивает» отладчик OllyDbg.

«Исключительная» защита

Часто в процессе работы программы возникают ситуации, которые невозможно предусмотреть. Например, попытка записи в ячейку памяти, которая принадлежит странице с неустановленным атрибутом «writeable», или же деление на ноль. Для таких ситуаций программисты Microsoft создали механизм обработки исключений.

Обработчик исключений, или SEH (англ. «Structured Exception Handling») - часть кода, на которую возложена функция обработки ошибок для данного треда. Нужно пояснить, что представляет собой тред ((от англ. «thread», нить). Думаю, ты знаешь, что код программы может быть «распараллелен», то есть несколько частей программы могут выполняться одновременно – в контексте единственного процесса (например, в графическом редакторе одновременно могут выполняться печать изображения и его редактирование). Каждая такая часть программы и называется тредом. При этом для каждого треда может быть установлен собственный обработчик исключений.

По умолчанию обработкой исключений занимается системный обработчик. Каким образом установить обработчик собственный?Указатель на структуру, содержащую адрес обработчика (который также совпадает и с адресом указателя на сам обработчик), находится по адресу FS:[0]. Следовательно, для того, чтобы заменить системный обработчик собственным, необходимо поместить в стек структуру, содержащую адрес нового обработчика, и указатель на старый обработчик. После чего поместить по адресу FS:[0] указатель на новую структуру.

Не будем терять время, рассмотрим на практике один из антиотладочных приемов. Если ты читал предыдущий выпуск журнала, то помнишь, что мы исследовали небольшую программу-«дрозофилу» (воспользуемся термином, который иногда в своих статьях использует Крис), написанную на ассемблере. Ее можно найти на нашем диске; точка входа располагается по адресу 0x0401000, а код, который выдает окошко с надписью «Hello, World!», имеет размер всего 26h байт. Соответственно, начиная с адреса 0x401026, располагается «выравнивающий» секцию массив нулевых байтов.
Разберем следующий код, который внесен в рассмотренную нами программу-«дрозофилу» «ex.exe» при помощи отладчика OllyDbg (не забывай, что код вносится, начиная с адреса 00401026, что требует использования LordPe для изменения точки входа в программу):

00401026XOR EAX,EAX; EAX=0
00401028PUSH 0040103A; помещение адреса нового обработчика в стек
0040102DPUSH DWORD PTR FS:[EAX]; помещение адреса старого обработчика в стек
00401030MOV DWORD PTR FS:[EAX],ESP; помещение в FS:[0] указателя на структуру
00401033CALL 00401033;генерация исключения путем переполнения стека
00401038JMP SHORT 00401038; данная инструкция никогда не будет исполнена
0040103APOP EAX; восстановить регистр
0040103BPOP EAX; восстановить регистр
0040103CPOP ESP; восстановить регистр ESP
0040103DJMP SHORT 00401000; перейти к выполнению программы

После установки обработчика исключений происходит инициирование исключительной ситуации (инструкция «CALL 00401033» уходит в бесконечную рекурсию, что неминуемо вызывает переполнение стека).

Если проанализировать этот код, можно заметить, что инструкция, расположенная по адресу 00401038, никогда не будет выполнена. Адрес, содержащийся в EIP, изменится после того, как исключение будет сгенерировано, и он будет равен содержимому указателя на обработчик исключения – 0x40103A. Поэтому по адресу 00401038 может быть размещена любая инструкция. Впрочем, использование в этом месте команды JMP позволяет ввести отладчик в заблуждение. Например, если операнд этой инструкции будет равен 0040103E, все последующие инструкции восстановления регистров будут трактоваться как данные, ибо ни одна часть программы не ссылается на них, а перед ними расположена инструкция безусловного перехода. Кроме того, ссылка на машинный код, который является серединой инструкции JMP, приводит к тому, что она рассматривается отладчиком как следующий код:

0040103DEBDB EB
0040103E> C100 00ROL DWORD PTR DS:[EAX],0;Shift constant out of range 1..31

И уж совсем «добить» реверсера можно, если разместить после команды JMP SHORT 00401000 безусловный переход на середину некоторой инструкции, размещенной в пределах секции кода, заставляя отладчик делать ошибочную попытку интерпретировать инструкции как данные. Это может быть реализовано следующим образом:

; Начало кода программы, который становится полностью "нечитабельным":

; Ниже расположены неверно интерпретируемые отладчиком инструкции:
00401000DB 6A;CHAR 'j'
00401001DB 00
00401002DB 68;CHAR 'h'
00401003DD ex_excep.00403000;ASCII "Simply program"
00401007DB 68;CHAR 'h'
00401008DD ex_excep.0040300F;ASCII "Hello, World!"
0040100CDB 6A;CHAR 'j'
0040100DADD AL,CH
0040100FOR EAX,6A000000
00401014ADD AL,CH
00401016ADD BYTE PTR DS:[EAX],AL
00401018ADD BYTE PTR DS:[EAX],AL
0040101AJMP DWORD PTR DS:[<&kernel32.ExitProcess>];kernel32.ExitProcess
00401020JMP DWORD PTR DS:[<&user32.MessageBoxA>] ;user32.MessageBoxA

; Начало антиотладочного кода:

00401026XOR EAX,EAX
00401028PUSH ex_excep.0040103A
0040102DPUSH DWORD PTR FS:[EAX]
00401030MOV DWORD PTR FS:[EAX],ESP
00401033CALL ex_excep.00401033
00401038JMP SHORT ex_excep.0040103E; фиктивный переход на середину инструкции

; Далее расположены неверно интерпретируемые отладчиком инструкции:

0040103ADB 58;CHAR 'X'
0040103BDB 58;CHAR 'X'
0040103CDB 5C;CHAR '\'
0040103DDB EB
0040103EROL DWORD PTR DS:[EAX],0;Shift constant out of range 1..31
00401041JMP SHORT ex_excep.0040100F; фиктивный переход на середину инструкции

Этот код практически не поддается анализу, что доказывает: механизм исключений - очень эффективный и мощный метод антиотладки. Интересно, что инструкции, располагающиеся вблизи точки входа, не интерпретируются в качестве кода даже после того, как точку останова устанавливают на «данные», располагающиеся по адресу 0x401000. Исправить ситуацию может только грамотный разбор антиотладочного кода профессиональным реверсером и множественные правки кода в процессе отладки.

Метод часто используется и для изменения EIP, то есть адреса выполняемой инструкции. Часть кода, которая, казалось бы, должна выполняться, может быть предварена не слишком явно определенным исключением. В результате, разбор структуры программы оказывается для реверс-инженера практически непосильной задачей.

«Кормим» vsprintf () спецификаторами

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

Отладчик OllyDbg содержит уязвимость, связанную с использованием ошибочного выполнения функции vsprintf (), входящей в msvcrt. Функция входит в семейство функций «printf». Сайт http://www.opennet.ru описывает его так:

«Функции семейства printf выводят данные в соответствии с параметром format, описанным ниже. Функции printf и vprintf направляют данные в стандартный поток вывода stdout; fprintf и vfprintf направляют данные в заданный поток вывода stream; sprintf, snprintf, vsprintf и vsnprintf направляют данные в символьную строку str. Функции vprintf, vfprintf, vsprintf, vsnprintf эквивалентны соответствующим функциям printf, fprintf, sprintf, snprintf, исключая то, что они вызываются с va_list, а не с переменным количеством аргументов. Эти функции не вызывают макрос va_end , и поэтому значение ap после вызова неопределенно. Приложение может позже само вызвать va_end(ap). Эти восемь функций выводят данные в соответствии со строкой format, которая определяет, каким образом последующие параметры (или доступные параметры переменной длины из stdarg(3)) преобразуют поток вывода».

Спецификаторы преобразований, начинающиеся символом «%», указывают, что за строкой следует параметр. Если в вызове функции указан спецификатор «%s», параметр типа const char * будет преобразован в указатель на символьный массив – строковой указатель. При этом функцией будут выведены символы, вплоть до символа-терминатора («NULL»).

В чем причина ошибочного выполнения функции vsprintf () в OllyDbg? Оказывается, в определенных случаях отладчик передает данные, встречающиеся в программе, непосредственно функции vsprintf (), без каких-либо дополнительных проверок. Представь себе, что в строке, переданной функции, содержатся спецификаторы преобразований. Если будет выполнено преобразование параметра в указатель, который будет указывать на неинициализированную область памяти, программа завершится с сообщением об ошибке. Исключение не будет обработано стандартным обработчиком, и процесс, породивший его, будет завершен. Ты, наверное, догадался, что в случае вызова некоторых API-функций в процессе исследования программы данным процессом будет являться отладчик. Это нас и интересует больше всего :). Ошибка использования функции vsprintf () при передаче ей символьной строки, в которой содержатся спецификаторы преобразования «%s», встречается в отладчике OllyDbg версии 1.10. Именно эта версия и полюбилась тысячам реверсеров по всему миру!К сожалению, отладчики уровня ядра и некоторые «прикладные» отладчики не имеют этой ошибки. Но наша цель - один из наиболее популярных отладчиков - OllyDbg.Ниже мы рассмотрим код, который демонстрирует использование уязвимости.

Программа может генерировать текстовые отладочные сообщения. Для этих целей в WIN32 служит функция OutputDebugStringА() библиотеки «kernel32.dll». Функцией OutputDebugStringA() регистрируется собственный обработчик исключений (мы говорили об обработчиках выше), после чего вызывается RaiseException(), инициирующая программное исключение. Если в системе присутствует отладчик, установленный по умолчанию, обработка сгенерированного исключения будет передана ему. В ином случае будет использоваться обработчик, установленный самой функцией. В том, что обработчик действительно установлен, легко убедиться, протрассировав по <F7> функцию OutputDebugStringA() до момента вызова внутренней функции, содержащей следующий код:

7C8024F9 PUSH EAX
7C8024FA MOV EAX,DWORD PTR SS:[EBP-4]
7C8024FD MOV DWORD PTR SS:[EBP-4],-1
7C802504 MOV DWORD PTR SS:[EBP-8],EAX
7C802507 LEA EAX,DWORD PTR SS:[EBP-10]
7C80250A MOV DWORD PTR FS:[0],EAX
7C802510 RETN

После выполнения этого кода и возврата в функцию OutputDebugStringA() обработчик будет установлен.
Вот прототип функции, которая вызывает исключение:

RaiseException(
DWORD dwExceptionCode,
DWORD dwExceptionFlags,
DWORD nNumberOfArguments,
CONST DWORD *lpArguments
);

Параметр *lpArguments формируется на основе входной строки для функции OutputDebugStringA(). Так как указатель будет иметь недопустимое значение, исключение не будет обработано. А это и приводит к «умиранию» отладчика.

На практике осуществить антиотладочный прием, описанный выше, очень легко (в качестве «подопытной» программы снова воспользуемся нашей «дрозофилой»):

00401026 PUSH 00401033; указатель на строку
0040102B CALL OutputDebugStringA; вызов функции
00401031 JMP 00401000; переход к выполнению программы
00401033 DB"%s%s",0; строка, терминированная нулевым значением

В принципе, если вызов функций – как высокоуровневых, так и NativeAPI – спрятан (этого можно добиться, используя некоторые методики, вроде Stolen code, то есть метод перемещения кода функций в PE-файл). Все попытки исследования сводятся к перемещению в ядро выполнением команды SYSENTER, что заставляет реверс-инженера использовать низкоуровневые отладчики, например, SoftIce. В свою очередь, это связано с большими трудозатратами. Поэтому эта защитная «фишка» является очень эффективной, если подходить к ее реализации с умом.

Содержание
загрузка...
Журнал Хакер #151Журнал Хакер #150Журнал Хакер #149Журнал Хакер #148Журнал Хакер #147Журнал Хакер #146Журнал Хакер #145Журнал Хакер #144Журнал Хакер #143Журнал Хакер #142Журнал Хакер #141Журнал Хакер #140Журнал Хакер #139Журнал Хакер #138Журнал Хакер #137Журнал Хакер #136Журнал Хакер #135Журнал Хакер #134Журнал Хакер #133Журнал Хакер #132Журнал Хакер #131Журнал Хакер #130Журнал Хакер #129Журнал Хакер #128Журнал Хакер #127Журнал Хакер #126Журнал Хакер #125Журнал Хакер #124Журнал Хакер #123Журнал Хакер #122Журнал Хакер #121Журнал Хакер #120Журнал Хакер #119Журнал Хакер #118Журнал Хакер #117Журнал Хакер #116Журнал Хакер #115Журнал Хакер #114Журнал Хакер #113Журнал Хакер #112Журнал Хакер #111Журнал Хакер #110Журнал Хакер #109Журнал Хакер #108Журнал Хакер #107Журнал Хакер #106Журнал Хакер #105Журнал Хакер #104Журнал Хакер #103Журнал Хакер #102Журнал Хакер #101Журнал Хакер #100Журнал Хакер #099Журнал Хакер #098Журнал Хакер #097Журнал Хакер #096Журнал Хакер #095Журнал Хакер #094Журнал Хакер #093Журнал Хакер #092Журнал Хакер #091Журнал Хакер #090Журнал Хакер #089Журнал Хакер #088Журнал Хакер #087Журнал Хакер #086Журнал Хакер #085Журнал Хакер #084Журнал Хакер #083Журнал Хакер #082Журнал Хакер #081Журнал Хакер #080Журнал Хакер #079Журнал Хакер #078Журнал Хакер #077Журнал Хакер #076Журнал Хакер #075Журнал Хакер #074Журнал Хакер #073Журнал Хакер #072Журнал Хакер #071Журнал Хакер #070Журнал Хакер #069Журнал Хакер #068Журнал Хакер #067Журнал Хакер #066Журнал Хакер #065Журнал Хакер #064Журнал Хакер #063Журнал Хакер #062Журнал Хакер #061Журнал Хакер #060Журнал Хакер #059Журнал Хакер #058Журнал Хакер #057Журнал Хакер #056Журнал Хакер #055Журнал Хакер #054Журнал Хакер #053Журнал Хакер #052Журнал Хакер #051Журнал Хакер #050Журнал Хакер #049Журнал Хакер #048Журнал Хакер #047Журнал Хакер #046Журнал Хакер #045Журнал Хакер #044Журнал Хакер #043Журнал Хакер #042Журнал Хакер #041Журнал Хакер #040Журнал Хакер #039Журнал Хакер #038Журнал Хакер #037Журнал Хакер #036Журнал Хакер #035Журнал Хакер #034Журнал Хакер #033Журнал Хакер #032Журнал Хакер #031Журнал Хакер #030Журнал Хакер #029Журнал Хакер #028Журнал Хакер #027Журнал Хакер #026Журнал Хакер #025Журнал Хакер #024Журнал Хакер #023Журнал Хакер #022Журнал Хакер #021Журнал Хакер #020Журнал Хакер #019Журнал Хакер #018Журнал Хакер #017Журнал Хакер #016Журнал Хакер #015Журнал Хакер #014Журнал Хакер #013Журнал Хакер #012Журнал Хакер #011Журнал Хакер #010Журнал Хакер #009Журнал Хакер #008Журнал Хакер #007Журнал Хакер #006Журнал Хакер #005Журнал Хакер #004Журнал Хакер #003Журнал Хакер #002Журнал Хакер #001