В прятки с отладчиком Крис Касперски ака мыщъх Хакер, номер #086, стр. 086-120-6 ; при компиляции с ключом /Zi компилятор MS VC вместо указателя ; на саму функцию возвращает указатель на переходник, что есть ; бэд (правда, можно написать простейший анализатор, распознающий ; jmp и вычисляющий эффективный адрес функции, но это же лишний код!) .text:00401019 loc_401019: ; DATA XREF: .text:loc_40106D?o .text:00401019 ; _main+39?o .text:00401019 jmp _jump .text:00401019 ; ???????????????????????????? .text:0040101E dd 8 dup(0CCCCCCCCh) .text:0040103E align 10h .text:00401040 .text:00401040 _thunk proc near ; CODE XREF: .text:loc_401005?j .text:00401040 ; .text:0040106D ; [мыщъх поскипал] .text:0040106D ; .text:0040106D _thunk endp; .text:00401081; ?????????????????????????????? .text:00401081 .text:00401081 _jump proc near ; CODE XREF: .text:loc_401019?j .text:00401081 mov eax, offset loc_401005 .text:00401086 jmp eax .text:00401086 _jump endp .text:00401086 Переходник и указатель разделяют целых 68h байт, а в некоторых случаях и побольше. Кошмар! Даже если копировать с запасом, то мы все равно уйдем лесом и рухнем в прорубь. Тем не менее, без ключа /Zi все работает вполне нормально, а отлаживать программу можно и в машинных кодах. [перехват одиночной API-функции] Переход на thunk реализуется просто. Сам thunk запрограммировать намного сложнее. На первый взгляд, никаких мин здесь нет: снимаем thunk/вызываем функцию/устанавливаем thunk. Вызываем мы, конечно, call'ом (а чем же еще!), забрасывающим на стек адрес возврата в thunk и чуть-чуть приподнимающим его вершину, но этого "чуть-чуть" оказывается вполне достаточно, чтобы API-функция не могла найти свои аргументы. [состояние стека на момент вызова API-функции до перехвата с вызовом по call'у (в квадратных скобках приведено смещение аргументов относительно esp)] [00]:адрес возврата в программу [04]:аргумент 1 [08]:аргумент 2 [0C]:аргумент 3 [после перехвата] [00]:адрес возврата в thunk [04]:адрес возврата в программу [08]:аргумент 1 [0C]:аргумент 2 [10]:аргумент 3 Отказаться от call'а нельзя, ведь наш thunk должен как-то отловить момент завершения функции, чтобы вернуть восстановленный jump на место, иначе данный перехват будет первым и последним. А что если… скопировать аргументы функции, продублировав их на вершине стека? Тогда на момент вызова API-функции картина будет выглядеть так: [состояние стека на момент вызова API-функции после перехвата с дубляжом аргументов] [00]:адрес возврата в thunk [04]:аргумент 1 [08]:аргумент 2 [10]:аргумент 3 [04]:адрес возврата в программу [08]:аргумент 1 [0C]:аргумент 2 [10]:аргумент 3 За исключением потери нескольких десятков байтов стекового пространства все выглядит очень неплохо и даже нормально работает, вот только код перехватчика получается довольно громоздким и совсем не универсальным. Почему? Вспомним, что API-функции придерживаются stdcall-соглашения, при котором аргументы удаляет из стека сама вызываемая функция. Аргументы легко скопировать с запасом, но откуда мы знаем, сколько их удалять при выходе из thunk'а? А удалять необходимо, ведь в противном случае стек окажется несбалансированным и все рухнет. |