Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #58, СЕНТЯБРЬ 2005 г.

Stealth patching своими руками

Крис Касперски

Спецвыпуск: Хакер, номер #058, стр. 058-050-5


В семействе NT природа точек останова - сугубо локально. Каждый процесс владеет своим набором регистров Dr0-Dr3, и хотя количество точек останова от этого не увеличивается, они срабатывают только в контексте того процесса, в котором были установлены. Елки-палки, опять этот контекст! Ни одно начинание без него не обходится. Такова природа многозадачной NT, а против нее не попрешь. В 9x все проще: точки останова носят глобальный характер, распространяющийся на все процессы, что одновременно хорошо и плохо. Хорошо потому, что для установки точек останова не нужно лезть в чужой процесс, а плохо потому, что точки останова срабатывают в каждом процессе и обработчику приходится каждый раз разбираться, кто есть кто и куда.

К счастью, в нашем распоряжении есть две мощных функции GetThreadContext и SetThreadContext. Первая читает контекст потока, вторая, соответственно, устанавливает. В общих чертах алгоритм выглядит так: определяем PID нужного процесса (а определить его можно с помощью вызовов TOOLHELP32, о которых рассказывается в любом хакерском FAQ или с помощью ничуть не менее известной недокументированной функции ZwQueryInformationProcess, описанной там же). Полученный PID передается функции CreateToolhelp32Snapshot, создающей список процесса со всеми его потоками, разбором которых занимаются функции Thread32First/Thread32Next (работают по принципу известной парочки FindFirstFile/FindNextFile).

Первый поток, как правило, и является основным, однако в некоторых случаях это не так, но это уже детали. Как бы там ни было, идентификатор потока передается функции OpenThread. Что за OpenThread? Нет какой функции! OpenProcess есть, а OpenThread конструктивно не предусмотрена. Каждый программист это знает! Достаточно взять в руки документацию и прочитать. Но документацию хакеры читают в последнюю очередь (если ничего не помогает, прочти, наконец, документацию), а перед этим просто вызывают функции наобум :) или, на худой конец, лезут в MSDN за Knowledge Base, в которой недвусмысленно сказано, что функция OpenThread все-таки есть. Она честно экспортируется KERNEL32.DLL, но в SDK и заголовочные файлы не включена. Так захотелось какому-то менеджеру из Microsoft. Может, у него настроение было плохое или жена заболела, а тут еще OpenThread'ы всякие... Заметка под номером Q121093 ("Points to Remember When Writing a Debugger for Win32s"), датируемая застойным 1997 годом, об этом конкретно и говорит. Так что жаловаться на закрытость информации не приходится.

Функция принимает единственный аргумент - идентификатор потока, а возвращает его дескриптор или голимый ноль, если с открытием происходит облом (например, недостаточно прав). Менять содержимое контекста на ходу недопустимо (это все равно что перебегать скоростную автомагистраль на красный свет), поэтому поток перед изменением необходимо затормозить функцией SuspendThread. Тогда можно вызывать GetThreadContext с флагом CONTEXT_FULL и читать контекст, организованный в виде одноименной структуры CONTEXT. Здесь опять возникают сложности. Platform SDK не приводит никакой информации о CONTEXT'е, мотивируя это тем, что работать с контекстом на низком уровне никому не нужно: он де недокументирован и на каждой платформе реализован по-своему… На самом деле существует только одна платформа - INTEL, а все остальное - экзотика. Маркетоиды могут говорить что угодно и громоздить один уровень абстракции поверх другого, но программистов на этом не проведешь! Разработчики Windows прекрасно понимали, что без работы с регистрами ни одна системная программа все равно не обходится, и подарили нам замечательный файл WINNT.H, входящий в состав Platform SDK и содержащий определения многих недокументированных структур (и структуры CONTEXT в том числе) с более-менее подробными комментариями.

Назад на стр. 058-050-4  Содержание  Вперед на стр. 058-050-6