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

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

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

Хакер, номер #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'а? А удалять необходимо, ведь в противном случае стек окажется несбалансированным и все рухнет.

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