![]() |
Хотел продолжить первый гайд, но понял что нужно объяснить что такое хуки
В этом гайде я расскажу что такое хуки, как они работают, и как их использовать. При использовании на других ресурсах необходимо указание авторства и ссылки на оригинальную темы! Перед тем как начать: Так как этот гайд сделан в целях обучения, здесь не будет показано использование готовых библиотек, а только сырой код. В процессе написания гайда я понял что без знаний ассемблера и низкоуровневых вы поймете лишь малую часть от написанного здесь. Но если вы поймете что здесь написано - будет очень хорошо. Все действия производились на 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 тему, я не могу найти
|
Цитата:
|
Цитата:
Делать мне было нечего, а работать не хотелось, поэтому вы видите этот гайд Создание ASI-плагина с нуля Хуки – что это такое и как с ними работать Безопасная инициализация и работа с SAMP Работа с рендером и Directx9 Обработка событий окна + ImGui В этом гайде мы создадим свой ASI-плагин с... www.blast.hk |
Сложно очень... 🙁
|
Цитата:
|
Цитата:
Расскажи, как ты посчитал это. Ибо, вообще нихуя не понятно. |
Цитата:
|
Цитата:
|
Цитата:
не дописал, четвёртый* |
Цитата:
|
Цитата:
Зачем ты сложил адрес хука??? HookAddress + HookAddress... ЗАЧЕМ??? Если нужно вычесть. Но, Киня же сделал зачем-то наоборот... Как мне быть, если я ничего не понимаю... 😭 |
Цитата:
опкод jmp, размер которой 5 (x86), прыгает на определенное место в коде и она является релативной, то есть на сколько прыгнуть ПОСЛЕ себя. например ты на адрес 10 и тебе надо прыгнуть на адрес 20. чтобы это сделать воспользуемся jmp. подводя итог прошлому абзацу, после инструкции jmp адрес будет 15 (т.к её размер 5) соотвественно нам нужно прыгнуть еще на 5. как это вычислить математически? jmp {куда надо}20-{откуда}10-{размер инструкции}5=5, то есть jmp 5. так же работает и call, но она делает ещё несколько действий кроме прыжка. с этим разобрались. теперь, то что ты процитировал, kin4stat берет адрес оригинальной функции (для дальнейшего её вызова чтобы не прерывать flow программы) из существующей инструкции call/jmp (не читал контекст). пользуясь знаниями из прошлого абзаца, зная как устроена инструкция jmp (JMP ADDR), то на 0 месте находится опкод, а на 1-4 сам РЕЛАТИВНЫЙ адресс. возьмем пример из прошлого абзаца: jmp 5 на адресе 10, соотвественно после этой инструкции программа будет на адресе 20, его нам и нужно достать. addr = {размер прыжка}5+{текущий адрес}10+{размер инструкции jmp}5=20. мы добавляем 5 т.к jmp берет за основу адрес после самой себя, поэтому при её построение мы отнимаем (т.к эти байты нам прыгать не надо), а при возвращение оригинального адреса мы их добавляем. вроде все понятно, не перечитывал да и по*** |
Цитата:
20 - (10 - 5) = 15... У нас в итоге не получается адрес равному 20... |
Цитата:
|
Цитата:
|
Цитата:
Цитата:
15(адрес после jmp)+5(на сколько прыгнуть)=20 |
У меня одного jmp хук вызывает краш при запуске игры? В консоль выводится только первое сообщение сампа, и дальше моментальный краш.
|
void* SetCallHook(uintptr_t HookAddress, void* DetourFunction) {
uintptr_t OriginalFunction = *reinterpret_cast(HookAddress + 1) + HookAddress + 5; DWORD oldProt; VirtualProtect(reinterpret_cast(HookAddress + 1), sizeof(uintptr_t), PAGE_READWRITE, &oldProt); *reinterpret_cast(HookAddress + 1) = reinterpret_cast(DetourFunction) - HookAddress - 5; VirtualProtect(reinterpret_cast(HookAddress + 1), sizeof(uintptr_t), oldProt, &oldProt); return reinterpret_cast(OriginalFunction); } а причем тут хук адрес + 1 Откуда взялась единица? объясните пожалуйста |
Цитата:
|
Цитата:
|
| Время: 12:42 |