Содержание статьи
Уже давно никто не трассирует программы от начала и до конца — слишком утомительно и непродуктивно. Однако не стоит полностью списывать трассировку со счетов, она и сейчас живее всех живых!
Запутанные участки кода, ответственные за проверку серийного номера, ключевого файла или расшифровку программы, довольно часто прогоняются отладчиком в пошаговом режиме. Кроме того, отладчик может «негласно» задействовать трассировку для выполнения некоторых операций. В частности, в OllyDbg установка точки останова на команду и/или диапазон EIP-адресов реализуется как раз через трассировку. Ее же используют многие плагины, например популярный FindString, выполняющий поиск заданной строки в регистрах (трактует их как указатели). Распаковщики упакованных файлов (особенно универсальные) активно используют трассировку для освобождения от упаковщика и восстановления оригинальной точки входа в программу (Original Entry Point, или, сокращенно, OEP).
Защита, умело препятствующая трассировке, затрудняет взлом программы, хотя и не делает его невозможным, поскольку на каждый антиотладочный болт с хитрой резьбой уже давно придуман свой антиантиотладочный ключ.
Трассировка в процессорах x86
Если TF-флаг, хранящийся в регистре EFLSGS (и гнездящийся в восьмом бите, считая от нуля), взведен, то после исполнения каждой команды процессор генерирует прерывание INT
или EXCEPTION_SINGLE_STEP
(80000004h) — как его «обозвали» разработчики Windows. Исключение составляют команды, модифицирующие регистр SS (селектор стека) и маскирующие прерывание на выполнение следующей команды. На этот шаг разработчики процессоров пошли потому, что в коде часто встречаются конструкции вида MOV
/MOV
. Легко сообразить, что, если прерывание произойдет после того, как новый селектор стека уже обозначен, а указатель вершины стека еще не инициализирован, мы получим неопределенное поведение системы, ведущее к краху (а ведь существует команда LSS, одним махом загружающая и SS, и ESP, но она не относится к числу самых популярных).
Простейший способ обнаружения трассировки состоит в чтении регистра флагов (EFLAGS) и проверке состояния бита TF. Если он не равен нулю — нас кто‑то злостно трассирует. С прикладного уровня прочитать содержимое регистра флагов можно самыми разными способами: командой PUSHFD, заталкивающей флаги в стек, генерацией исключения (при которой SEH-обработчику передается контекст потока вместе со всеми регистрами, включая регистр флагов); наконец, контекст можно получить API-функцией GetThreadContext
.
Сегодня мы будем говорить лишь о первом способе — команде PUSHFD
. При кажущейся незамысловатости она скрывает целый пласт хитростей, известных далеко не всякому хакеру.
Эксперимент #1 — «чистый» PUSHFD
Напишем несложную программку, заталкивающую в стек регистр флагов через PUSHFD
и тут же выталкивающую ее обратно в EAX для тестирования значения бита TF.
Простейшая программа TF-0x0-simple.c для обнаружения трассировки через PUSHFD
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);}
Откомпилируем ее следующим образом.
cl.exe /c /Ox /Os /G6 TF-0x0-simple.clink.exe TF-0x0-simple.obj /ENTRY:nezumi /MERGE:.rdata=.text /ALIGN:16 /DRIVER /FIXED /SUBSYSTEM:CONSOLE KERNEL32.LIB USER32.lib
Все это шаманство потребовалось:
- чтобы убить стартовый код и начать программу с интересующей нас функции
nezumi(
;) - чтобы сократить размер программы, равный в данном случае 768 байтам.
Не обращая внимания на ругательство линкера warning LNK4078: multiple ".text" sections found with different attributes (40000040), запустим программу. Убедимся, что она честно говорит: debugger is not detected. А теперь загрузим ее в MS VC dbg и будем трассировать (клавиша F11), пока не достигнем первого call’а (им будет MessageBox). Ага, debugger is detected! Цель достигнута!
Теперь испытаем cdb.exe из набора Debugging Tools. Поскольку он органически не умеет стопиться на OEP, раскомментируем int
и перекомпилируем программу: загрузим ее в отладчик, указав имя файла в командной строке. Первый раз отладчик всплывает в ntdll!DbgBreakPoint по int
. Это всплытие нам совершенно не интересно, так что пишем g для продолжения выполнения программы и попадаем на «наш» собственный int
, стоящий в начале nezumi(
.
Последовательно отдавая команду t, трассируем функцию до достижения call’а, а потом говорим g. Отладчик не обнаружен! Как так? А очень просто — CDB отслеживает команду PUSHFD и эмулирует ее выполнение, «вычищая» TF-бит из стека. Аналогичным образом себя ведут Soft-Ice, Syser, OllyDbg и многие другие «правильные» отладчики. А вот IDA и GDB «честно» показывают TF-бит, как он есть, чем и обнаруживают свое присутствие.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»