Gratt
Модостроитель
- Регистрация
- 14 Ноя 2014
- Сообщения
- 3.301
- Благодарности
- 4.636
- Баллы
- 625
Общие положения
Указатель на функцию
Начнем разбор на простых примерах:
Пример 1:
Пример 2:
Соглашение __stdcall в основном используется в win API, отличается от __cdecl только очисткой стека.
Строим указатель:
Пример 3:
Соглашение __fastcall имеет особенность передавать аргументы в регистрах.
Строим указатель:
Пример 4:
Строим указатель, а точнее 3 эквивалентных:
Пример 5:
Строим указатель:
Для закрепления памяти
* __cdecl соглашение о вызовах по умолчанию для обычных функций
* __thiscall соглашение о вызовах по умолчанию для функций-членов (методов)
* У функций-членов первый аргумент всегда this
* У функций-членов с соглашением __thiscall второй аргумент всегда vtable
* В указателе на метод вида class::* аргументы this и vtable не указывается
* В указателе на метод вида class::* с соглашением __thiscall последний можно не писать
* Соглашения о вызовах __fastcall и __thiscall совместимы
В принципе перехват функций дело элементарное. Все алгоритмы уже реализованы, так что программисту остается лишь указать адрес перехватываемой функции и куда ее переадресовать. Поэтому выделим для себя 2 главных понятия, которые нужно усвоить при реализации хуков: умение правильно определять сигнатуру функции (ее тип) и как правильно на ее основе расписать указатель. Хитрого тут ничего нет, все делается шаблонно, но если вдруг возникнут какие-либо трудности - все вопросы можно задать в комментариях к посту, потом сделаем FAQ в конце первого сообщения.
Главная техническая особенность хуков в Union - возможность множественного перехвата функции, то бишь перехвата одной функции несколькими плагинами. Такой подход позволит разработчикам создавать проекты не боясь, что кто-то уже похукал определенную областью памяти и плагины окажутся несовместимы. Реализация такова, что запросы образуют стек на функцию, в котором плагин вызывает собственный обработчик сразу же после оператора return предшественника.
Функция, в которую был переадресован вызов, описывает новый алгоритм. Внутри этого алгоритма возможно обращение и к оригинальной реализации. Для этого в любом месте тела функции необходимо обратиться к экземпляру хука как к оригинальной функции, а ее возвращаемый результат можно использовать для собственных вычислений. Новая функция будет считаться выполненой, как только отработает оператор return для всего стека хуков.
Указатель на функцию
Обрадую заранее. Начиная с Union SDK 1.1a эти знания будут скорее полезными, чем необходимыми, так как там реализовано автоприведение указателей. А сейчас все-таки побеседуем.
Что же необходимо знать для построения указателя. Во-первых тип и аргументы функции. Во-вторых соглашение о вызовах и чем они отличаются. Если с первым проблем нет, то со вторым есть нюансы, которые покажутся новичку неочевидными. Но начнем с простого и создадим общий шаблон указателя.
C++:
Функция универсальная: [тип] [соглашение о вызовах] [имя функции] ( [аргументы] )
Указатель универсальный: [тип] ( [соглашение о вызовах] * [имя указателя] ) ( [аргументы] )
Указатель на метод: [тип] ( [соглашение о вызовах] [имя класса]:: * [имя указателя] ) ( [аргументы] )
Получается мы просто переписываем функцию "другими словами". Соглашение и имя заключаются в скобки, добавляется "*" маркируя указатель. Для методов класса, если речь идет об узкоспециальных указателях, добавляется имя класса с оператором обращения к членам.
Начнем разбор на простых примерах:
Пример 1:
C++:
0x12345678 void __cdecl func1()
Что можно сказать об этой функции. Она обычная. Обычная настолько, что и соглашение о вызовах у нее по умолчанию - __cdecl. Это значит, что мы имеем право не указывать его в реализации.
Строим указатель:
C++:
typedef void(__cdecl * LPFUNC1)(); // эквивалентно void( * LPFUNC1)()
// пример определения
LPFUNC1 lpfunc1 = (LPFUNC1)0x12345678;
Пример 2:
C++:
0x12345678 const int __stdcall func2()
Строим указатель:
C++:
typedef const int(__stdcall * LPFUNC2)();
// пример определения
LPFUNC2 lpfunc2 = (LPFUNC2)0x12345678;
Пример 3:
C++:
0x12345678 const zSTRING __fastcall func3(zCArray<unsigned short>& array, oCNpc* npc)
Строим указатель:
C++:
typedef const zSTRING(__fastcall * LPFUNC3)(zCArray<unsigned short>& array, oCNpc* npc);
// пример определения
LPFUNC3 lpfunc3 = (LPFUNC3)0x12345678;
Далее затронем указатели на методы класса (рассмотрим только нестатические, так как у статических все идентично предыдущим примерам). Принцип все тот же, но следует усвоить вот что. У методов первый аргумент ВСЕГДА неявный this. Неявный потому, что в аргументы он передается сам согласно правилам языка (сразу за оператором выбора членов . или ->). Также если у метода стоит соглашение о вызовах __thiscall (по умолчанию для методов), то после this второй аргумент - указатель на vtable.
Пример 4:
C++:
0x12345678 void __thiscall CClass::method1()
C++:
typedef void(__thiscall * LPMETHOD1_V1)(CClass* _this);
typedef void(__fastcall * LPMETHOD1_V2)(CClass* _this, void* vtable);
typedef void( CClass::* LPMETHOD1_V3)( );
// пример определения
LPMETHOD1_V1 method1_v1 = (LPMETHOD1_V1 )0x12345678;
LPMETHOD1_V2 method1_v2 = (LPMETHOD1_V2 )0x12345678;
LPMETHOD1_V3 method1_v3 = *(LPMETHOD1_V3*)0x12345678;
Теперь по порядку. Мы видим, что у оригинальной функции аргументов нет. Но если следовать правилу выше, то как минимум один все таки есть - это неявный this. А поскольку функция __thiscall, то и второй аргумент соответственно указатель на vtable. Тогда почему в method1_v1 он отсутствует? Все просто. Если указатель тоже имеет соглашение __thiscall, то он не указывается.
Теперь почему у method1_v2 соглашение __fastcall. Это вопрос уже интереснее. Смысл этого соглашения равносилен __thiscall. Там и там аргументы передаются в регистрах, а __fastcall, в отличии от __thiscall, может объявляться не только в методах, но и в любых других функциях. Это означает, что благодаря этому соглашению можно перенаправить вызов метода в обычную функцию. И вот тут уже не забываем указатель на vtable.
Конкретный указатель на метод класса описан в method1_v3. Поскольку компилятор и так понимает, что указатель строится на функцию-член, то и задавать соглашение о вызовах не обязательно, ведь это __thiscall (по умолчанию для методов). А this и vtable в данном случае писать вообще не нужно. Примечание: конструкция *(LPMETHOD1_V3*) имеет такой вид, потому что синтаксически (LPMETHOD1_V3) недопустим (преобразование int адреса к class::* указателю). Фактически же это одно и тоже.
Пример 5:
C++:
0x12345678 void __cdecl CClass::method2(double x, double y);
C++:
typedef void(__cdecl * LPMETHOD1_V1)(CClass* _this, double x, double y);
typedef void(__cdecl CClass::* LPMETHOD1_V2)(double x, double y);
// пример определения
LPMETHOD1_V1 method1_v1 = (LPMETHOD1_V1 )0x12345678;
LPMETHOD1_V2 method1_v2 = *(LPMETHOD1_V2*)0x12345678;
Тут возможны только два варианта указателя. В обоих случаях обязательно указываем соглашение __cdecl, так как по умолчанию оно только для обычных функций. Указатель на vtable не пишем, поскольку функция не __thiscall. Неявный this необходимо учесть только в первом варианте.
Ну и в общем это все. Пока что можно в качестве шпаргалки использовать эту тему, а через несколько успешных попыток она уже не понадобится. Для практики можно открыть GothicNames, приложенный к проекту, и брать оттуда любые функции.
Для закрепления памяти
* __cdecl соглашение о вызовах по умолчанию для обычных функций
* __thiscall соглашение о вызовах по умолчанию для функций-членов (методов)
* У функций-членов первый аргумент всегда this
* У функций-членов с соглашением __thiscall второй аргумент всегда vtable
* В указателе на метод вида class::* аргументы this и vtable не указывается
* В указателе на метод вида class::* с соглашением __thiscall последний можно не писать
* Соглашения о вызовах __fastcall и __thiscall совместимы