![]() |
Хотел продолжить первый гайд, но понял что нужно объяснить что такое хуки
В этом гайде я расскажу что такое хуки, как они работают, и как их использовать. При использовании на других ресурсах необходимо указание авторства и ссылки на оригинальную темы! Перед тем как начать: Так как этот гайд сделан в целях обучения, здесь не будет показано использование готовых библиотек, а только сырой код. В процессе написания гайда я понял что без знаний ассемблера и низкоуровневых вы поймете лишь малую часть от написанного здесь. Но если вы поймете что здесь написано - будет очень хорошо. Все действия производились на Visual Studio 2019 с параметром Код:
/std:c++17Все адреса указаны для SAMP R3-1, на других версиях будете ловить краши И так, начнем: Хук(от англ. Hook) - перехват. В нашем случае это перехват внутриигровых функций; Когда игра захочет их вызвать - будут выполняться наши действия (наш код), а затем уже можно продолжить выполнение функции, либо сразу сделать возврат, чтобы функция ничего не сделала. Перед тем как я расскажу про сами хуки, нужно немного углубится в устройство вызова функций. У каждой функции есть свое соглашение о вызове. Соглашение о вызове - "Правило" которое регулирует каким образом аргументы функции будут переданы самой функции и как именно будет произведен возврат значения, а также кто будет очищать стек после вызова функции(это не полный список, но самое основное что стоит знать). Если например вызвать Код:
cdeclКод:
stdcallВ архитектуре x86 исторически сложилось, что разным людям не нравилось что-то в других соглашениях о вызове, и они создавали свои. На x64 такой бардак тоже есть, но уже между разными OC. Существует много соглашений о вызовах, но описывать все я не буду, ибо они вам вряд ли пригодятся( Код:
pascalМы же рассмотрим 4 соглашения о вызовах: Код:
cdeclКод:
stdcallКод:
fastcallКод:
thiscallcdecl является основным соглашением о вызове и используется почти везде. Возврат осуществляется через регистр eax, регистр st0 для x87, и пару регистров eax:edx для значений размером в 5-8 байт. Стек очищает тот кто вызывает функцию, поэтому cdecl поддерживает переменное число аргументов. Установлено по умолчанию в MSVC. stdcall является основным соглашением о вызовах в Windows, а также во многих библиотеках(например basslib). Возврат осуществляется через регистр eax, очистка стека производится самой функцией. Переменное число аргументов не поддерживает. thiscall используется для вызова методов класса. В регистр ecx кладется скрытый аргумент this, очистка стека производится самой функцией, возврат значения через регистр eax. Переменное число аргументов не поддерживает. fastcall используется редко. В хуках зачастую используется для обхода thiscall в msvc(чуть позже расскажу что это). Первые два аргумента кладутся в регистры ecx и edx, остальные в том же порядке через стек. Очистка стека производится самой функцией. Переменное число аргументов не поддерживает. Из-за использования регистров для передачи аргументов его назвали fastcall, т.к. операции с регистрами на старых компьютерах были заметно быстрее операциями со стеком. Теперь можно перейти к теории о хуках. Для перехвата используются две техники - подмена вызова(call hook) и уже после вызова(в прологе) прыжок в хук. Начнем с первого: в основном вызовы происходят по релативному адресу, но бывают и вызовы по абсолютному адресу который находится в регистре. Релативный адрес(от англ. Relative address) - это адрес, относительно места откуда происходит вызов. Абсолютный адрес - это адрес, указываемый относительно всего адресного пространства программы. Наперед скажу что мы будем работать только с релативными адресами. Покажу на примере вызова функции в GTA:SA: C++: Код:
.Вызов происходит по адресу 0x53E972, вызываемая функция находится по адресу 0x719800 Но asm код выше - дизассемблированный код. В самой программе он хранится вот так: Код:
E8 89 AE 1D 00E8 - опкод вызова. В asm мнемонике записывается как call 89 AE 1D 00 - Релативный адрес вызова, записанный в порядке байт little endian. Что такое порядок байт - лучше почитать на википедии Если перевести релативный адрес в нормальное число, то получим 0x1DAE89. Откуда же вышло это число? Оно было посчитано как разница между адреса вызова и адреса вызываемой функции. Считается по формуле: (Адрес назначения вызова/прыжка) - (Адрес вызова/прыжка) - 5 Цитата:
Займемся этим. Сама функция установки хука будет предельна проста и будет лишь возвращать адрес, по которому нужно сделать прыжок обратно. Перед подменой релативного адреса нужно снять защиту с секции кода приложения(защита там стоит воизбежание случайной записи в код и дальшейнего UB), а после подмены вернуть все обратно Установка call хука: Код:
voidПоказывать буду на примере хука вывода сообщения в чат. Хук будем ставить вот сюда: Код: Код:
.text:10067A2A 008 6A 00 push 0 ; a6Соглашение о вызове у нас Код:
thiscallMSVC не дает использовать Код:
thiscallКод:
fastcallСначала напишем тело функции хука: C++: [CODE] void __fastcall HOOK_AddChatMessage ( void * pChat , void * EDX , int nType , const char * szText , const char * szPrefix , unsigned long textColor , unsigned long prefixColor ) { std :: cout : " Ну а теперь установим сам хук: C++: Код:
// Где нибудь за пределами функцииКомпилируем код, кидаем асишник в папку игры, устанавливаем DebugConsole, заходим в игру и видим в консоли сообщения из чата. Цитата:
jmp хуки ставятся где угодно(на самом деле call хуки тоже, но более запарно) Для установки jmp хука требуется немного больше действий. Так как ставить мы его будем в случайном месте, не факт что там где мы будем ставить его, будет ровно 5 байт. Поэтому перед установкой хука нужно смотреть сколько байт занимают инструкции на месте установки хука. Я буду смотреть опять же на функции добавления сообщения в чат. Переходим в пролог(начало) функции и смотрим на ассемблерный код: Код: Код:
.text:10067460 000 55 push ebpИ видим что у нас тут 4 опкода занимающих ровно 5 байт. Но если вам не повезет как тут, то нужно брать в большую сторону. Например тут, нужно будет взять 6 байт. Код: Код:
.text:10067478 00C 8B 74 24 18 mov esi, [esp+0Ch+a4]Идем дальше. Если мы просто затрем код по адресу, то у нас все сломается. Поэтому перед тем как ставить сам хук, нужно скопировать оригинальный код. Пишем функцию для установки jmp хука(опкод у JMP - E9): C++: Код:
voidВ конец трамплина мы добавляем прыжок обратно, чтобы продолжить выполнение и ничего не сломать Теперь пишем обработчик хука: К сожалению в голых хуках не обойтись без функции-обработчика с ассемблерным кодом. C++: [CODE] void HOOK_AddChatMessage ( void * pChat , int nType , const char * szText , const char * szPrefix , unsigned long textColor , unsigned long prefixColor ) { std :: cout : " Вероятно посмотрев на код вы ничего не поняли. Сейчас объясню. Т.к. теперь телом хука выступает функция HOOK_Raw_AddChatMessage и из нее мы вызываем наш обработчик, т.к. вызов делаем мы сами, то Код:
__fastcallКод:
__declspec(naked)pushad и popad нужны для сохранения и возврата в исходное состояние регистров. Т.к. мы указали компилятору не генерировать код на входе и выходе, то сохранять регистры некому, поэтому делаем это вручную. Аргументы со стека теперь придется тащить самим, т.к. мы перехватываем уже после вызова. В этот момент на стеке появится еще одно значение, и еще один вызов сместит все аргументы в функции на 1, и произойдет не то, чего мы ожидали. Теперь устанавливаем сам хук. Ставить будем тут: Код: Код:
.text:10067460 000 55 push ebpC++: Код:
// Где-то вне функцийСнова компилируем, кидаем в папку игры и видим в консоли все сообщения из чата. На этот раз все, а не только сообщений от сервера, ведь мы перехватили саму функцию AddChatMessage, а не одно из ее использований Цитата:
|
Цитата:
|
Цитата:
Цитата:
|
Цитата:
|
|
Цитата:
Цитата:
|
Цитата:
Ловушка больше подходит для UEF-хуков, но они медленные |
Цитата:
|
Дайте ссылку на 1 тему, я не могу найти
|
Цитата:
|
| Время: 21:03 |