Сrackme, прячущий код на API-функциях

Перехват одиночной API-функции


Переход на thunk реализуется просто. Сам thunk запрограммировать намного сложнее. На первый взгляд никаких мин здесь нет: снимаем thunk/вызываем функцию/устанавливаем thunk. Вызываем мы, конечно, call'ом (а чем же еще!), забрасывающим на стек адрес возврата в thunk и чуть-чуть приподнимающим его вершину, но этого "чуть-чуть" оказывается вполне достаточно, чтобы API-функция не могла найти свои аргументы (см.рис. 4).

                                         [00]:адрес возврата в thunk

       [00]:адрес возврата в программу   [04]:адрес возврата в программу

       [04]:аргумент 1                   [08]:аргумент 1

       [08]:аргумент 2                   [0C]:аргумент 2

       [0C]:аргумент 3                   [10]:аргумент 3

Рисунок 4 состояние стека на момент вызова API-функции до (слева) и после (справа) перехвата с вызовом по call'у (в квадратных скобках приведено смещение аргументов относительно esp)

Отказаться от call'а нельзя — ведь наш thunk должен как-то отловить момент завершения функции, чтобы вернуть восстановленный jump на место, иначе данный перехват будет первым и последним. А что если… скопировать аргументы функции, продублировав их на вершине стека? Тогда на момент вызова API-функции картина будет выглядеть так (см. рис. 5):

                                         [00]:адрес возврата в thunk

                                         [04]:аргумент 1

                                         [08]:аргумент 2

                                         [10]:аргумент 3

       [04]:адрес возврата в программу   [04]:адрес возврата в программу



       [08]:аргумент 1                   [08]:аргумент 1

       [0C]:аргумент 2                   [0C]:аргумент 2

       [10]:аргумент 3                   [10]:аргумент 3

Рисунок 5 состояние стека на момент вызова API-функции до (слева) и после (справа) перехвата с дубляжом аргументов

За исключением потери нескольких десятков байтов стекового пространства все выглядит очень замечательно и нормально работает, вот только код перехватчика получается довольно громоздким и не универсальным.
Почему? Вспомним, что API-функции придерживаются stdcall- соглашения при котором аргументы удаляет из стека сама вызываемая функция. Аргументы легко скопировать "с запасом", но откуда мы знаем сколько их удалять при выходе из thunk'а? А удалять необходимо, ведь в противном случае стек окажется несбалансированным и все рухнет.

Вообще-то, можно просто проанализировать значение регистра ESP до и после выполнения API-функции. Дельта и будет тем количеством байт, на которые мы должны увеличить ESP. Но есть и более короткие пути: если перехватываемая функция известна, достаточно просто создать функцию-двойник, имеющий тот же самый прототип. На языке Си это будет выглядеть так (в данном случае перехватывается MessageBoxA, в заголовке которой насильно прописывается строка "hacked", подтверждающая, что перехватчик исправно работает):

// наш thunk

//============================================================================

// получаем управление до вызова функции MessageBoxA

// может делать что угодно с параметрами и т.д.

__stdcall thunk(HWND h, LPCTSTR lpTxt, LPCTSTR lpCaption, UINT uType)

{

       _do(_UNINSTALL_THUNK_);           // восстанавливаем оригинальную функцию

       MessageBox(h,lpTxt,"hacked",uType);      // вызываем оригинальную функцию

       _do(_INSTALL_THUNK_);                    // вновь устаналиваем thunk

}

Листинг 4 демонстрация перехвата API-функции с известным прототипом


Содержание раздела