Переход на 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-функции до (слева) и после (справа) перехвата с дубляжом аргументов
За исключением потери нескольких десятков байтов стекового пространства все выглядит очень замечательно и нормально работает, вот только код перехватчика получается довольно громоздким и не универсальным.