HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2
НОВЫЕ ТОРГОВАЯ НОВОСТИ ЧАТ
loading...
Скрыть
Вернуться   ANTICHAT > ПРОГРАММИРОВАНИЕ > С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 02.08.2020, 15:57
Const
Новичок
Регистрация: 28.10.2018
Сообщений: 28
С нами: 3970020

Репутация: 18
По умолчанию

Всем привет. В последнее время я стал замечать, что абсолютное большинство использует всякие готовые библиотеки для хуков, и даже не заморачиваются о строении функций, типах хуков и прочим. Как таковых гайдов очень мало, а если и есть - на английском языке. В этой теме я расскажу обо всем просто и понятно.

Начнем с банального. Да кто такой все таки, этот ваш, хук?! Хук, с английского обозначает: "ловить". Назвали его так, потому что он ловит вызовы каких либо команд. Не забегая вперед, расскажу о основных командах, которые вы должны знать.

Абсолютно все процессы состоят из машинного кода. Это последовательность битов, но не бойтесь, с битами работать нам не потребуется (на начальных этапах). При помощи различных дизассемблеров (я использую IDA Pro), можно этот машинный код превратить в ассемблерный. Если вы собираетесь работать с хуками, вы не обойдетесь без IDA Pro и Cheat Engine, так что устанавливайте эти программы. Рассказывать, как с ними работать, я не буду, в интернете полно гайдов.

Нам предоставлен ассемблерный код программы, нам нужно знать две основные команды:

JMP - команда прыжка. Ее опкод - 0xE9. Выполняет прыжок с одного место, на другое. Если есть JMP на одно место, обязательно есть прыжок обратно, чтобы не прервать цикл игры.

CALL - команда вызова. Имет одинаковую инструкцию размером в 5 байт, как и JMP. Единственное, чем отличается, так это не обязательно прыгать обратно.

У этих команд одинаковые инструкции. Допустим, у нас есть адрес 0xFF00FF. Это адрес на инструкцию одной из этих команд. Если не прибавлять ничего, считывая 1 байт (uint8_t) с этого адреса мы получим как раз таки опкод одной из этих команд. По смещению +1 от адреса инструкции, мы получим релативный адрес, размером в 4 байта. Он отличается от обычного адреса, куда хочет прыгнуть или откуда хочет вызвать опкод. Он высчитывается по такой формуле: (куда_прыгаем или откуда_вызываем) - (откуда_прыгаем или где_вызываем) - 5.

Допустим, у нас есть такая штучка:

Код:





Код:
.text:0053ECBD 004                 call    _Idle


Это вызов функции _Idle. Адрес функции _Idle - 0x0053E920. В данном случае, релативный адрес будет таким: 0x0053E920 - 0x0053ECBD - 5. Этот релативный адрес будет располагаться по адресу инструкции + 1. В данном случае, 0x0053ECBD + 1, и будет иметь размер 4 байта (uint32_t).

Получается, чтобы получить опкод, нам нужно выполнить такое чтение:

C++:





Код:
uint8_t
call_opcode
=
*
reinterpret_cast

(
0x0053ECBD
)
;


Чтобы получить релативный адрес, такое:

C++:





Код:
uint32_t
relative_address
=
*
reinterpret_cast

(
0x0053ECBD
+
1
)
;


Я думаю, основную логику инструкций JMP и CALL вы поняли, расскажу про первый метод хука, как я его назвал, Redirect.

Суть хука заключается в том, чтобы подменить релативный адрес команды JMP/CALL на свой. Чтобы данное провернуть, нужно снять протекцию с региона функцией VirtualProtect, занопить всю инструкцию размером в 5 байт функцией memset (на всякий), подменить опкод на CALL/JMP (0xE8/0xE9), записав 1 байт, и подменить релативный адрес, который высчитать по формуле, которую я представил выше, и восстановить протекцию.

C++:





Код:
unprotect_region
protect_of_region
(
pointer_on_source
,
size_of_default_instruction
)
;
// Инициализируем класс unprotect_region. Снимаем защиту с региона памяти.
original_instructions
.
first
=
*
reinterpret_cast

(
reinterpret_cast

(
pointer_on_source
)
)
;
// Записываем в пару оригинальный опкод команды.
original_instructions
.
second
=
*
reinterpret_cast

(
reinterpret_cast

(
pointer_on_source
)
+
0x01
)
;
// Записываем в пару оригинальный релативный адрес.
std
::
memset
(
pointer_on_source
,
no_operation_opcode
,
size_of_default_instruction
)
;
// Ноплю всю инструкцию JMP/CALL, её статичный размер для x86 - 5 байт.
*
reinterpret_cast

(
pointer_on_source
)
=
method_of_hook
;
// Меняем оригинальный опкод на опкод команды, которую мы указали в параметрах.
uint32_t
relative_address
=
reinterpret_cast

(
pointer_on_destination
)
-
reinterpret_cast

(
pointer_on_source
)
-
5
;
// Вычисляем релативный адрес прыжка от pointer_on_source до pointer_on_destination.
// Если говорить проще - прыгаем с оригинальной функции на нашу.
*
reinterpret_cast

(
reinterpret_cast

(
pointer_on_source
)
+
0x01
)
=
relative_address
;
// Перезаписываем релативный адрес на собственный.
protect_of_region
.
~
unprotect_region
(
)
;
// Вызываем деструктор класса, восстанавливаем оригинальный уровень протекции региона.


pointer_on_source - указатель на адрес инструкции JMP/CALL. pointer_on_destination - указатель на вашу функцию, на которую вы подменили вызов.

В вашей функции, вы должны вызвать оригинальную функцию, на которую был вызов.

В данном случае:

C++:





Код:
//int __usercall Idle@(int a1@, int a2@, int bp0@, int a4@, int a5@, long double a6@, int a3)
using
idle_t
=
int
(
__cdecl
*
)
(
int
,
int
,
int
,
int
,
int
,
long
double
,
int
)
;
idle_t idle
=
reinterpret_cast

(
0x0053E920
)
;
int
idle_hook
(
int
a1
,
int
a2
,
int
bp0
,
int
a4
,
int
a5
,
long
double
a6
,
int
a3
)
{
return
idle
(
a1
,
a2
,
bp0
,
a4
,
a5
,
a6
,
a3
)
;
// Вызываем оригинальную функцию.
}


Прошу заметить, в idle_hook я не указал __cdecl, только потому что в С++ функции по умолчанию имеют такое соглашение о вызовах. Если бы функция была __stdcall - вы бы записали __stdcall, если бы __thiscall, то пришлось бы поступить немного по-другому.

В idle_hook вы бы приписали соглашение о вызовах __fastcall, в idle_t - __thiscall, а в idle_hook первым параметром вы бы записали void *, и вторым тоже. В итоге у вас бы получилось:

C++:





Код:
using
idle_t
=
int
(
__thiscall
*
)
(
void
*
,
int
,
int
,
int
,
int
,
int
,
long
double
,
int
)
;
idle_t idle
=
reinterpret_cast

(
0x0053E920
)
;
int
__fastcall
idle_hook
(
void
*
_this
,
void
*
unused
,
int
a1
,
int
a2
,
int
bp0
,
int
a4
,
int
a5
,
long
double
a6
,
int
a3
)
{
return
idle
(
_this
,
a1
,
a2
,
bp0
,
a4
,
a5
,
a6
,
a3
)
;
// Вызываем оригинальную функцию.
}


Почему не __thiscall, спросите вы. А все потому, что компиляторы не дают статическим функциям иметь данное соглашение. __fastcall очень похож по строению пролога (поговорим чуть позже об этом) на __thiscall, только одна разница - во второй параметр добавляется еще один указатель, он не используется. Просто не трогайте его.

С Redirect хуками мы разобрались, теперь расскажу о Trampoline.

Trampoline от Redirect кардинально отличается. Если Redirect просто подменяет релативный адрес команды вызова или прыжка, то Trampoline взаимодействует с прологом функции.

Что такое пролог функции? Пролог функции - первые несколько байт функции, которые подготавливают стек, пушат регистры.

У пролога есть свой эпилог. Эпилог отличается тем, что он располагается в конце функции, и восстанавливает стек и регистры до того состояния, которое было до вызова.

Расскажу на примере функции void __cdecl CTimer__Update(void):



Логика трамплин хука заключается в том, чтобы этот самый пролог сохранить в отдельную функцию, занопить весь пролог, поставить там прыжок на нашу функцию, в нашей функции вызвать ту, в которой мы сохранили пролог, и вдобавок приписать туда прыжок обратно.

Получается так: вместо пролога, jmp -> наша_функция -> jmp трамплин -> jmp обратно (+1, чтобы не было рекурсии).

C++:





Код:
if
(
length_of_prologue

(
pointer_on_source
)
-
reinterpret_cast

(
pointer_on_gateway
)
-
5
;
// Вычисляем релативный адрес прыжка от трамплина до оригинальной функции.
*
reinterpret_cast

(
reinterpret_cast

(
pointer_on_gateway
)
+
length_of_prologue
)
=
0xE9
;
// Записываем опкод прыжка, +1 байт после пролога в трамплине.
*
reinterpret_cast

(
reinterpret_cast

(
pointer_on_gateway
)
+
length_of_prologue
+
0x01
)
=
relative_address
;
// Записываем релативный адрес прыжка на ориг. функцию, +2 байта после пролога.
unprotect_region
protect_of_region
(
pointer_on_source
,
length_of_prologue
)
;
// Инициализируем класс, снимаем защиту памяти.
std
::
memset
(
pointer_on_source
,
0x90
,
length_of_prologue
)
;
// Обнуляем весь пролог оригинальной функции.
*
reinterpret_cast

(
pointer_on_source
)
=
0xE9
;
// Подменяем первый байт пролога на опкод прыжка.
relative_address
=
reinterpret_cast

(
pointer_on_destination
)
-
reinterpret_cast

(
pointer_on_source
)
-
5
;
// Вычисляем релативный адрес прыжка от оригинальной функции на нашу.
*
reinterpret_cast

(
reinterpret_cast

(
pointer_on_source
)
+
0x01
)
=
relative_address
;
// Перезаписываем релативный адрес.
protect_of_region
.
~
unprotect_region
(
)
;
// Восстанавливаем протекцию региона.


В использовании:

C++:





Код:
#include "hook/hook.h"
struct
vec3d
{
float
x
,
y
,
z
;
}
;
using
process_aim_t
=
void
(
__thiscall
*
)
(
void
*
cam_pointer
,
vec3d
*
position_of_camera
,
float
*
,
float
*
,
float
*
)
;
process_aim_t process_aim
;
void
__fastcall
process_aim_hook
(
void
*
cam_pointer
,
void
*
not_used
,
vec3d
*
position_of_camera
,
float
*
_1
,
float
*
_2
,
float
*
_3
)
{
process_aim
(
cam_pointer
,
position_of_camera
,
_1
,
_2
,
_3
)
;
}
class
guide_of_hooking
{
public
:
hook
*
hooked_call_process_aim
;
guide_of_hooking
(
)
{
hooked_call_process_aim
=
new
hook
(
0x00521500
,
process_aim_hook
,
13
)
;
// 13 - размер пролога.
process_aim
=
hooked_call_process_aim
->
get_trampoline

(
)
;
}
~
guide_of_hooking
(
)
{
delete
hooked_call_process_aim
;
}
}
guide_of_hooking
;


Если останутся вопросы, напишите в комментарии.

Исходный код на GitHub - https://github.com/dev-Const/hook/blob/master/hook.h

Пара примеров:

C++:





Код:
#include "hook/hook.h"
using
timer_update_t
=
void
(
__cdecl
*
)
(
)
;
timer_update_t timer_update
=
reinterpret_cast

(
0x00561B10
)
;
// void __cdecl CTimer__Update()
void
timer_update_hook
(
)
{
timer_update
(
)
;
}
class
guide_of_hooking
{
public
:
hook
*
hooked_call_timer_update
;
guide_of_hooking
(
)
{
// .text:0053E968 | 00C | call _ZN6CTimer6UpdateEv
hooked_call_timer_update
=
new
hook
(
0x0053E968
,
timer_update_hook
,
0
,
redirect_hook
,
call_method
)
;
// Размер пролога 0, потому что он здесь не требуется, мы подменяем CALL.
}
~
guide_of_hooking
(
)
{
delete
hooked_call_timer_update
;
}
}
guide_of_hooking
;


C++:





Код:
#include "hook/hook.h"
using
timer_update_t
=
void
(
__cdecl
*
)
(
)
;
timer_update_t timer_update
;
void
timer_update_hook
(
)
{
timer_update
(
)
;
// Вызываем его.
}
class
guide_of_hooking
{
public
:
hook
*
hooked_call_timer_update
;
guide_of_hooking
(
)
{
/*
            .text:00561B10 | 000 | mov ecx, _timerFunction
            .text:00561B16 | 000 | sub esp, 0Ch
        */
hooked_call_timer_update
=
new
hook
(
0x00561B10
,
timer_update_hook
,
6
)
;
timer_update
=
hooked_call_timer_update
->
get_trampoline

(
)
;
// Получаем трамплин.
}
~
guide_of_hooking
(
)
{
delete
hooked_call_timer_update
;
}
}
guide_of_hooking
;
 
Ответить с цитированием

  #2  
Старый 02.08.2020, 17:09
SiTrak
Участник форума
Регистрация: 14.01.2018
Сообщений: 192
С нами: 4384333

Репутация: 113
По умолчанию

опкодер разлогинься
 
Ответить с цитированием

  #3  
Старый 02.08.2020, 17:11
Const
Новичок
Регистрация: 28.10.2018
Сообщений: 28
С нами: 3970020

Репутация: 18
По умолчанию

Цитата:
Сообщение от SiTrak  

опкодер разлогинься
Нет, я не опкодер, но тоже по своему криминально тут сижу.
 
Ответить с цитированием

  #4  
Старый 04.10.2022, 20:15
Hy_u_Ladno
Участник форума
Регистрация: 28.11.2020
Сообщений: 112
С нами: 2872827

Репутация: 18
По умолчанию

мит хук?
 
Ответить с цитированием
Ответ





Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.