Различия

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

Ссылка на это сравнение

articles:anti-debug-04 [2017/09/05 02:55] (текущий)
Строка 1: Строка 1:
 +====== anti-debug-04 ======
 +<​sub>​{{anti-debug-04.odt|Original file}}</​sub>​
 +
 +====== энциклопедия антиотладочных приемов —\\ самотрассировка и прочие головоломки\\ (выпуск #04h) ======
 +
 +крис касперски,​ ака мыщъх, a.k.a nezumi, a.k.a souriz, a.k.a elraton, no-email
 +
 +**сегодня мы будем ломать мыщъхиный ****crack****-****me****,​ напичканный антиотадочными приемами,​ основанных на особенностях обработки исключений отладчиками и на ошибках в ****debug engine****, о которых мыщъх расскажет по ходу дела, демонстрируя интимные подробности основных хакерских инструментов — ольги, иды, ****syser'​****а,​ ****x86emu ****и прочих отладчиков**
 +
 +===== введение =====
 +
 +Разгадывать загадки намного интереснее,​ чем читать готовые решения. А потому пока еще не поздно оторвемся от статьи и попробуем расковырять JohoR crack-me (который можно взять с диска, прилагаемого к журналу,​ или скачать из мыщъиного репрозитория на OpenRCE: https://​www.openrce.org/​repositories/​users/​nezumi/​crackme-jhr.zip). В подсказку не заглядывать! Исходный текст не смотреть! Впрочем... сам по себе исходный текст (даже с учетом всех содержащихся в нем комментариев) ничего не проясняет и совершенно не объясняет как же его отлаживать?​!
 +
 +Здесь отсутствует шифровка,​ самомодификация и прочие приемы,​ ослепляющие статический анализ. Дизассемблер выдает аккуратный листинг,​ каждая машинная команда которого абсолютно понятна,​ но… результат действия всей программы в целом очень трудно предсказуем и требует довольно глубоких знаний устройства процессора и операционной системы. Поистине танталовы муки! Какой-то несчастный десяток машинных инструкций (ядро crack-me) отделяет нас от победы! Что ж! Тем большее наслаждение испытываешь от взлома! Ну, а если взломать никак не получается,​ на этот случай мыщъх приводит развернутое объяснение.
 +
 +===== что мы будем ломать =====
 +
 +Исходный текст JohoR crack-me, написанного на MS VC, приведен в листинге 1 и после компиляции это чудо мыщъхиной инженерии занимает всего 832 байта,​ большая часть из которых приходится на PE-заголовок,​ который,​ конечно,​ было бы можно ужать, программируя в hex-кодах,​ но это же сколько труда потратить надо! А так — файл легко компилируются штатными утилитами от Microsoft.
 +
 +{{anti-debug-04_Image_0.png?​553}}
 +
 +Рисунок 1 Syser вообще отказывается грузить JohoR crack-me в release build'​е
 +
 +Кстати,​ о компиляции. По многочисленным просьбам трудящихся,​ мыщъх отказался от командных файлов и перешел на макак (в смысле .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 уже пофиксила этот косяк).
 +
 +#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);​
 +
 +}
 +
 +Листинг 1 полный исходный текст JohoR crack-me
 +
 +{{anti-debug-04_Image_1.png?​553}}
 +
 +Рисунок 2 попытка отладки дебежного билда JohoR crack-me под Syser'​ом — отладчик в панике,​ система — в ауте, хакер — на полной измене
 +
 +===== алгоритм =====
 +
 +Первые три команды сохраняют указатель на текущую (системную) SEH-запись в регистре EBX и заталкивают в стек флаги процессора,​ попутно обнуляя EAX. Следующие три команды устанавливают новый SEH-обработчик,​ находящийся по смещению l1, замыкая SEH-цепочку термирующим указателем -1 (FFFFFFFFh),​ сигнализирующим системе о том, что данный обработчик — последний. Вот такой маленький трюк (почему-то большинство остальных хакеров добавляют свой обработчик к цепочке уже существующих,​ хотя передавать им управление все равно не собираются,​ так зачем же тогда усложнять код?).
 +
 +{{anti-debug-04_Image_2.png?​117}}
 +
 +Рисунок 3 результат работы JohoR crack-me при запуске без отладчика
 +
 +Сразу же после установки 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 (все еще находящейся в разработке). Подобнее об этом мыщъх рассказывает в своем блоге: http://​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"​ в опциях отладчика).
 +
 +{{anti-debug-04_Image_3.png?​553}}
 +
 +Рисунок 4 реакция Syser'​а на машинную команду UD2
 +
 +Инструкция UD2 генерирует исключение типа ILLEGALINSTRUCTION,​ перехватываемое Soft-Ice и не отдаваемое отлаживаемой программой вплоть до отдачи команды "​faults off"​. Что же касается Syser'​а,​ то он такой команды вообще не знает, "​обзывая"​ ее как "​DB"​ (объявить байт) и неверно дизассемблируя весь последующий код (что не покажется удивительным,​ если вспомнить,​ что UD2 – двухбайтовая команда).
 +
 +Пара последующих NOP'​ов не делает ничего,​ кроме генерации пошагового исключения,​ особенность обработки которого мы обсуждали двумя абзацами выше. Так зачем же тогда они нужны?​! Все очень просто — разные отладчики имеют разные баги, "​съедая"​ различное количество исключений…Ага!!! Правильно! Данный crack-me определяет тип отладчика по значению count, уникальность которого обеспечивается соотношением команд,​ генерирующих свои собственные исключения,​ к общему количеству трассируемых инструкций. Если убрать NOP'​ы,​ crackme продолжит детектить активную отладку,​ но уже не сможет определить какой именно отладчик используется хакером.
 +
 +{{anti-debug-04_Image_4.png?​117}}
 +
 +Рисунок 5 результат работы JohoR crack-me при запуске под IDA-Pro 4.7
 +
 +Команда 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. А в cracm-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. Вот и попробуй догадаться об этом при дизассемблировании!!!
 +
 +{{anti-debug-04_Image_5.png?​553}}
 +
 +Рисунок 6 установка программных точек останова внутри "​горячей"​ зоны
 +
 +Наконец,​ команда JMP END_OF_LINEвыводит код из зоны трассировки. Программа восстанавливает прежний SEH, выталкивает из стека флаги и распечатывает значение счетчика исключений,​ после чего завершает свое выполнение вызовом функции ExitProcess(0).
 +
 +===== заключение =====
 +
 +Так как же все-таки ломают такие программы?​ И каким отладчиками?​! В случае статического кода (к которому относится данный crack-me) проблема решается установкой точки останова _за_ пределами "​горячей"​ зоны, где происходит выброс исключений,​ с прогоном их на живом процессоре,​ то есть _без_ пошаговой трассировки. Если же нам жизненно необходимо подсмотреть значение некоторых регистров или ячеек памяти внутри "​горячей"​ зоны — на них устанавливается аппаратная точка останова.
 +
 +Динамический код (упакованный,​ зашифрованный,​ самомодифицирующийся) заломать намного сложнее,​ поскольку,​ нам реально необходимо прогнать его через пошаговый трассировщик,​ с которым и борется защита,​ причем,​ весьма эффективно борется. BOCHS (бесплатная виртуальная машина со встроенным отладчиком) единственный разумный выбор, но сколько грузится на нем Windows лучше не говорить.
 +
 +