Антиотладочные приемы. Положи SoftIce на лопатки

TanaT

Xakep, номер #050, стр. 054-057

tanat@hotmail.ru

Наш журнал часто рассказывает о взломах программ, детально разбирая технику самого процесса и используемый при этом инструментарий. Пришло время встать по другую сторону баррикад. Сегодня мы узнаем, как защитить свою программу и усложнить жизнь крякеру. Мы разберемся во внутреннем устройстве отладчика и в принципах его работы, вместе поищем дыры в его механизмах и, наконец, ответим на вопрос: "Всесилен ли SoftIce?". Ведь не секрет, что с его помощью многие защиты снимаются в течение часа. Да какое там часа, 10 минут (при условии, что ты слышишь о нем не в первый раз). В общем, читай дальше, не пожалеешь!

Многие считают, что защищать программы не нужно. В этом действительно есть рациональное зерно. Когда я, например, слышу что-нибудь об антиотладочных приемах и дизассемблерных ловушках, на ум сразу приходит высказывание: Everything that can run, can be cracked. Защищать программы бесполезно: отбить охоту копаться в твоем коде можно лишь у начинающего крякера, опытного профи не остановить ничем. Взлом программы - дело времени. В твоих силах увеличить это время настолько, что сам крякер пожалеет, что взялся за твою программу.

Однако такая точка зрения грешит своей необъективностью: разумный компромисс заключается в том, чтобы защитить свою программу и сделать ее регистрацию выгодной. Пока защита будет взламываться, можно успеть продать какое-то количество продукта, что само по себе уже достижение. А обеспечив качественную поддержку со стороны разработчика, можно привлечь к себе в клиенты фирмы и корпорации. Ведь нельзя представить, что, например, Yahoo или Yandex будут использовать пиратские версии соответствующих антивирусов и брэндмауэров (firewalls по-русски).

Вообще отладчиков существует тьма-тьмущая, все не ограничивается одним SoftIce, хотя у последнего много преимуществ. Итак, отладчик может работать как обычная программа или в нулевом кольце защиты. К первым можно отнести Turbo Debugger и Code View, а ко вторым - уважаемый SI. Не стоит удивляться, что мы вспомнили такую, казалось бы, древность, как TD (тормознутый дебаггер) и CV (продукт всеми любимого БГ). На их примерах проще всего понять принцип работы любого отладчика.

Inside Debugger

Сейчас мы попытаемся понять "образ мышления отладчика" :). Итак, почти все отладчики используют так называемый режим трассировки (для этого нужно установить в единичку флаг трассировки TF). В этом режиме после выполнения любой команды генерируется отладочное исключение (или прерывание, кому как больше нравится) int 01h (h мы поставили в конце по привычке, хотя на самом деле число 01 одинаково и в десятичной и в шестнадцатеричной системах). Исключение составляют команды, модифицирующие сегментные регистры. Следовательно, при выполнении каких-либо операций с этими регистрами происходит "потеря трассировочного прерывания". Далее: начиная с Intel 80386 появились новые возможности отладки (специальные отладочные регистры DR0-DR7), модифицировалась и потеря трассировочного прерывания. Теперь оно теряется только при операциях с регистром SS. Что же нового дают регистры DR0-DR7? Они позволяют ловить: исполнение команды, запись данных, чтение/запись в порт и запись/чтение данных, но не исполнение. Таким образом, любой современный отладчик либо использует TF (что очень маловероятно - слишком старо), либо 8 отладочных регистров (почти всегда).

Теперь разберем подробнее. Регистры DR0-DR3 служат для установки брейкпоинтов (то есть их может быть одновременно только четыре - это будет очень важно в дальнейшем), DR4 и DR5 зарезервированы, DR6 частично зарезервирован, а частично используется (нам не важно, как) и DR7 - самый главный, он задает режим работы других регистров, содержащих точки останова.

Таким образом, вся отладка происходит через эти регистры. В принципе, теперь ты и сам можешь написать свой отладчик, правда, придется досконально изучить каждый бит трассировочных регистров, но ведь это мелочи :).

Один на один с отладчиком

Ну что, пришло время и нам показать, на что мы способны? Пойдем по порядку. Первыми идут старики: TD и CV. У них есть дыры? Если по существу, то это одна сплошная дыра. Обойти эти отладчики очень просто (поэтому ими никто не пользуется). Дело в том, что они являются отладчиками реального времени, следовательно, имеют с отлаживаемой программой общий стек, позволяют программе влиять на свой собственный код, находятся в одном адресном пространстве с прогой-пациентом. Что из этого можно извлечь?

Начну с садистского приема. Все-таки пусть эта тварь тоже почувствует себя жертвой :). Этот прием очень прост: мы отключим клавиатуру! Представь, Ламер запустит твою программулину под отладчиком, а у него раз - и клавиатура отключилась. Что ему делать? Да ничего, только перезагружаться. А отключить клаву можно с помощью махинаций с портами (3 способа):

in al,21hor al,00000010b ; irq 1 клавиатурное irqout 21h,al

или

in al,61hor al,10000000b ; бит 7 - отключает клавуout 61h,al

или

mov al, 0ADh ; отключение клавиатурыout 64h,al

К таким же садистским приемам можно отнести и переход в нестандартный графический режим: в таком случае экран исказится, земля задрожит... И отладка станет невозможной! Но тут многое зависит от этой самой нестандартности: клавы-то у всех почти одинаковые, а вот нестандартности еще и поискать надо. Но мы еще не закончили с клавиатурой. Следующий код вызывает переполнение порта клавиатуры, он безотказно действует под MS-DOS, но и только :(.

in al,064push axmov al,0FEout 064,alpop axout 064,al

Далее. Помнишь, мы говорили о потере трассировочного прерывания? Хоть этот прием и старый (все о нем давно знают), но тебе стоит на это посмотреть:

...pop ss ; в режиме трассировки после этой команды прерывание int 1 не будет вызваноpushfpop ax ; Записать флаги в axtest ax,0100h ; Проверка: установлен ли флаг TF в единичку?jnz OPS...OPS: ; работа в пошаговом режиме! Тут ты можешь попытаться повесить комп, выйти из программы и стереть command.com...

Потеря процессором трассировочного прерывания после выполнения команды POP SS приведет к тому, что отладчик "не заметит" команду PUSHF, и в стек будет занесено реальное состояние регистра флагов (с установленным битом TF).

Идем дальше. Помнишь, мы говорили об отладочном исключении (за номером 1)? Так вот, можно поменять вектор этого прерывания (перегрузить его). То есть теперь оно будет указывать на что-нибудь, нужное тебе (что ты и будешь использовать в программе), но отнюдь не на отладку. Тут уж раздолье для твоей фантазии. Примеров приводить не буду, так как перегружать прерывания можно только под DOS, ибо мастдай такую штуку не пропустит.

Еще на старые версии SI действовал такой прием: есть прерывание int 03h, оно является как бы API SI, то есть SI через это прерывание управляет и может быть управляем. Соответственно ничто тебе не мешает самому управлять SI через свою программу и вызвать зависание или перезагрузку компа :). НО! Это действует только для DOS (WINDOWS вместо прерываний предоставляет WIN API) и только на старые версии SI (потом вышел патч, и дыра исчезла).

Всесилен ли SoftIce?

Нет. Не всесилен - его можно побороть. Правда, с одной оговоркой - можно побороть программу, но не крякера-профи. Чтобы охладить твою радость, скажу, что хотя можно побороть SI, нельзя побороть IDA (крутейший дизассемблер). Но против последнего существуют свои приемы: самомодифицирующийся и зашифрованный код. К тому же исследовать мегабайты дизассемблированного кода - занятие не из легких.

Но вернемся к SI. Как же положить его на лопатки? Да не простой SI, а последний, пропатченный. Ну что ж, давай разберемся.

Если обобщить, то существует три способа: первый - убивает SI (он просто вылетает), второй - очень сильно затрудняет отладку, третий - очень перспективный способ, делающий отладку по большому счету вообще безнадежной. Давай по порядку.

Первый способ состоит в эмуляции исключительной ситуации. Например, деления на ноль. Ты же знаешь, что на ноль делить нельзя?

А что сделает компьютер в этом случае? Он сгенерирует исключение! (Исключение - это что-то типа ошибки, обработку которой выполняет специальная функция - обработчик исключения). Но языки высокого уровня позволяют перехватывать исключения и обрабатывать их по-своему! А что делает отладчик? Следует учесть, что SI (реально крутая прога) пытается полностью эмулировать процессор и выполняет те же действия, что и прога-пациент. Значит, он тоже будет делить на ноль! А он-то, в отличие от твоей проги, делить на ноль не умеет! Осталась сущая ерунда: научить твою прогу делить на ноль. Вот пример на языке C:

// Защита__try //Это начало блока, где может возникнуть исключительная ситуация{int Number1=5, Number2=5; // Думаю, это не нуждается в комментарияхdouble Result=Result/(Number1-Number2); // Вот она изюминка: пусть SI делит результат на ноль}__except(EXCEPTION_EXECUTE_HANDLER) // А вот тут мы ловим исключение и не даем программе вылететь с ошибкой{// Здесь находится твой дальнейший код}А вот как выглядит тот же прием на Delphi:var Number1, Number2: integer;Result: real;begintryNumber1:=5;Number2:=5;Result:=Result/(Number1-Number2);except...... /* Твой кодend

"В чем сила брат?" А сила в том, что крякеру надо будет пытаться найти адрес, где начинается твой дальнейший код, а это может быть не просто, так как реализация такого механизма сильно зависит от конкретного компилятора! Ради полноты картины следует сказать, что и IDA раньше вылетал после такого трюка. Однако его последние версии дизассемблируют все корректно. Чтобы затруднить анализ такого кода, нужно воспользоваться несколькими дополнительными приемами: вставить несколько таких блоков (размазать защиту по всей программе) в полезные процедуры, сделать специальные большие (много кода) функции, которые после многочисленных операций возвращают какое-нибудь число, которое и нужно присваивать числам a и b (для деления на ноль). В общем, сделать такой прием не столь очевидным для обнаружения.

Что еще мы можем предложить отладчику? А вот что. Почти любое руководство по SI говорит: "Найди функцию, с помощью которой осуществляется считывание пароля из окошка. Это может быть либо GetWindowTextA, либо GetDlgItem, либо еще что-нибудь..."

Вот тут-то и можно подловить мальчиша-кибальчиша: узнать самому, с помощью какой функции осуществляется ввод пароля, дизассемблировать ее :) (почти любая функция начинается с команд PUSH EBP или MOV EBP,ESP - которые твоя прога может выполнить и самостоятельно). Потом выполнить какую-то часть этой функции самому и передать управление на оставшийся кусок (обычной командой jmp, а для получения адреса, куда передавать управление, можно воспользоваться API-функцией GetProcAddr()). Таким образом неопытный взломщик так никогда и не узнает, каким способом ты получаешь свои данные из окна. Конечно, опытный профи догадается, но не сразу. Потом ему придется потрудиться, чтобы сломать твою программулину.

Последний и самый действенный способ - это использовать несколько потоков (одновременно работающие функции), которые будут преобразовывать полученные данные. Вот представь: пользователь ввел пароль, твоя программа его получила, SI это засек, один поток преобразует пароль (например, меняет все буквы на маленькие), второй меняет его еще как-нибудь (например, после каждой буквы вставляет число, генерируемое по специальной формуле). Что сделает SI? Он может вообще не засечь второго потока. А тогда вообще кранты! Но опытный крякер догадается, что где-то происходит изменение данных. Правда, ему придется потратить на это время. А теперь подумаем: сколько точек останова можно поставить с использованием отладочных регистров? Правильно - четыре. Помнишь, я обращал на это твое внимание? А что тебе мешает организовать больше четырех потоков? Ничего! И вот тут даже опытный крякер приплывет. Ты спросишь: "А что мешает крякеру установить программную точку останова?" То есть, почему взломщик не сможет после каждой команды вызвать int 01h без использования трассировочных регистров (это и есть программная точка останова)? А то, что программная точка останова МОДИФИЦИРУЕТ код (она его изменяет, этим грешат еще отладчики реального режима). А то, что код изменился, можно запросто узнать, подсчитав его контрольную сумму CRC. Таких алгоритмов очень много в сети на всех языках, так что я не буду повторяться.

Некролог

Напоследок я хочу раскрыть тебе несколько тонкостей, на которых не стал останавливаться в статье. Я не стал приводить примеры реализации многопотоковых приложений, так как они сильно зависят от языка программирования и требуют глубоких знаний. Реализацию многопотоковых приложений на Delphi можно найти на странице Horrific'а: www.cydsoft.com/vr-online/3_2001/delphi1.htm.

Теперь о том, как еще можно усложнить жизнь взломщику. Надо размазывать защиту по программе, используя потоки, где только можно, например, сделать поток, считающий CRC твоего кода и сигнализирующий об отладке.

Старайся придумывать свои приемы. Основа каждого приема - оригинальность. Например, можно взять какую-нибудь известную функцию (к которой отладчик или дизассемблер привык), поменять местами пару команд или добавить простых NOP (команда ассемблера, которая ничего не делает) и ассемблировать заново. В результате ты получишь нечто новое, что и следует использовать в своих творениях.

Теперь несколько слов, почему нельзя напрямую обращаться к регистрам DR0-DR7. Ведь постоянно их модифицируя, можно помешать отладчику использовать их в своих целях. Дело в том, что обращение к ним возможно лишь в защищенном, реальном и SMM-режимах (SMM - это System Managment Mode, но нам это сейчас неважно). Легально перейти в эти режимы обычной программе под WiN невозможно. То есть win дает переходить в эти режимы, но только через дырки, оставленные для себя. А что касается NT/2000 (наших любимцев), то там такой переход вообще очень сложное дело, и каждая новая дырка зашивается очень быстро. Так что напрямую помешать отладке мы не сможем.

И помни: ты сможешь обмануть SI (он не всесилен), да и IDA тоже, но чтобы облапошить крякера, надо придумать что-нибудь более оригинальное. Самым эффективным способом является многопотоковость. Каким бы гениальным ни был крякер, ресурсы его инструментария просто истощатся, а писать собственный отладчик для твоей проги - слишком велика честь. За это время ты распродашь продукта столько, что его взломанная версия никому не будет нужна.

Содержание
Выпуски журнала "Xakep"
Журнал Хакер. Содержание номера #001Журнал Хакер. Содержание номера #002Журнал Хакер. Содержание номера #003Журнал Хакер. Содержание номера #004Журнал Хакер. Содержание номера #005Журнал Хакер. Содержание номера #006Журнал Хакер. Содержание номера #007Журнал Хакер. Содержание номера #008Журнал Хакер. Содержание номера #009Журнал Хакер. Содержание номера #010Журнал Хакер. Содержание номера #011Журнал Хакер. Содержание номера #012Журнал Хакер. Содержание номера #013Журнал Хакер. Содержание номера #014Журнал Хакер. Содержание номера #015Журнал Хакер. Содержание номера #016Журнал Хакер. Содержание номера #017Журнал Хакер. Содержание номера #018Журнал Хакер. Содержание номера #019Журнал Хакер. Содержание номера #020Журнал Хакер. Содержание номера #021Журнал Хакер. Содержание номера #022Журнал Хакер. Содержание номера #023Журнал Хакер. Содержание номера #024Журнал Хакер. Содержание номера #025Журнал Хакер. Содержание номера #026Журнал Хакер. Содержание номера #027Журнал Хакер. Содержание номера #028Журнал Хакер. Содержание номера #029Журнал Хакер. Содержание номера #030Журнал Хакер. Содержание номера #031Журнал Хакер. Содержание номера #032Журнал Хакер. Содержание номера #033Журнал Хакер. Содержание номера #034Журнал Хакер. Содержание номера #035Журнал Хакер. Содержание номера #036Журнал Хакер. Содержание номера #037Журнал Хакер. Содержание номера #038Журнал Хакер. Содержание номера #039Журнал Хакер. Содержание номера #040Журнал Хакер. Содержание номера #041Журнал Хакер. Содержание номера #042Журнал Хакер. Содержание номера #043Журнал Хакер. Содержание номера #044Журнал Хакер. Содержание номера #045Журнал Хакер. Содержание номера #046Журнал Хакер. Содержание номера #047Журнал Хакер. Содержание номера #048Журнал Хакер. Содержание номера #049Журнал Хакер. Содержание номера #050Журнал Хакер. Содержание номера #051Журнал Хакер. Содержание номера #052Журнал Хакер. Содержание номера #053Журнал Хакер. Содержание номера #054Журнал Хакер. Содержание номера #055Журнал Хакер. Содержание номера #056Журнал Хакер. Содержание номера #057Журнал Хакер. Содержание номера #058Журнал Хакер. Содержание номера #059Журнал Хакер. Содержание номера #060Журнал Хакер. Содержание номера #061Журнал Хакер. Содержание номера #062Журнал Хакер. Содержание номера #063Журнал Хакер. Содержание номера #064Журнал Хакер. Содержание номера #065Журнал Хакер. Содержание номера #066Журнал Хакер. Содержание номера #067Журнал Хакер. Содержание номера #068Журнал Хакер. Содержание номера #069Журнал Хакер. Содержание номера #070Журнал Хакер. Содержание номера #071Журнал Хакер. Содержание номера #072Журнал Хакер. Содержание номера #073Журнал Хакер. Содержание номера #074Журнал Хакер. Содержание номера #075Журнал Хакер. Содержание номера #076Журнал Хакер. Содержание номера #077Журнал Хакер. Содержание номера #078Журнал Хакер. Содержание номера #079Журнал Хакер. Содержание номера #080Журнал Хакер. Содержание номера #081Журнал Хакер. Содержание номера #082Журнал Хакер. Содержание номера #083Журнал Хакер. Содержание номера #084Журнал Хакер. Содержание номера #085Журнал Хакер. Содержание номера #086Журнал Хакер. Содержание номера #087Журнал Хакер. Содержание номера #088Журнал Хакер. Содержание номера #089Журнал Хакер. Содержание номера #090Журнал Хакер. Содержание номера #091Журнал Хакер. Содержание номера #092Журнал Хакер. Содержание номера #093Журнал Хакер. Содержание номера #094Журнал Хакер. Содержание номера #095Журнал Хакер. Содержание номера #096Журнал Хакер. Содержание номера #097Журнал Хакер. Содержание номера #098Журнал Хакер. Содержание номера #099Журнал Хакер. Содержание номера #100Журнал Хакер. Содержание номера #101Журнал Хакер. Содержание номера #102Журнал Хакер. Содержание номера #103Журнал Хакер. Содержание номера #104Журнал Хакер. Содержание номера #105Журнал Хакер. Содержание номера #106Журнал Хакер. Содержание номера #107Журнал Хакер. Содержание номера #108Журнал Хакер. Содержание номера #109Журнал Хакер. Содержание номера #110Журнал Хакер. Содержание номера #111Журнал Хакер. Содержание номера #112Журнал Хакер. Содержание номера #113Журнал Хакер. Содержание номера #114