Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР 116, АВГУСТ 2008 г.

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

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

Хакер, номер #116, стр. 116-066-1

Самотрассировка и прочие головоломки

Сегодня мы будем ломать мой crack-me, напичканный антиотладочными приемами. Они основаны на особенностях обработки исключений отладчиками и на ошибках в debug engine, о которых я расскажу по ходу дела. А заодно продемонстрирую интимные подробности основных хакерских инструментов — ольги, иды, syser'а, x86emu и прочих.

Разгадывать загадки намного интереснее, чем читать готовые решения. А потому, пока еще не поздно, оторвись от статьи и попробуй расковырять JohoR crack-me (который можно взять с диска, прилагаемого к журналу, или скачать из моего репрозитория на OpenRCE: openrce.org/repositories/users/nezumi/crackme-jhr.zip). В подсказку не заглядывать! Исходный текст не смотреть! Впрочем, сам по себе исходный текст (даже с учетом всех содержащихся в нем комментариев) совершенно не объясняет, как же его отлаживать!

Здесь отсутствуют шифровка, самомодификация и прочие приемы, ослепляющие статический анализ. Дизассемблер выдает аккуратный листинг, каждая машинная команда которого абсолютно понятна. Однако результат действия программы в целом очень трудно предсказуем и требует довольно глубоких знаний устройства процессора и операционной системы. Поистине танталовы муки! Какой-то несчастный десяток машинных инструкций (ядро crack-me) отделяет нас от победы! Что ж, тем большее наслаждение испытываешь от взлома! Ну, а если самому взломать никак не получается – на этот случай я привожу развернутое объяснение.

Что мы будем ломать

Исходный текст JohoR crack-me приведен в листинге 1. Это чудо моей инженерной мысли после компиляции занимает всего 832 байта, большая часть которых приходится на PE-заголовок. Конечно, его можно было бы ужать, программируя в hex-кодах, но это ж сколько труда надо потратить! А так — файлы легко компилируются штатными утилитами от Microsoft.

Кстати, о компиляции. По многочисленным просьбам трудящихся, я отказался от командных файлов и перешел на макак (в смысле, на .mak), обрабатываемых утилитой NMAKE из комплекта поставки MS Visual Studio. «NMAKE /f crackme_jhr.mak» собирает релиз, а NMAKE /f «crackme_jhr.mak» CFG=«crackme_jhr - Win32 Debug» — отладочную версию. Только все равно отладить ее с помощью MS Visual Studio не удастся — нет смысла даже пытаться.

Также поддерживается сборка и из IDE – достаточно открыть макаку и сделать build. Тупая студия всегда ищет скомпилированный файл в каталогах \Debug и \Release, тогда как мыщъх создает его в текущей директории, поэтому запуск файла непосредственно из IDE невозможен (хотя, возможно, что в последних версиях MS уже пофиксила этот косяк).

Исходный текст JohoR crack-me

#include <windows.h>

int count; char str[]="0123456789ABCDEF!";

__declspec(naked) nezumi()

{

__asm{

; //int 03; // for soft-ice

xor eax, eax ; // eax := 0

mov ebx, fs:[eax]; // old SEH

pushfd ; // save EFLAGS

;//-[new seh]-;

push offset l1; // handler proc

push -1; // the last handler in the chain

mov fs:[eax], esp ; // assign the new handler

;//-[hacker time]-;

xor eax,[eax]; // <-- ACCESS VIOLATION

;//-[set TF bit]-;

push -1; // TF := 1

xor eax,[eax]; // <-- ACCESS VIOLATION

popfd ; // EFLAGS := 00244ED7h

;//-[TRACE-ZONE]-;

mov eax, [eax]; // <-- ACCESS VIOLATION

nop; // <-- INT 01

ud2; // <-- ILLEGAL INSTRUCTION

nop; // <-- INT 01

nop; // <-- INT 01

int 03 ; // <-- INT 01

jmp end_of_line; // :-- to exit -->

;//-[seh handler]- -;

l1: mov eax, [esp + 04h] ; // *EXCEPTION_RECORD

l2: mov edx, [esp + 0Ch] ; // EDX -> ContextRecord

mov eax, [eax]; // EXCEPTION CODE

cmp eax, 0C000001Dh ; // ILLEGAL INSTRUCTION

jz x2 ; // X-->

cmp eax, 080000003h ; // INT 03

jz x1; // -- skip 1 byte -->

cmp eax, 0C0000005h ; // ACCESS VIOLATION

jnz set_tf_bit; // -- don't skip -->

x2:inc dword ptr [edx+0B8h] ; // skip one byte

x1:inc dword ptr [edx+0B8h] ; // skip one byte

set_tf_bit: ; // <--X

cmp dword ptr [edx + 0B8h], offset end_of_line

jae end_of_handler ; // dont set TF-bit _outside_ trace-zone

or dword ptr [edx+0C0h],100h; // <---- set TF-bit _inside_ trace-zone

end_of_handler:xor eax,eax; // EXCEPTION_CONTINUE_SEARCH

inc [count] ; // EXCEPTION COUNT

ret; // end of the handler

;//-[exit]-;

end_of_line:mov fs:[eax],ebx; // restore the old SEH

sub esp, 8; // restore the stack

popfd; // restore the flags

}

// print EXCEPTION COUNT

count = str[(count>0x10)?0x10:count]; MessageBox(0,&count,"JohoR",MB_OK);

ExitProcess(0);

}

Алгоритм

Первые три команды сохраняют указатель на текущую (системную) SEH-запись в регистре EBX и заталкивают в стек флаги процессора, попутно обнуляя EAX. Следующие три команды устанавливают новый SEH-обработчик, находящийся по смещению l1, замыкая SEH-цепочку термирующим указателем -1 (FFFFFFFFh). Он сигнализирует системе о том, что данный обработчик — последний. Вот такой маленький трюк (почему-то большинство хакеров добавляют свой обработчик к цепочке уже существующих – хотя передавать им управление все равно не собираются, зачем же тогда усложнять код?).

Сразу же после установки SEH-обработчика выполняется команда XOR EAX, [EAX], «выбрасывающая» исключение доступа типа ACCESS VIOLATION. Операционная система ловит его и передает управление на метку l1, с кодом C0000005h, расположенным в двойном слове по адресу [ESP + 04h]. Обработчик видит, что это ACCESS VIOLATION и, зная, что его вырабатывает инструкция XOR EAX, [EAX], лезет в регистровый контекст. При этом он увеличивает значение EIP на два байта — sizeof(XOR EAX, [EAX]), поскольку ACCESS VIOLATION представляет собой fault. Если пояснять, то в момент генерации исключения регистр EIP указывает на начало возбудившей его машинной команды. При выходе из обработчика процессор будет выполять XOR EAX, [EAX] снова и снова, пока мы либо не изменим EAX так, чтобы он указывал на валидную область памяти, либо не увеличим значение EIP, переходя к выполнению следующей машинной команды. Что мы и делаем, попутно увеличивая счетчик вызова исключений (count) на единицу.

Зачем нам это? При пошаговой трассировке программы, когда взведен TF-бит, операционная система следом за ACCESS VIOLATION генерирует SINGLE STEP. В результате, вместо одного исключения мы получаем целых два, и SEH-обработчик под отладчиком вызывается дважды, позволяя программе обнаружить, что ее ломает злобный хакер! Отладчики MS VC, MS WinDbg, Soft-Ice (и ряд других) давят SINGLE STEP-исключение, сбрасывая флаг трассировки через контекст, а вот Ольга 1.1x об этом не заботится. Ошибка была исправлена только в версии 2.x (все еще находящейся в разработке). Подробнее об этом мыщъх рассказывает в своем блоге: souriz.wordpress.com/2008/05/09/bug-in-olly-windows-behavior-and-peter-ferrie («bug in Olly, Windows behavior and Peter Ferrie»).

Три следующих машинных команды взводят флаг трассировки. На самом деле, флаг трассировки взводится с помощью всего двух команд — PUSH -1/POPFD, а XOR EAX, [EAX], расположенная между ними, вставлена для борьбы с одним экспериментальным отладчиком, что «отлавливает» инструкцию POPFD. Если ломаемая программа взводит бит трассировки, отладчик врубает модуль эмуляции, но если управление на POPFD передается посредством правки регистрового контекста, – отладчик этого не просекает. Точнее, раньше не просекал, а сейчас ошибка исправлена.

POPFD выталкивает -1 (FFFFFFFFh) из стека, устанавливая процессорные флаги в единицу. Конечно, далеко не все флаги, а только те, которые позволено модифицировать прикладной программе. О чем, кстати говоря, «догадывается» далеко не каждый эмулирующий отладчик, а потому – значение EFLAGS под «живым» процессором и, например, x86emu сильно отличаются. Но x86emu, в общем-то, и не подряжался эмулировать все флаги процессора. В принципе, здесь можно вставить проверку — если EFLAGS не равняется 00244ED7h, то одно из двух — либо нас ломают на эмуляторе, либо это какой-то очень левый процессор. Впрочем, поскольку достойных эмулирующих отладчиков под x86 все равно нет, подобная проверка лишена смысла.

Но не будем отвлекаться. Флаг трассировки успешно взведен и по завершению команды, следующей за POPFD, процессор генерирует пошаговое исключение. А этой командой является наша старая знакомая XOR EAX, [EAX], «выбрасывающая» ACCESS VIOLATION, что предшествует пошаговому исключению. В данном случае система генерирует два вполне законных исключения. Вот только большинство отладчиков ошибочно принимают исключение, сгенерированное программой, за свое собственное и давят его. В результате чего SEH-обработчик вызывается на один раз меньше. Ольга 1.1х эту ситуацию обрабатывает вполне правильно (точнее, никак не обрабатывает, тупо передавая все исключения программе), а вот исправленная Ольга 2.х путается в исключениях и «давит» лишнее (с ее точки зрения) пошаговое исключение. Другими словами, под Ольгой 1.1х SEH-обработчик вызывается на один раз больше, чем под «живым» процессором (с учетом первой команды MOV EAX, [EAX]), а под Ольгой 2.х — на один раз меньше. Красота! И какую же версию нам выбирать? Что касается Soft-Ice (и некоторых других отладчиков), он «кушает» все пошаговые исключения, генерируемые отлаживаемой программой, обламывая самотрассировку. Потому SEH-обработчик вызывается только на MOV EAX, [EAX] — как следствие: счетчик вызовов count оказывается намного меньше, чем ожидает защита, сразу же понимающая с кем она имеет дело.

Команда NOP «честно» генерирует пошаговое исключение (ведь бит трассировки взведен!), но Soft-Ice его поглощает. Остальные отладчики (типа Ольги и IDA-Pro) хотя бы можно настроить на отдачу пошаговых исключений ломаемой программе. Причем, IDA-Pro 5.2 предложит сделать это автоматически, в то время как Ольга требует ручной настройки (вкладка «Exceptions» в опциях отладчика).

Инструкция UD2 генерирует исключение типа ILLEGAL INSTRUCTION, перехватываемое Soft-Ice и не отдаваемое отлаживаемой программой вплоть до отдачи команды «faults off». Syser такой команды вообще не знает, обзывая ее как «DB» («объявить байт») и неверно дизассемблируя весь последующий код (что не покажется удивительным, если вспомнить, что UD2 – двухбайтовая команда).

Пара последующих NOP'ов не делает ничего, кроме генерации пошагового исключения, особенность обработки которого мы обсуждали двумя абзацами выше. Так зачем же тогда они нужны? Подсказка— разные отладчики имеют разные баги, «съедая» различное количество исключений. Правильно! Данный crack-me определяет тип отладчика по значению count, уникальность которого обеспечивается соотношением команд, генерирующих свои собственные исключения, к общему количеству трассируемых инструкций. Если убрать NOP'ы, crackme продолжит детектить активную отладку, но уже не сможет определить, какой именно отладчик используется хакером.

Команда INT 03h также вставлена неспроста, а с умыслом. Если даже настроить отладчик на отдачу INT 03h ломаемой программе, наличие INT 03h существенно затрудняет отладку. Если бы INT 03h не было, то, чтобы быстро выбраться из глубин системного обработчика исключений назад к ломаемой программе, достаточно было бы покрыть трассируемый блок программными точками останова (в Ольге для этого нужно на каждой команде нажать <F2>). Программные точки останова представляют собой однобайтовую инструкцию CCh, легко обнаруживаемую подсчетом контрольной суммы и «разваливающую» самомодифицирующий код. Впрочем, ни того, ни другого в crack-me нет, а есть только INT 03h. Чисто теоретически, отладчик может (и должен) отличать свои собственные INT 03h от чужих, отдавая программе только те исключения, которые она сама же и сгенерировала. Но на практике отладчики путаются. Ольга, настроенная на отдачу INT 03h ломаемой программе, при установке программной точки останова поверх INT 03h циклится, вынуждая хакера применять аппаратные точки останова (которых всего четыре) или точки останова на регион памяти, реализованные через подмену атрибутов страниц, что также легко обнаруживается.

Кстати, с точки зрения процессора, INT 03h генерирует trap, а не fault. То есть, регистр EIP в момент генерации исключения смотрит на команду, следующую за INT 03h, которой в нашем случае является двухбайтовая инструкция JMP END_OF_LINE. В чем же подвох? А в том, что SEH-обработчик отлавливает BREAKPOINT-исключение (соответствующее коду 080000003h) и увеличивает значение EIP на единицу. Неужели управление передается в середину инструкции JMP END_OF_LINE? Какой хитрый прием против дизассемблера! Гм, вот только непонятно... первый байт опкода JMP SHORT равен EBh, второй — представляет относительное смещение целевого перехода. И чтобы оно соответствовало осмысленной машинной инструкции, необходимо, чтобы метка END_OF_LINE располагалась на определенном смещении от команды JMP. А в crack-me между ними расположен SEH-обработчик. Выходит, если его изменить, то crack-me сразу перестанет работать? Такая хитрая защита исходных текстов от изменения!

И чего только со страху не покажется. Да, в руководствах от Intel черным по белому написано, что BREAKPOINT это trap, а не fault. Вот только SEH-обработчик вызывается не процессором, а операционной системой. Той, что написана компанией Microsoft. А Microsoft Way умом не понять. Ну чем можно объяснить, что она подменяет процессорный контекст, умышленно уменьшая EIP на единицу? Парни из Microsoft впопыхах забыли, что BREAKPOINT может генерироваться как опкодом CCh, так и CDh 03h, а потому, если внедрить CDh 03h в программу и никак ее не обрабатывать, то после выхода из исключения, регистр EIP будет смотреть на опкод 03h, соответствующий команде ADD чего-то там. Допустим, за CDh 03h следует ССh (еще один INT 03h, только слегка другой). Тогда процессор выполнит опкод 03h CCh — ADD ECX, ESP. Вот и попробуй догадаться об этом при дизассемблировании!

Наконец, команда JMP END_OF_LINE выводит код из зоны трассировки. Программа восстанавливает прежний SEH, выталкивает из стека флаги и распечатывает значение счетчика исключений, после чего завершает свое выполнение вызовом функции ExitProcess(0).

Счастливый финал

Так как же все-таки ломают эти программы? И какими отладчиками? В случае статического кода (к которому относится мой crack-me) проблема решается установкой точки останова за пределами «горячей» зоны, где происходит выброс исключений, с прогоном их на живом процессоре (без пошаговой трассировки). Если же нам жизненно необходимо подсмотреть значение некоторых регистров или ячеек памяти внутри «горячей» зоны — на них устанавливается аппаратная точка останова.

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

Содержание
загрузка...
Журнал Хакер #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