Эта статья не совсем для новичков, вы уже должны знать, что такое указатели, как работают указатели, как работает память компьютера, что такое смещения, что такое классы, и как присваивать адреса классам. Данную информацию вы можете изучить на GuidedHacking, и о адресах в моей теме: Статья - Память и адреса компьютера [#1]
Сегодня я объясню, как работает получение функций из виртуальных таблиц классов.
Как можете видеть на скриншоте, все отлично работает, нам выводит наибольший индекс существующей сущности. И нет, на сервере не 227 игроков, просто к сущностям относятся не только игроки. Проверить работоспособность этого можно вызвав и другой метод, но я вас уверяю, вас бы просто крашнуло, если что-то было не так.
Сегодня я объясню, как работает получение функций из виртуальных таблиц классов.
VTable или виртуальная таблица - массив по которому находятся указатели на объявленные методы производного класса.
Виртуальные таблицы находятся по первому смещению в объектах(классах).
Когда вызывается метод(функция), мы ищем виртуальную таблицу объекта, и вызываем соответствующий метод производного класса.
Методы в виртуальной таблице находятся по каждому 4 байту в 32-х битном приложении, и каждому 8 байту в 64-х. То есть, в 32 битном приложении первая функция в таблице будет по адресу 0x0, вторая по 0x4, третья по 0x8, и так далее.
Методы из VTable имеют сооглашения о вызовах __thiscall*, потому-что им требуется, чтобы указатель this(объекта) передавался в ECX.
Виртуальные таблицы находятся по первому смещению в объектах(классах).
Когда вызывается метод(функция), мы ищем виртуальную таблицу объекта, и вызываем соответствующий метод производного класса.
Методы в виртуальной таблице находятся по каждому 4 байту в 32-х битном приложении, и каждому 8 байту в 64-х. То есть, в 32 битном приложении первая функция в таблице будет по адресу 0x0, вторая по 0x4, третья по 0x8, и так далее.
Методы из VTable имеют сооглашения о вызовах __thiscall*, потому-что им требуется, чтобы указатель this(объекта) передавался в ECX.
Теперь мы можем выстроить иерархию до метода из виртуальной таблицы какого-либо объекта(если вы все поняли, конечно же).
Но нам вовсе не нужно добавлять смещение до метода, потому-что виртуальная таблица это массив, поэтому нам нужно это реализовать индексированием, потому-что каждый индекс также занимает 4 байта в 32-х битном приложении, и 8 байтов в 64-х битном.
То есть, схема теперь будет выглядеть так:
Базовый адрес модуля объекта -> Адрес объекта -> смещение до VTable(всегда 0x0) -> смещение до метода
Но нам вовсе не нужно добавлять смещение до метода, потому-что виртуальная таблица это массив, поэтому нам нужно это реализовать индексированием, потому-что каждый индекс также занимает 4 байта в 32-х битном приложении, и 8 байтов в 64-х битном.
То есть, схема теперь будет выглядеть так:
Базовый адрес модуля объекта -> Адрес объекта -> смещение до VTable[ индекс метода ]
Для гораздо более удобных манипуляций с свойствами и методами классов игр мы создаём собственный класс, которому присваиваем адрес класса игры, таким образом "реконструируя" класс игры.
Найти адрес класса игры можно разными способами, но в некоторых игровых движках существует у модулей экспортируемая функция "CreateInterface", которая как раз и вернёт адрес интересующего нам класса игры. Я не буду рассказывать, как это сделать, за меня это давно сделали, оставляю ссылки:
https://guidedhacking.com/threads/csgo-createinterface-tutorial-how-to-get-interfaces.14701/
Найти адрес класса игры можно разными способами, но в некоторых игровых движках существует у модулей экспортируемая функция "CreateInterface", которая как раз и вернёт адрес интересующего нам класса игры. Я не буду рассказывать, как это сделать, за меня это давно сделали, оставляю ссылки:
https://guidedhacking.com/threads/csgo-createinterface-tutorial-how-to-get-interfaces.14701/
Если вы уже знаете о получении интерфейсах, и вызове методов из него, то для вас это интересующий вопрос, ведь вы и так можете легко получить интересующий вас метод просто объявив виртуальную функцию по нужному индексу.
В этом и проблема, что вам нужно объявлять функцию по конкретному индексу, иначе у вас ничего не получится.
В некоторых классах игр могут быть абсолютно не нужные для вас методы, как например здесь:
cstrike15_src/cdll_int.h at f82112a2388b841d72cb62ca48ab1846dfcc11c8 · perilouswithadollarsign/cstrike15_src
Поэтому в конце концов мы сталкиваемся с тем, что у вас появляется нужда в получении метода по вашему указанному индексу.
Вы бы могли конечно создавать кучу методов-пустышек, как на примере ниже, но зачем? Это занимает место, и выглядит плохо. Поэтому я подробно расскажу, как получать метод по вашему указанному индексу.
В этом и проблема, что вам нужно объявлять функцию по конкретному индексу, иначе у вас ничего не получится.
В некоторых классах игр могут быть абсолютно не нужные для вас методы, как например здесь:
cstrike15_src/cdll_int.h at f82112a2388b841d72cb62ca48ab1846dfcc11c8 · perilouswithadollarsign/cstrike15_src
Поэтому в конце концов мы сталкиваемся с тем, что у вас появляется нужда в получении метода по вашему указанному индексу.
Вы бы могли конечно создавать кучу методов-пустышек, как на примере ниже, но зачем? Это занимает место, и выглядит плохо. Поэтому я подробно расскажу, как получать метод по вашему указанному индексу.
Ну теперь, когда вы узнали базовую информацию о виртуальных таблицах, то можно перейти непосредственно к реализации получения метода из неё.
В разных читах это делается по разному, но я попытаюсь для примера максимально это упростить, а после покажу все это дело в одной строке без шаблонов и переменных.
Для примера я покажу получение метода GetHighestEntityIndex из объекта IClientEntityList игры CS:GO
В разных читах это делается по разному, но я попытаюсь для примера максимально это упростить, а после покажу все это дело в одной строке без шаблонов и переменных.
Для примера я покажу получение метода GetHighestEntityIndex из объекта IClientEntityList игры CS:GO
Первым делом нам требуется шаблон функции, который мы можем сделать с помощью объявления using либо typedef. Шаблон функции будет представлять возвращаемое значение функции, сооглашение о вызове, и аргументы функции. Объявлять шаблон функции с using либо typedef решать вам, разницы нет.
C++:
// Шаг 1: Объявляем using либо typedef,
// после using сразу пишем название шаблона функции
using GetHighestEntityIndexFn =
typedef
// Шаг 2: Теперь нам нужно указать возвращаемое значение метода,
// написать сооглашение о вызове __thiscall*,
// и только после этого название шаблона функции в случае с typedef
using GetHighestEntityIndexFn = int( __thiscall* )
typedef int( __thiscall* GetHighestEntityIndexFn )
// Шаг 3: Теперь в скобках пишем аргументы функции,
// первый аргумент всегда void* в который помещается наш объект(this),
// который мы получили с помощью CreateInterface(https://guidedhacking.com/threads/csgo-createinterface-tutorial-how-to-get-interfaces.14701/)
using GetHighestEntityIndexFn = int( __thiscall* )( void* );
typedef int( __thiscall* GetHighestEntityIndexFn )( void* );
Теперь требуется получить адрес массива VTable из объекта, тут не трудно, просто преобразуем полученный интерфейс класса(либо адрес класса) 2 раза в указатель, и разыменовываем, сначала, чтобы получить указатель на объект, потом, чтобы получить указатель первого смещения, по которому всегда находится VTable.
C++:
// C
uintptr_t* virtual_table = *( uintptr_t** )g_interfaces.m_pClientEntityList;
// C++
uintptr_t* virtual_table = *reinterpret_cast< uintptr_t** >( g_interfaces.m_pClientEntityList );
Тут ещё проще, объявляем переменную, и присваиваем ей адрес метода, который мы получаем из массива виртуальной таблицы. В моем случае, индекс функции равен 6, учитывайте, что индексы функций начинаются с нуля, а не с единицы!
Последнее действие - преобразование адреса метода в наш шаблон функции. Также делаем это объявлением переменной, которую мы будем уже использовать, как вызов метода.
C++:
uintptr_t get_highest_entity_index_address = virtual_table[ 6 ];
Последнее действие - преобразование адреса метода в наш шаблон функции. Также делаем это объявлением переменной, которую мы будем уже использовать, как вызов метода.
C++:
// C
GetHighestEntityIndexFn get_highest_entity_index_fn = ( GetHighestEntityIndexFn )get_highest_entity_index_address;
// C++
GetHighestEntityIndexFn get_highest_entity_index_fn = reinterpret_cast< GetHighestEntityIndexFn >( get_highest_entity_index_address );
Как можете видеть на скриншоте, все отлично работает, нам выводит наибольший индекс существующей сущности. И нет, на сервере не 227 игроков, просто к сущностям относятся не только игроки. Проверить работоспособность этого можно вызвав и другой метод, но я вас уверяю, вас бы просто крашнуло, если что-то было не так.
Конечно же мы не будем для каждого метода создавать столько переменных и шаблонов, это просто нецелесообразно.
Для начала избавимся от адреса метода, и получим следующий результат:
Пора избавиться от переменной с виртуальной таблицей! Для этого мы также должны получить виртуальную таблицу, но теперь преобразуем её не в uintptr_t(unsigned int), а в шаблон функции, и после этого оборачиваем все это дело в скобки, и указываем индекс метода. Оборачивать в скобки нужно обязательно, иначе мы попытаемся взять данные не из массива виртуальной таблицы(и вообще у вас по идеи не получится, вы должны получить ошибку от компилятора).
Последнее действие - избавиться от шаблона. Для этого мы просто помещаем шаблон без using/typedef и названия в преобразование адреса метода, и добавляем два уровня указателя, после __thiscall*. У нас получится аж 3 уровня указателя, но почему? Потому-что сооглашение о вызове __thiscall* всегда идёт с указателем, и так как нам нужно метод получить из виртуальной таблицы, то мы добавляем ещё два уровня указателя. Но не забываем про тип переменной, вместо типа шаблона функции мы также просто пишем сам шаблон функции, и после __thiscall* пишем название переменной.
Для начала избавимся от адреса метода, и получим следующий результат:
C++:
// C
GetHighestEntityIndexFn get_highest_entity_index_fn = ( GetHighestEntityIndexFn )( virtual_table[ 6 ] );
// C++
GetHighestEntityIndexFn get_highest_entity_index_fn = reinterpret_cast< GetHighestEntityIndexFn >( virtual_table[ 6 ] );
C++:
using GetHighestEntityIndexFn = int( __thiscall* )( void* );
uintptr_t* virtual_table = *reinterpret_cast< uintptr_t** >( g_interfaces.m_pClientEntityList );
GetHighestEntityIndexFn get_highest_entity_index_fn = reinterpret_cast< GetHighestEntityIndexFn >( virtual_table[ 6 ] );
Пора избавиться от переменной с виртуальной таблицей! Для этого мы также должны получить виртуальную таблицу, но теперь преобразуем её не в uintptr_t(unsigned int), а в шаблон функции, и после этого оборачиваем все это дело в скобки, и указываем индекс метода. Оборачивать в скобки нужно обязательно, иначе мы попытаемся взять данные не из массива виртуальной таблицы(и вообще у вас по идеи не получится, вы должны получить ошибку от компилятора).
C++:
// C
GetHighestEntityIndexFn get_highest_entity_index_fn = ( *( GetHighestEntityIndexFn** )( g_interfaces.m_pClientEntityList ) )[ 6 ];
// C++
GetHighestEntityIndexFn get_highest_entity_index_fn = ( *reinterpret_cast< GetHighestEntityIndexFn** >( g_interfaces.m_pClientEntityList ) )[ 6 ];
C++:
using GetHighestEntityIndexFn = int( __thiscall* )( void* );
GetHighestEntityIndexFn get_highest_entity_index_fn = ( *reinterpret_cast< GetHighestEntityIndexFn** >( g_interfaces.m_pClientEntityList ) )[ 6 ];
Последнее действие - избавиться от шаблона. Для этого мы просто помещаем шаблон без using/typedef и названия в преобразование адреса метода, и добавляем два уровня указателя, после __thiscall*. У нас получится аж 3 уровня указателя, но почему? Потому-что сооглашение о вызове __thiscall* всегда идёт с указателем, и так как нам нужно метод получить из виртуальной таблицы, то мы добавляем ещё два уровня указателя. Но не забываем про тип переменной, вместо типа шаблона функции мы также просто пишем сам шаблон функции, и после __thiscall* пишем название переменной.
C++:
// C
int( __thiscall* get_highest_entity_index_fn )( void* ) = ( *( int( __thiscall*** )( void* ) )( g_interfaces.m_pClientEntityList ) )[ 6 ];
// C++
int( __thiscall* get_highest_entity_index_fn )( void* ) = ( *reinterpret_cast< int( __thiscall*** )( void* ) >( g_interfaces.m_pClientEntityList ) )[ 6 ];
Конечно же мы не будем в каждой функции нашего чита создавать переменную-метод. Для упрощения нашей жизни мы создадим функцию в интерфейс-классе, который мы и создали для этого. Туда мы поместим наш код, но без переменной, сразу возвращая вызов метода, не забывайте, что первый аргумент всегда объект, так как наша функция вызывается из этого же объекта, то просто пишем this. Выглядеть это будет примерно так:
C++:
class IClientEntityList {
enum Indicies : size_t {
GETHIGHESTENTITYINDEX = 6
};
public:
int GetHighestEntityIndex( ) { //Индекс метода //Вызываем метод
return ( *reinterpret_cast< int( __thiscall*** )( void* ) >( g_interfaces.m_pClientEntityList ) )[ GETHIGHESTENTITYINDEX ]( this );
}
};
Конечно же каждый раз писать столько кода не очень читабельно, и удобно, учитывая, что в одном классе игры может быть более 10 методов. Поэтому мы можем создать дефайн для всего этого дела. Я поделюсь с вами своими дефайнами. Я не буду их разбирать, тут должно быть все понятно, только оставлю ссылку на ресурс, где вы можете узнать о вариативных макросах.
Variadic macros
Пример использования
Написал на английском потому-что на русском не уместится.
C++:
#define GET_VIRTUAL_METHOD( return_type, method_index, ... ) ( *reinterpret_cast< return_type ( __thiscall*** )( void*, __VA_ARGS__ ) >( this ) )[ method_index ]
#define VIRTUAL_METHOD( return_type, name, method_index, call_args, ... ) \
return_type name( __VA_ARGS__ ) noexcept { \
return GET_VIRTUAL_METHOD( return_type, method_index, __VA_ARGS__ )call_args; \
}
Пример использования
Написал на английском потому-что на русском не уместится.
Получать функции игр по индексу из виртуальной таблицы объектов очень удобно, и экономит много времени.
Теперь вы знаете, что такое виртуальная таблица, как из неё получать методы(функции), и использовать.
Надеюсь я достаточно хорошо разобрал получение методов из виртуальных таблиц, и вы все поняли, но если вам что-то не понятно, напишите в тему, я постараюсь помочь.
Всех с новым годом!
Теперь вы знаете, что такое виртуальная таблица, как из неё получать методы(функции), и использовать.
Надеюсь я достаточно хорошо разобрал получение методов из виртуальных таблиц, и вы все поняли, но если вам что-то не понятно, напишите в тему, я постараюсь помочь.
Всех с новым годом!
Последнее редактирование: