В прятки с отладчиком Крис Касперски ака мыщъх Хакер, номер #086, стр. 086-120-2 Фактически мы приходим к идее создания API-перехватчика (ака шпиона), только шпионить он должен не за чужим процессом, а за своим собственным, перехватывая нужные API-функции и скрытно выполняя некоторые дополнительные действия. Это серьезно затрудняет дизассемблирование, поскольку ключевые моменты защитного алгоритма идут мимо хакера, который ни хрена не может понять, как это работает и почему (например, можно внедрить в CreateFile процедуру проверки ключевого файла, а в самой программе только имитировать его выполнение, заставляя хакера анализировать километры совершенного левого кода). [классический перехват API-функций] Прежде чем приступать к кодированию, разберем алгоритм классического перехвата, пример готовой реализации можно найти, например, в wmfhotfix.cpp от Ильфака Гильфанова, исходный код которого можно скачать по адресу: castlecops.com/downloads-file-499-details-WMF_hotfix_source_code.html: - Перехватчик запоминает несколько первых команд API-функции в своем буфере (buf); - Дописывает к ним jump на остаток API-функции (after_thunk); - В начало API-функции ставит jump на X-код (thunk); - X-код выполняет все, что задумано, и прыгает на buf, отдавая управление API-функции; Эта схема позволяет выполнять X-код перед вызовом API-функции. Выполнение после завершения осуществляется чуть-чуть сложнее (call вместо jump с подменой адреса возврата), но классическая схема имеет кучу проблем и подводных граблей, которые очень сложно обойти. Переменная длина машинных команд на x86 затрудняет определение их границ, и мы не можем просто взять и скопировать несколько байт, ведь при этом легко разрезать последнюю машинную команду напополам, и тогда вместо перехвата мы получим крах. Приходится либо тащить за собой примитивный дизассемблер (а это очень много ассемблерных строк), либо ориентироваться на стандартный пролог push ebp/mov ebp,esp, занимающий всего три байта (55 8B EC), в то время как jump на thunk требует, как минимум, пять! За прологом же может идти все, что угодно: и sub esp,xxx и push, и… В общем-то, вариантов не так уж и много, но закладываться на них ни в коем случае нельзя, иначе перехватчик получится не универсальным и не переносимым. Проблема номер два — точки останова. Допустим, отладчик типа SoftICE или OllyDbg установил программную точку останова на перехваченную функцию, внедрив в ее начало машинную команду INT 03h (CCh) с предварительным сохранением оригинального содержимого в своем теле. Что должен делать в этом случае наш перехватчик? Даже если он распознает искаженный пролог, то копировать начало API-функции в buf ни в коем случае нельзя, ведь тогда будет скопирована и точка останова. Когда она сработает и передаст управление отладчику, то он просмотрит свои записи, увидит, что по данному адресу лично он не устанавливал никакой точки останова и не будет ее восстанавливать! А затем возникает необработанное исключение и крах. Как быть, что делать? Можно, конечно, проанализировать первый байт API-функции и, если он равен CCh, то нужно отказаться от перехвата, только с таким предложением лучше сразу идти в Resycle Bin. Зачем нам нужен перехватчик, который ничего не перехватывает? |