Старые антиотладочные приемы на новый лад

       

универсальный антиотладочный прием распознающий трассировку под большинством отладчиков


Данный прием позволяет обнаруживать soft-ice и многие другие отладчики, причем, на пассивную отладку он не реагирует (и это хорошо!). Пользователь нашей программы может запускать soft-ice и отлаживать остальные программы, но... как только он попытается загрузить в отладчик нашу она тут же подаст сигнал. Тоже самое произойдет, если хакер присоединит отладчик к уже запушенному процессу или ворвется в середину программы путем установки точек останова на API-функции. Естественно, антиотладочный код должен выполняться не до, а после загрузки отладчика. То есть, размещать его в самом начале защищаемой программы не стоит. Лучше — многократно дублировать в различных местах.

Прелесть данного приема в том, что его достаточно трудно распознать при взломе. Явные проверки отсутствуют и команда jmp under_debugger выглядит невинной овечкой. При ее выполнении без отладчика возбуждается исключение, перехватываемое обработчиком и выполнение программы идет уже совсем по другому пути (обработчик подменяет указатель команд, хотя, впрочем, он мог бы этого и не делать — в отличии от прерываний из которых в ms-dos нужно было выходить как можно скорее, чтобы не развалить систему, обработчик структурного исключения в принципе может вмещать в себя весь код программы целиком и химичить с контекстом совершенно необязательно — зачем лишний раз привлекать внимание хакера?). Под отладчиком же команда jmp under_debugger выполняется "как есть" и хакер может очень долго ковыряться в подложной ветке under_debugger не понимая, что здесь вообще происходит и откуда это взялось?! Чтобы оттянуть взлом, лучше не говорить сразу, что отладчик обнаружен, а подсунуть какой-нибудь зашифрованный код или что-то еще.

Главное, чтобы команды popf и jmp under_debugger не были разделены никакими другими инструкциями! Иначе защита не сработает! Трассировочное исключение генерируется сразу же после выполнения первой команды, расположенной после popf, и если ею окажется, например, nop, то jmp'у никаких исключений уже не достанется.
Так что замаскировать защитный код, рассосредоточив его по всему оперативному периметру уже не удастся. К тому же, хакер может легко нейтрализовать защиту, просто заменив jmp under_debugger на jmp continue. Чтобы этому противостоять, необходимо взводить в SEH-обработчике специальный флаг и проверять его по ходу выполнения программы — был ли он вызван или нет. А в самом SEH-обработчике еще контролировать и тип исключения, иначе хакер просто добавит xor eax,eax/mov [eax],eax (обращение по нулевому указателю, генерирующее исключение) и тогда SEH-обработчик будет получать управление как под отладчиком так и без него. Кстати говоря, защита данного типа была использована в программе ulink Юрия Харона, взлом которой подробно описан в моих "Фундаментальных основах хакерства — записках мыщъх'а", которую можно бесплатно скачать с моего ftp-сервера nezumi.org.ru (напоминаю, что он доступен не все время, а только когда мыщъх шевелит хвостом).

Зададимся таким вопросом: может ли отладчик трассировать программу, которая уже находится под трассировкой (например, отлаживается другим отладчиком или трассирует сама себя). Непосредственно — нет, поскольку флаг трассировки в x86 процессорах один и его вложенность не поддерживается. С другой стороны, если вышестоящий отладчик отлеживает обращение к флагу трассировки и эмулирует возбуждение трассировочного прерывания, передавая управление не на следующую команду на SEH-обработчик, такая схема может работать, правда, код отладчика существенно усложнится, а, поскольку, коммерческие отладчики ориентированы исключительно на легальных программистов, которые взломом защит не занимаются (или, во всяком случае, предпочитают это не афишировать), то создавать идеальный отладчик производителю просто не резон. Что же до некоммерческих отладчиков… При всем моем уважении к ним (я часто использую OllyDbg и люблю его), они еще не встали с горшка и для достижения совершенства им еще расти и расти. Впрочем, эмулирующие отладчики с такой защитой справляются на раз, но где вы видели эмулирующий отладчик под Windows? Можно, конечно, взять полноценный эмулятор PC с интегрированным отладчиком типа BOCHS (под который, кстати говоря, существуют и дополнительные отладчики, входящие в исходные тексты, но отсутствующие в готовой бинарной сборке), однако, отлаживать на нем Windows-приложения практически нереально, поскольку нет никакой возможности отличить код одного процесса от другого.



Программа, которая трассирует себя, под отладчиками выполняется неправильно — она трассируется отладчиком, но не сама собой. Хорошая идея — повесить на трассер распаковщик, расшифровывающий программу по мере ее выполнения. Это существенно затрудняет взлом, зачастую делая его практически невозможным. Вместо явной или косвенной проверки на отладчик, программа задействует общие с отладчиком ресурсы и под отладчиком становится просто нефункциональна.

Простейший пример такой защиты приведен ниже. Он "позаимствован" из моей книги "Техника и философия хакерских атак" (полную электронную версию которой можно бесплатно скачать с ftp://nezumi.org.ru) и работает только под ms-dos, тем не менее легко переносится в Windows, просто обработчик прерывания заменяется на обработчик структурного исключения (как именно это делается показано в листинге 2) и перед расшифровкой вызывается API-функция VirtualProtect для установки атрибута записи (по умолчанию секции .text/.code и .rodata имеют атрибут read-only и непосредственная расшифровка кода в них невозможно). Windows-вариант не сложен в реализации, но слишком громоздок и потому ненагляден.

       ; // устанавливаем новый обработчик трассировочного прерывания int 01h

       mov ax,2501h         ; функция 25h (установить прерывание), прерывание — 01h

       lea dx,newint01h     ; указатель на обработчик прерывания

       int 21h                    ; сервисная функция ms-dos



      

       ; // взводим флаг трассировки

       pushf                ; сохраняем регистр флагов в стеке

       pop ax               ; выталкиваем его в регистр ax

       or ah,1                    ; взводим бит TF

       push ax                    ; сохраняем измененный регистр ax

в стеке

       popf                 ; выталкиваем модифицированное значение в регистр флагов

      

       // теперь после выполнения каждой команды процессор будет генерировать int 01,

       // передавая управление его обработчику



      

       // подготавливаем параметры для расшифровки

       lea si, crypted_begin

       mov cx, (offset crypted_end - crypted_begin) / 2

      

repeat:       ; // основной цикл расшифровки

       lodsw                ; читаем очередное слово по si

в ax, увеличивая si на 2

       mov [si-2],bx        ; записываем в ячейку [esi-2] содержимое bx

       loop repeat          ; крутим цикл, пока cx не равен нулю

                           ; кажется, что это дурной цикл, работящий как memset

                           ; т.е. заполняющий область памяти содержимым bx

                           ; которое даже не было инициализировано!

                           ; однако, все не так и на каждом ходу генерируется

                           ; трассировочное прерывание, передающее управление

                           ; обработчику int

01h, который неявно модифицирует bx

      

       ; // сбрасываем флаг трассировки

       pushf                ; сохраняем регистр флагов в стеке

       pop dx               ; выталкиваем его в регистр dx

                           ; (ax

используется обработчиком int

01h)

       and dh,0FEh          ; сбрасываем бит TF

       push dx                    ; сохраняем измененный регистр dx

в стеке

       popf                 ; выталкиваем модифицированное значение в регистр флагов

       ; // еще одна ловушка для хакера

jmp_to_dbg:

       jmps under_debugger

      

       //

       ...                  ; "полезная нагрузка" (основной код программы)

       //

      

new_int_01h:

       xor ax, 9fadh        ; зашифровываем содержимое регистра ax

       mov bx, ax           ; помещаем его в регистр bx

       mov word ptr cs:[jmp_to_dbg],9090h

                           ; "убиваем" условный переход, ведущий к подложной ветке

                           ; (под отладчиком обработчик int 01h не получит

                           ; управления и переход не будет убит)

       iret                 ; выходим из обработчика прерывания

      

crypted_begin:

      

       //

       ...                  ; зашифрованный код/данные

       //

      

crypted_end:


Содержание раздела