Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #86, ФЕВРАЛЬ 2006 г.

В прятки с отладчиком

Крис Касперски ака мыщъх

Хакер, номер #086, стр. 086-120-3


К тому же механизм DEP, поддерживаемый XP SP2 и выше, запрещает выполнение кода в области данных, значит, располагать buf в стеке нельзя. То есть можно, конечно, но перед этим необходимо вызывать VirtualProtect, назначая права доступа на исполнение. Это не проблема, но все-таки напрягает. Исходный код классического перехватчика получается слишком большим и совершенно ненаглядным, так что не будем его здесь приводить, а лучше покурим хорошей травы и подумаем, как нам быть (Крис, разумеется, не предлагает тебе курить ничего незаконного, трав в мире много, попадаются даже полезные. – Прим. gorl).

[универсальный перехватчик API-функций]

А вот другой способ перехвата, весьма критикуемый многими программистами. Он не очень элегантный, но зато предельно простой:

- Сохраняем несколько байт от начала API-функции, копируя их в buf (>= sizeof(jump));

- В начало API-функции ставим jump на наш thunk;

- В thunk'е: анализируем аргументы функции и вообще делаем все, что хотим;

- В thunk'е: восстанавливаем начало API-функции, копируя buf в ее начало;

- В thunk'е: вызываем восстановленную API-функцию call'ом с подменой адреса возврата;

- В thunk'е: в начало API-функции вновь устанавливаем jump на thunk и выходим;

Все это умещается буквально в 5-10 строк Сишного кода и очень быстро программируется. Это ликвидирует проблему точек останова, поскольку они исполняются на своем законном месте, причем отладчик всплывает на оригинальной API-функции, а код перехватчика остается незамеченным. При этом никакая часть кода не исполняется в области данных, что, с точки зрения DEP, очень хорошо. Необходимость определения границ машинных команд отпадает сама собой, поскольку перед выполнением API-функции мы возвращаем ее содержимое на место, и скопированные байты автоматически стыкуются со своим хвостом. Короче, сплошные преимущества. Так не бывает. А недостатки где? А вот!

При перехвате интенсивно используемых функций наша программа будет слегка тормозить за счет постоянного вызова VirtualProtect(api_func, 0x1000, PAGE_READWRITE, &old)/VirtualProtect(api_func, 0x1000, old, &old_old). Правда, можно присвоить началу функции атрибут PAGE_EXECUTE_READWRITE и при удалении thunk'а его не восстанавливать. Теоретически это ослабит безопасность системы, поскольку наш процесс может пойти вразнос и что-то сюда записать, однако эта угроза не настолько велика, чтобы принимать ее всерьез.

Многопоточность — вот главная проблема и самый страшный враг. Что произойдет, если в процессе модификации API-функции ее попытается исполнить другой поток? Переключение контекста может произойти в любой момент. Допустим, поток A выполнил инструкцию push ebp и только собирался приступить к mov ebp,esp, как был прерван системным планировщиком Windows, переключившим контекст управления на поток B, устанавливающий thunk. Когда поток A возобновит свою работу, то команды mov ebp,esp там уже не окажется. Вместо нее будет торчать "хвост" от jump, попытка выполнения которого ничем хорошим не кончится. Крах будет происходить не только на многоЦПшных, но даже на одноЦПшных системах, пускай и с небольшой вероятностью. Аналогичная проблема имеется и у классических перехватчиков, но, поскольку они устанавливают thunk один-единственный раз, для них она не так актуальна.

Назад на стр. 086-120-2  Содержание  Вперед на стр. 086-120-4