• Авторизация


Трассировка 15-08-2010 00:30 к комментариям - к полной версии - понравилось!


Трассировка в x86-процессорах

Если TF-флаг, хранящийся в регистре EFLSGS (и гнездящийся в 8-м бите, считая от нуля), взведен, то после исполнения каждой команды процессор генерирует прерывание INT 01h или EXCEPTION_SINGLE_STEP (80000004h), как его "обозвали" разработчики Windows. Исключение составляют команды, модифицирующие регистр SS (селектор стека), маскирующие прерывание на выполнение следующей команды. На этот шаг разработчики процессоров пошли потому, что в коде достаточно часто встречаются конструкции вида MOV SS, new_ss/MOV ESP, new_ESP. Как нетрудно сообразить, если прерывание произойдет после того, как новый селектор стека уже обозначен, а указатель вершины стека еще не инициализирован, мы получим неопределенное поведение системы, ведущее к краху (а ведь существует команда LSS, одним махом загружающая и SS и ESP, но она не относится к числу самых популярных).

TF-флаг

Простейший способ обнаружения трассировки состоит в чтении регистра флагов (EFLAGS) и проверке состояния бита TF. Если он не равен нулю - нас кто-то злостно трассирует. С прикладного уровня прочитать содержимое регистра флагов можно самыми разными способами: командой PUSHFD, заталкивающей флаги в стек, генерацией исключения (при которой SEH-обработчику передается контекст потока вместе со всеми регистрами, включая регистр флагов), наконец, контекст можно получить API-функцией GetThreadContext.

Сегодня мы будем говорить лишь об первом способе - команде PUSHFD. При кажущейся незамысловатости она скрывает целый пласт хитростей, известных далеко не всякому хакеру.
Эксперимент №1 - "чистый" PUSHFD

Напишем следующую несложную программу (см. листинг 1), заталкивающую в стек регистр флагов через PUSHFD и тут же выталкивающую ее обратно в EAX для тестирования значения бита TF.
char yes[] = "debugger is detected :-)";
char noo[] = "debugger is not detected";

nezumi()
{
char *p = noo; // презумпция невиновности is on ;-)
__asm
{
; int 03 ; для отладки
pushfd ; сохраняем флаги в стеке, включая и TF
pop eax ; выталкиваем сохраненные флаги в eax
and eax, 100h ; проверяем состояние TF-бита
jz not_under_dbg ; если TF не взведен, нас не трассируют...
mov [p], offset yes ; ...ну, или мы не смогли это обнаружить ;)
not_under_dbg:
}

MessageBox(0, p, p, MB_OK);
}

Листинг 1. Простейшая программа TF-0x0-simple.c для обнаружения трассировки через PUSHFD.

Откомпилируем ее следующим образом (см. листинг 2). Все это шаманство потребовалось: а) чтобы убить стартовый код и программа сразу же начиналась с интересующей нас функции nezumi(); б) чтобы сократить размер программы, равный в данном случае 768 байтам.

cl.exe /c /Ox /Os /G6 TF-0x0-simple.c
link.exe TF-0x0-simple.obj /ENTRY:nezumi /MERGE:.rdata=.text
/ALIGN:16 /DRIVER /FIXED /SUBSYSTEM:CONSOLE KERNEL32.LIB USER32.lib

Листинг 2. Сборка простейшей тестовой программы.

Не обращая внимания на ругательство линкера "warning LNK4078: multiple ".text" sections found with different attributes (40000040)", запустим программу, убедившись, что в отсутствии отладчика она честно говорит "debugger is not detected", а теперь загрузим ее в MS VC dbg и будем трассировать (клавиша ), пока не достигнем первого call'а (им будет MessageBox). Ага! "debugger is detected :-)"! Цель достигнута!!!

Olly

Теперь испытаем cdb.exe из набора Debugging Tools. Поскольку он органически не умеет стопиться на OEP, раскомментируем "int 03" и перекомпилируем программу, загрузив ее в отладчик путем указания имени файла в командной строке. Первый раз отладчик всплывает в ntdll!DbgBreakPoint по int 03h. Этот акт всплытия нам совершенно не интересен, так что пишем "g" для продолжения выполнения программы и попадаем на "наш" собственный int 03h, стоящий в начале nezumi(). Последовательно отдавая команду "t", трассируем функцию до достижения CALL'а, а потом говорим "g" и... отладчик не обнаружен!!! Как так?! А очень просто - CDB отслеживает команду PUSHFD и эмулирует ее выполнение, "вычищая" TF-бит из стека. Аналогичным образом себя ведет Soft-Ice, Syser, OllyDbg и многие другие "правильные" отладчики. А вот IDA и GDB "честно" показывают TF-бит как он есть, чем и обнаруживают свое присутствие.
Эксперимент №2 - игры с префиксами

В лексиконе x86 помимо самостоятельных команд есть так называемые префиксы (prefix) - например, префикс повторения (REPE/PEPNE), префикс перекрытия сегмента (CS:, DS:, SS:, ES:, FS:, GS:), префикс изменения разрядности (с опкодом 66h) и т.д. Префиксы работают только со "своим" набором команд - в частности, префикс повторения применяется только совместно со строковыми инструкциями (MOVSD, LODSD, STOSD). На остальные команды он никак не воздействует (лишь увеличивает время их декодирования), а потому PUSHFD и REPE:PUSHFD - синонимы.

Умный отладчик должен учитывать, что перед командой PUSHFD может стоять один или несколько "мусорных" префиксов, автоматически отбрасывая их. Но это в теории. Добавим "REPE" перед "PUSHFD" в нашу программу и перекомпилируем ее, переименовав в TF-0x1-prefix.c.

Такие отладчики, как CDB, Soft-Ice и Syser автоматически отбрасывают префиксы, препятствуя их обнаружению. MS VC, IDA и GDB как обнаруживались так и обнаруживаются, а вот OllyDbg (даже в новой версии со всеми плагинами!) палится даже на банальном REPE, не говоря уже про сочетание нескольких префиксов!
Эксперимент №3 - прерывания в маске

Немного видоизменим нашу тестовую программу, добавив перед инструкцией PUSHFD пару команд MOV AX,SS/MOV SS,AX. И хотя реальной модификации регистра SS при этом не происходит, процессор все равно маскирует трассировочное прерывание на время команды, следующей на MOV SS,AX, которой и является PUSHFD.
nezumi()
{
char *p = noo; // презумпция невиновности is on ;-)
__asm
{
int 03 ; для отладки
mov ax,ss ; маскируем трассировочное прерывание...
mov ss,ax ; ...на время выполнения команды PUSHFD
pushfd ; сохраняем флаги в стеке, включая и TF
pop eax ; выталкиваем сохраненные флаги в eax
and eax,100h ; проверяем состояние TF-бита
jz not_under_dbg ; если TF не взведен, нас не трассируют
mov [p],offset yes
not_under_dbg:
}
MessageBox(0, p, p, MB_OK);
}

Листинг 3. TF-0x2-SS-change.c - ловля TF-бита через маскирование трассировочного прерывания.

Откомпилируем ее и посмотрим, как отладчики справятся с этой ситуацией. Вот мы доходим до MOV SS,AX, нажимаем (Step into) и... перескакиваем(!) через PUSHFD, позволяя ей сохранить в стеке истинное состояние TF-бита, что немедленно приводит к обнаружению отладчика.

Olly палится

И MS VC, и CDB, и Soft-Ice, и OllyDbg, и IDA, и GDB - все они ловятся на этот крючок. Syser (вплоть до версии 1.95.1900.0894) тоже ловился, пока мыщъх не отписал его разработчикам и они не пофиксили этот баг. В результате чего Syser стал единственным (на сегодняшний день) отладчиком, распознающим инструкции, модифицирующие SS и если за ними следует PUSHFD, то включающим специальный "эмулятор", подсовывающий программе сброшенный TF-бит.
Анти-антиотладка

Пользователям Syser'а хорошо! Им вообще ни о чем заботиться не нужно! А что делать приверженцам остальных отладчиков?! При "ручной" трассировке программы, обнаружив PUSHFD, достаточно прекратить трассировку и установив точку останова за ее концом, сказать отладчику или , прогоняя данный фрагмент кода без трассировки, что (естественно) не позволит обнаружить трассировку, поскольку ее нет вообще.

При автоматизированных прогонах в OllyDbg можно поставить точки останова на все команды, модифицирующие SS, заставляя его всплывать, передавая бразды правления в наши лапы для разруливания ситуации по вышеописанной методике. Проблема в том, что таких команд очень много, это не только MOV SS,16-bit Reg/Mem и POP SS, но еще MOV X,SS/POP SS плюс различные префиксы. В частности, MOV SS, EAX выполняется точно также, как и MOV SS,AX, но имеет другой опкод, что необходимо учитывать при составлении списка команд, на которые мы брякаемся.
Трассировка ветвлений

Pentium-процессоры умеют трассировать... ветвления (условные/безусловные переходы и вызовы функций). Для этого нужно взять MSR-регистр MSR_DEBUGCTLA и взвести в нем бит BTF (single-step on branches), тогда при взведенном TF-бите в регистре флагов EFLAGS трассировочное прерывание будет генерироваться не после каждой машинной команды, а лишь на инструкциях ветвления, что полезно для разбивки программы на функциональные блоки (например, можно написать real-time трассер, сравнивающий прогоны ветвлений программы до и после истечения испытательного срока, что позволит нам легко найти тот "заветный" jxx, который нужно захачить). С другой стороны, если защита взведет BTF-бит, то все известные мыщъх'у отладчики не смогут нормально работать, поскольку не проверяют его состояние при трассировке.

Бит BTF регистра MSR_DEBUGCTLA

Запись MSR-регистров осуществляется привилегированной командой WRMSR и при попытке ее исполнения на прикладном уровне процессор генерирует исключение, однако писать свой собственный драйвер для игр с BTF-битом совершенно необязательно и можно воспользоваться недокументированной native-API функцией NtSystemDebugControl(), экспортируемой из NTDLL.DLL, пример вызова которой можно найти на http://www.openrce.org/blog/view/535/Branch_Tracing_with_Intel_MSR_Registers, однако для этого необходимо: а) обладать правами администратора; б) в последних пакетах обновления для Server 2003 и XP возможности этой функции были существенно урезаны и, по-видимому, политика урезания продолжится и в дальнейшем, так что все-таки без драйвера не обойтись.

Источник "Энциклопедия антиоладочных приемов"
вверх^ к полной версии понравилось! в evernote


Вы сейчас не можете прокомментировать это сообщение.

Дневник Трассировка | Falling_802_13 - Дневник Falling_gerl | Лента друзей Falling_802_13 / Полная версия Добавить в друзья Страницы: раньше»