Участник
-
Автор темы
- #1
прелюдия, скипайте если вы не совсем чайник и знаете что такое указатель хуки тайпдефы и прочий шлак.
в гайде будут использоваться сокращения для типов unsigned long long и const char*
#define ui unsigned long long
#define cc const char*
для начала вывод в консоль. берем любой модуль открываем в IDA, пусть будет engine2.dll, чекаем импорты, ищем что-нибудь очевидное типа "Msg".
видим что это экспорт из tier0.dll, берем базу этого модуля в оперативке и берем оттуда экспорт Msg
вполне справедливо предположить что функция работает так же как и printf, принимает %s %d и подобные токены и заменяет их параметрами.
typedef void(__fastcall* ConMsg)(cc rcx, ui rdx, ui r8, ui r9);
ConMsg CMsg = 0;
...
CMsg = (ConMsg)GetProcAddress(GetModuleHandleA("tier0.dll"), "ConMsg");
ну и оверлоады для удобства
void CMSG(cc pure) {
CMsg(pure, 0, 0,0);
}
void CMSG(cc format, ui p1) {
CMsg(format, p1, 0,0);
}
void CMSG(cc format, ui p1, ui p2) {
CMsg(format, p1, p2,0);
}
void CMSG(cc format, ui p1, ui p2, ui p3) {
CMsg(format, p1, p2,p3);
}
вызываем
CMSG("injection completed successfuly at %s!", (ui)std::to_string(std::time(0)).c_str());//#include <ctime>, #include <string> но это чисто ради примера конечно же <ctime> скорее всего вам нахрен не нужен будет.
воаля видим в консоли "injection completed successfuly at 1591207471!"
давайте перейдем к сущностям. что это такое? это любой игровой объект. крип герой дерево мир что угодно. для упрощения задачи в 1000 раз качаем бинарки мак ос с символами
обычно все что связано с сущностями это модуль client.dll, поэтому открываем в IDA наш client.dylib и ищем там символы типа GetEnt FindEnt EntFind и т.д., конкретно в этом гайде давайте рассмотрим функцию ent_find(CCommandContext const&,CCommand const&).
декомпилим видим как получается первая сущность в ентити листе:
сущность = CGameEntitySystem::NextEnt(g_pGameEntitySystem, 0);
дальше снова вызывается NextEnt(g_pGameEntitySystem, сущность) и так пока сущность != 0;
казалось бы все просто но лично у меня крашило иногда от такой итерации(именно когда получал последующую сущность через NextEnt)(мб кривой код просто хз, но убрав эту говнофункцию краши исчезли магическое совпадение или логическое заключение не знаю). ручная итерация быстрее(на наносекунды, ха-ха) и надежнее.
не будем париться расскажу вам все секреты: у CEntityInstance(родитель C_BaseEntity) есть член CEntityIdentity* m_pEntity, а у CEntityIdentity есть члены C_BaseEntity* m_pEnt и CEntityIdentity* m_pNext. то есть имея первую сущность мы берем ее CEntityIdentity идем к m_pNext и берем оттуда m_pEnt и вот наша следующая сущность.(данная теория выведена путем тыка туда сюда по ячейкам в дебагере и реклассе)
первый скрин. C_BaseEntity ent1 в памяти. на оффсете 0x10 лежит указатель на CEntityIdentity, условно _ent1
второй скрин. CEntityIdentity _ent1 в памяти. на 0х0 лежит указатель на C_BaseEntity ent1, а на 0х58 лежит указатель на CEntityIdentity у ent2
третий скрин. та самая CEntityIdentity у сущности ent2. на 0х0 лежит указатель на саму сущность, на 0х58 указатель на CEntityIdentity третьей сущности, на 0х50 указатель на CEntityIdentity у сущности ent1(он нам не нужен)
псевдокод итерации:
C_BaseEntity* ent = CGameEntitySystem::NextEnt(entsystem,0);
while (ent) {
...
std::cout << ent->health << std::endl;
...
if (!ent->m_pEntity->m_pNext) break;
ent = ent->m_pEntity->m_pNext->m_pEnt;
}
оффсеты : m_pEntity - +0x10
m_pNext - +0x58
m_pEnt - +0
находим нашу ent_find по сиге(ибо это не виртуалка, то есть вызывается по относительному адресу а не по дереференсу [вмт+индекс*8]):
(сигу делаем разумеется уже в client.dll, найдете эту функцию по хрефу "format: ent_find")
что такое сига? это последовательность байт инструкций функции.
(заходим в IDA options->general->number of opcode bytes(non-graph) ставим 10)
у меня получилась такая сига 48 83 EC 28 8B 02 83 F8 02 7D 12 48 8D 0D ? ? ? ? 48 83 C4 28 48 FF 25 ? ? ? ? 48 89 6C, вопросиками заменил относительные адреса(то есть адреса в инструкциях типа mov, lea, call jmp и т.д.)
потом находим по оффсету от этой сиги относительные адреса(то есть сколько нужно прибавить к текущему чтобы получить нужный адрес) g_pGameEntitySystem и NextEnt
NextEnt находится на 0x5a от начала функции
g_pGameEntitySystem находится на 0x51 от начала функции
теперь идем в нашу визуалку
окей это конец первой части скоро будет вторая(где вы поймете откуда я взял оффсеты на имена сущностей) и еще дофига других.
изначально гайд задумывался для чайников но я уже забыл каково это быть чайником поэтому если вы что-то не поняли пишите что конкретно не поняли я отвечу.
фулл код:
в гайде будут использоваться сокращения для типов unsigned long long и const char*
#define ui unsigned long long
#define cc const char*
мы даже будем говорить не столько о самом с++ а больше о взаимодействии с++ и компонентов компуктера.
если вы прям ваще не знаете что такое с++, ну не знаю соберите там какой-нибудь хелло ворлд чтоли, потому что данный спойлер предполагает базовое знание с++ на уровне чуть повыше чем хелло ворлд
гайд для х64. все что здесь написано подразумевает х64 машину.
для начала разберемся что такое указатель.
оперативка условно разделена на ячейки размером 1 байт, у каждой ячейки есть адрес и содержимое.
указатель это число размером 8 байт которому соответствует адрес какой-то ячейки. у указателя есть тип(то есть сколько байт с ячейки будет прочитано и в каком виде представить данные(без знака со знаком с запятой и тд)) который будет использоваться при дереференсе(чтении с указателя) этого указателя.
допустим у меня есть 4 ячейки с адресами 1000, 1001, 1002, 1003 где хранятся какието данные.
я говорю с++
int* мой_указатель = 1000;
std::cout<< *мой_указатель;//звездочка это один из операторов дереференса(чтения по адресу).
std::cout << std::endl;
в этом случае компьютер идет в сегмент оперативки который винда выделила для моего процесса, идет в этом сегменте по адресу 1000 и читает оттуда 4 байта(то есть читает по 1 байту с ячеек 1000 1001 1002 1003) и пишет мне эти 4 байт как 4 байтовое целое число со знаком(int) в консоль.
если же я объявлю мой_указатель как short*, то компьютер прочитает по адресу 1000 два байта(то есть ячейки 1000 и 1001) и выдаст мне это в консоль как 2 байтовое целое число(short) со знаком в консоль.
то же самое можно записать по-другому
ui мой_указатель = 1000;
std::cout << *(int*)мой_указатель;
я объявляю мой_указатель как простое число, а когда мне нужно прочитать с этого адреса я меняю тип мой_указатель с числа на указатель на какой-нибудь тип(это называется каст). другими словами, я говорю компилятору: "прочитай по адресу мой_указатель 4 байта и представь это как 4 байтовое целое число со знаком"
указатели на мои типы данных я объявляю указателями, указатели на чужие(вальвовские) типы данных я объявляю как простые числа.
если вы ничего не поняли, то вот вам вся суть в двух словах: указатель это просто 8 байт число.
регистры процессора
у процессора есть своя "оперативка" - регистры. на х64 это 16 8 байтовых ячеек для хранения данных и кучка других ячеек для служебного пользования
конвенция вызова это правила по которым осуществляется вызов функции: как передавать параметры, что с ними делать, какие регистры функции можно засирать, какие нельзя и т.д.
на х64 преимущественно используется __fastcall, где при вызове функции целочисленные аргументы слева направо пихаются в rcx,rdx,r8,r9 и потом в стек, а floatы пихаются в xmm0-xmm16 регистры.
__thiscall, конвенция функций класса, это тот же __fastcall где rcx это this(то есть __fastcall с первым параметром this).
хук
хук это когда ты перехватываешь вызов функции и выполняешь свой код.
то есть игра вызывает функцию GetSomeValue("abc"), а ты хукнул эту функцию и при вызове этой функции будет выполняться твоя функция hkGetSomeValue, где ты делаешь что угодно(например проверяешь, если первый параметр равен "abc" то вернуть 0) а потом если хочешь вызываешь оригинал.
хуки бывают разные - вмт байтпатч и еще дофига других.
вмт хук.
у классов бывает такая штука как виртуальная таблица методов, это когда первые 8 байт класса занимает указатель на массив указателей на виртуальные функции класса
эти виртуальные функции вызываются так:
берется указатель на класс, дереференсится(то есть читается 8 байт по адресу указателя на класс), прибавляется индекс функции(то есть какая по счету) умноженный на размер указателя(8 байт) и снова дереференсится, на выходе получаем адрес функции.
суть вмт хука в том чтобы заменять эти указатели на функции в вмт на свои.
обычно вмт находятся в страничке(сегмент оперативки у модуля) без прав на запись(будет исключение если вы пытаетесь записать туда где нельзя писать), поэтому перед вмт хуком нужно ставить rwx протект на регион(синоним странички).
байтпатч хук.
это когда берется уже сама функция и в ней изменяются инструкции(то есть где-то в оригинальной функции просто делается прыжок на твою функцию)
прототип функции
это шаблон игровой функции, который нужен чтобы компилятор понимал в какие регистры пихать параметры и каким типом представлять возврат функции
пример:
typedef void//не нужен возврат
(__fastcall* TYPE_NAME)//конвенция __fastcall
(ui param1, ui param2);//то есть компилятор знает что у функции всего два параметра и пихать их надо в rcx и rdx, а r8 и r9 например можно спокойно засирать.
хм ну вроде все поехали.
если вы прям ваще не знаете что такое с++, ну не знаю соберите там какой-нибудь хелло ворлд чтоли, потому что данный спойлер предполагает базовое знание с++ на уровне чуть повыше чем хелло ворлд
гайд для х64. все что здесь написано подразумевает х64 машину.
для начала разберемся что такое указатель.
оперативка условно разделена на ячейки размером 1 байт, у каждой ячейки есть адрес и содержимое.
указатель это число размером 8 байт которому соответствует адрес какой-то ячейки. у указателя есть тип(то есть сколько байт с ячейки будет прочитано и в каком виде представить данные(без знака со знаком с запятой и тд)) который будет использоваться при дереференсе(чтении с указателя) этого указателя.
допустим у меня есть 4 ячейки с адресами 1000, 1001, 1002, 1003 где хранятся какието данные.
я говорю с++
int* мой_указатель = 1000;
std::cout<< *мой_указатель;//звездочка это один из операторов дереференса(чтения по адресу).
std::cout << std::endl;
в этом случае компьютер идет в сегмент оперативки который винда выделила для моего процесса, идет в этом сегменте по адресу 1000 и читает оттуда 4 байта(то есть читает по 1 байту с ячеек 1000 1001 1002 1003) и пишет мне эти 4 байт как 4 байтовое целое число со знаком(int) в консоль.
если же я объявлю мой_указатель как short*, то компьютер прочитает по адресу 1000 два байта(то есть ячейки 1000 и 1001) и выдаст мне это в консоль как 2 байтовое целое число(short) со знаком в консоль.
то же самое можно записать по-другому
ui мой_указатель = 1000;
std::cout << *(int*)мой_указатель;
я объявляю мой_указатель как простое число, а когда мне нужно прочитать с этого адреса я меняю тип мой_указатель с числа на указатель на какой-нибудь тип(это называется каст). другими словами, я говорю компилятору: "прочитай по адресу мой_указатель 4 байта и представь это как 4 байтовое целое число со знаком"
указатели на мои типы данных я объявляю указателями, указатели на чужие(вальвовские) типы данных я объявляю как простые числа.
если вы ничего не поняли, то вот вам вся суть в двух словах: указатель это просто 8 байт число.
регистры процессора
у процессора есть своя "оперативка" - регистры. на х64 это 16 8 байтовых ячеек для хранения данных и кучка других ячеек для служебного пользования
конвенция вызова это правила по которым осуществляется вызов функции: как передавать параметры, что с ними делать, какие регистры функции можно засирать, какие нельзя и т.д.
на х64 преимущественно используется __fastcall, где при вызове функции целочисленные аргументы слева направо пихаются в rcx,rdx,r8,r9 и потом в стек, а floatы пихаются в xmm0-xmm16 регистры.
__thiscall, конвенция функций класса, это тот же __fastcall где rcx это this(то есть __fastcall с первым параметром this).
хук
хук это когда ты перехватываешь вызов функции и выполняешь свой код.
то есть игра вызывает функцию GetSomeValue("abc"), а ты хукнул эту функцию и при вызове этой функции будет выполняться твоя функция hkGetSomeValue, где ты делаешь что угодно(например проверяешь, если первый параметр равен "abc" то вернуть 0) а потом если хочешь вызываешь оригинал.
хуки бывают разные - вмт байтпатч и еще дофига других.
вмт хук.
у классов бывает такая штука как виртуальная таблица методов, это когда первые 8 байт класса занимает указатель на массив указателей на виртуальные функции класса
эти виртуальные функции вызываются так:
берется указатель на класс, дереференсится(то есть читается 8 байт по адресу указателя на класс), прибавляется индекс функции(то есть какая по счету) умноженный на размер указателя(8 байт) и снова дереференсится, на выходе получаем адрес функции.
суть вмт хука в том чтобы заменять эти указатели на функции в вмт на свои.
обычно вмт находятся в страничке(сегмент оперативки у модуля) без прав на запись(будет исключение если вы пытаетесь записать туда где нельзя писать), поэтому перед вмт хуком нужно ставить rwx протект на регион(синоним странички).
байтпатч хук.
это когда берется уже сама функция и в ней изменяются инструкции(то есть где-то в оригинальной функции просто делается прыжок на твою функцию)
прототип функции
это шаблон игровой функции, который нужен чтобы компилятор понимал в какие регистры пихать параметры и каким типом представлять возврат функции
пример:
typedef void//не нужен возврат
(__fastcall* TYPE_NAME)//конвенция __fastcall
(ui param1, ui param2);//то есть компилятор знает что у функции всего два параметра и пихать их надо в rcx и rdx, а r8 и r9 например можно спокойно засирать.
хм ну вроде все поехали.
видим что это экспорт из tier0.dll, берем базу этого модуля в оперативке и берем оттуда экспорт Msg
вполне справедливо предположить что функция работает так же как и printf, принимает %s %d и подобные токены и заменяет их параметрами.
typedef void(__fastcall* ConMsg)(cc rcx, ui rdx, ui r8, ui r9);
ConMsg CMsg = 0;
...
CMsg = (ConMsg)GetProcAddress(GetModuleHandleA("tier0.dll"), "ConMsg");
ну и оверлоады для удобства
void CMSG(cc pure) {
CMsg(pure, 0, 0,0);
}
void CMSG(cc format, ui p1) {
CMsg(format, p1, 0,0);
}
void CMSG(cc format, ui p1, ui p2) {
CMsg(format, p1, p2,0);
}
void CMSG(cc format, ui p1, ui p2, ui p3) {
CMsg(format, p1, p2,p3);
}
вызываем
CMSG("injection completed successfuly at %s!", (ui)std::to_string(std::time(0)).c_str());//#include <ctime>, #include <string> но это чисто ради примера конечно же <ctime> скорее всего вам нахрен не нужен будет.
воаля видим в консоли "injection completed successfuly at 1591207471!"
давайте перейдем к сущностям. что это такое? это любой игровой объект. крип герой дерево мир что угодно. для упрощения задачи в 1000 раз качаем бинарки мак ос с символами
Пожалуйста, авторизуйтесь для просмотра ссылки.
обычно все что связано с сущностями это модуль client.dll, поэтому открываем в IDA наш client.dylib и ищем там символы типа GetEnt FindEnt EntFind и т.д., конкретно в этом гайде давайте рассмотрим функцию ent_find(CCommandContext const&,CCommand const&).
декомпилим видим как получается первая сущность в ентити листе:
сущность = CGameEntitySystem::NextEnt(g_pGameEntitySystem, 0);
дальше снова вызывается NextEnt(g_pGameEntitySystem, сущность) и так пока сущность != 0;
казалось бы все просто но лично у меня крашило иногда от такой итерации(именно когда получал последующую сущность через NextEnt)(мб кривой код просто хз, но убрав эту говнофункцию краши исчезли магическое совпадение или логическое заключение не знаю). ручная итерация быстрее(на наносекунды, ха-ха) и надежнее.
не будем париться расскажу вам все секреты: у CEntityInstance(родитель C_BaseEntity) есть член CEntityIdentity* m_pEntity, а у CEntityIdentity есть члены C_BaseEntity* m_pEnt и CEntityIdentity* m_pNext. то есть имея первую сущность мы берем ее CEntityIdentity идем к m_pNext и берем оттуда m_pEnt и вот наша следующая сущность.(данная теория выведена путем тыка туда сюда по ячейкам в дебагере и реклассе)
первый скрин. C_BaseEntity ent1 в памяти. на оффсете 0x10 лежит указатель на CEntityIdentity, условно _ent1
второй скрин. CEntityIdentity _ent1 в памяти. на 0х0 лежит указатель на C_BaseEntity ent1, а на 0х58 лежит указатель на CEntityIdentity у ent2
третий скрин. та самая CEntityIdentity у сущности ent2. на 0х0 лежит указатель на саму сущность, на 0х58 указатель на CEntityIdentity третьей сущности, на 0х50 указатель на CEntityIdentity у сущности ent1(он нам не нужен)
псевдокод итерации:
C_BaseEntity* ent = CGameEntitySystem::NextEnt(entsystem,0);
while (ent) {
...
std::cout << ent->health << std::endl;
...
if (!ent->m_pEntity->m_pNext) break;
ent = ent->m_pEntity->m_pNext->m_pEnt;
}
оффсеты : m_pEntity - +0x10
m_pNext - +0x58
m_pEnt - +0
находим нашу ent_find по сиге(ибо это не виртуалка, то есть вызывается по относительному адресу а не по дереференсу [вмт+индекс*8]):
(сигу делаем разумеется уже в client.dll, найдете эту функцию по хрефу "format: ent_find")
что такое сига? это последовательность байт инструкций функции.
(заходим в IDA options->general->number of opcode bytes(non-graph) ставим 10)
у меня получилась такая сига 48 83 EC 28 8B 02 83 F8 02 7D 12 48 8D 0D ? ? ? ? 48 83 C4 28 48 FF 25 ? ? ? ? 48 89 6C, вопросиками заменил относительные адреса(то есть адреса в инструкциях типа mov, lea, call jmp и т.д.)
потом находим по оффсету от этой сиги относительные адреса(то есть сколько нужно прибавить к текущему чтобы получить нужный адрес) g_pGameEntitySystem и NextEnt
NextEnt находится на 0x5a от начала функции
g_pGameEntitySystem находится на 0x51 от начала функции
теперь идем в нашу визуалку
C++:
...
#include <Psapi.h>
#define InRange(x, a, b) (x >= a && x <= b)
#define getBit(x) (InRange((x & (~0x20)), 'A', 'F') ? ((x & (~0x20)) - 'A' + 0xA): (InRange(x, '0', '9') ? x - '0': 0))
#define getByte(x) (getBit(x[0]) << 4 | getBit(x[1]))
ui FPat(const ui& start_address, const ui& end_address, const char* target_pattern) {
const char* pattern = target_pattern;
ui first_match = 0;
for (ui position = start_address; position < end_address; position++) {
if (!*pattern)
return first_match;
const unsigned char pattern_current = *reinterpret_cast<const unsigned char*>(pattern);
const unsigned char memory_current = *reinterpret_cast<const unsigned char*>(position);
if (pattern_current == '\?' || memory_current == getByte(pattern)) {
if (!first_match)
first_match = position;
if (!pattern[2])
return first_match;
pattern += pattern_current != '\?' ? 3 : 2;
}
else {
pattern = target_pattern;
first_match = 0;
}
}
return NULL;
}
ui FPat(const char* module, const char* target_pattern) {
MODULEINFO module_info = { 0 };
if (!GetModuleInformation(GetCurrentProcess(), GetModuleHandleA(module), &module_info, sizeof(MODULEINFO)))
return NULL;
const ui start_address = ui(module_info.lpBaseOfDll);
const ui end_address = start_address + module_info.SizeOfImage;
return FPat(start_address, end_address, target_pattern);
}
inline ui GetAbsoluteAddress(ui instruction_ptr, int offset, int size)
{
return instruction_ptr + *reinterpret_cast<int32_t*>(instruction_ptr + offset) + size;
}
...
ent_find_pat = FPat("client.dll", "48 83 EC 28 8B 02 83 F8 02 7D 12 48 8D 0D ? ? ? ? 48 83 C4 28 48 FF 25 ? ? ? ? 48 89 6C");
if (ent_find_pat == 0) {
CMSG("ent_find_pat is NULL :(");
return;
}
NextEnt = (fNextEnt)GetAbsoluteAddress(ent_find_pat + 0x5a, 1, 5);//делаем из относительного адреса абсолютный, 1 это размер самой инструкции call(e8) а 5 это размер вместе с адресом(e8 12 34 56 78, где 12 34 56 78 это относительный адрес))
CGameEntSystem = *(ui*)(GetAbsoluteAddress(ent_find_pat + 0x51, 3, 7));//3 размер самой инструкции mov rcx, qword:[](48 8b 0d) 7 это размер вместе с 4 байт относительным адресом(48 8b 0d 12 34 56 78)
ui ent = NextEnt(CGameEntSystem, 0);//получаем первую сущность
while (ent) {
if (*(ui*)(ent + 0x10))//если у сущности есть CEntityIdentity(ну сущностей без CEntityIdentity не бывает но все же)
{
if(*(ui*)(*(ui*)(ent + 0x10) + 0x18))//если у сущности есть обычное имя
CMSG("ent name %s\n",*(ui*)(*(ui*)(ent+0x10)+0x18));
else if(*(ui*)(*(ui*)(ent + 0x10) + 0x20))//если у сущности нет обычного но есть внутреннее имя
CMSG("ent name %s\n", *(ui*)(*(ui*)(ent + 0x10) + 0x20));
}
*(ui*)(*(ui*)(ent + 0x10) + 0x58) == 0 ? ent = 0 : ent = *(ui*)(*(ui*)(*(ui*)(ent + 0x10) + 0x58));//если m_pNext равен 0 то ent = 0 чтобы прервать цикл, в противном случае ent = m_pNext->m_pEnt
}
изначально гайд задумывался для чайников но я уже забыл каково это быть чайником поэтому если вы что-то не поняли пишите что конкретно не поняли я отвечу.
фулл код:
C++:
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#define ui unsigned long long
#define cc const char*
#pragma once
#include <ctime>
#include <string>
#include <Psapi.h>
#define InRange(x, a, b) (x >= a && x <= b)
#define getBit(x) (InRange((x & (~0x20)), 'A', 'F') ? ((x & (~0x20)) - 'A' + 0xA): (InRange(x, '0', '9') ? x - '0': 0))
#define getByte(x) (getBit(x[0]) << 4 | getBit(x[1]))
typedef void(__fastcall* ConMsg)(cc,ui,ui,ui);
ConMsg CMsg = 0;
void CMSG(cc pure) {
CMsg(pure, 0, 0, 0);
}
void CMSG(cc format, ui p1) {
CMsg(format, p1, 0, 0);
}
void CMSG(cc format, ui p1, ui p2) {
CMsg(format, p1, p2, 0);
}
void CMSG(cc format, ui p1, ui p2, ui p3) {
CMsg(format, p1, p2, p3);
}
ui FPat(const ui& start_address, const ui& end_address, const char* target_pattern) {
const char* pattern = target_pattern;
ui first_match = 0;
for (ui position = start_address; position < end_address; position++) {
if (!*pattern)
return first_match;
const unsigned char pattern_current = *reinterpret_cast<const unsigned char*>(pattern);
const unsigned char memory_current = *reinterpret_cast<const unsigned char*>(position);
if (pattern_current == '\?' || memory_current == getByte(pattern)) {
if (!first_match)
first_match = position;
if (!pattern[2])
return first_match;
pattern += pattern_current != '\?' ? 3 : 2;
}
else {
pattern = target_pattern;
first_match = 0;
}
}
return NULL;
}
ui FPat(const char* module, const char* target_pattern) {
MODULEINFO module_info = { 0 };
if (!GetModuleInformation(GetCurrentProcess(), GetModuleHandleA(module), &module_info, sizeof(MODULEINFO)))
return NULL;
const ui start_address = ui(module_info.lpBaseOfDll);
const ui end_address = start_address + module_info.SizeOfImage;
return FPat(start_address, end_address, target_pattern);
}
inline ui GetAbsoluteAddress(ui instruction_ptr, int offset, int size)
{
return instruction_ptr + *reinterpret_cast<int32_t*>(instruction_ptr + offset) + size;
}
typedef ui(__fastcall* fNextEnt)(ui, ui);
fNextEnt NextEnt;
ui CGameEntSystem;
ui ent_find_pat;
void OnInject() {
CMsg = (ConMsg)GetProcAddress(GetModuleHandleA("tier0.dll"), "Msg");
ent_find_pat = FPat("client.dll", "48 83 EC 28 8B 02 83 F8 02 7D 12 48 8D 0D ? ? ? ? 48 83 C4 28 48 FF 25 ? ? ? ? 48 89 6C");
if (ent_find_pat == 0) {
CMSG("ent_find_pat is NULL :(");
return;
}
NextEnt = (fNextEnt)GetAbsoluteAddress(ent_find_pat + 0x5a, 1, 5);
CGameEntSystem = *(ui*)(GetAbsoluteAddress(ent_find_pat + 0x51, 3, 7));
ui ent = NextEnt(CGameEntSystem, 0);
while (ent) {
if (*(ui*)(ent + 0x10))
{
if (*(ui*)(*(ui*)(ent + 0x10) + 0x18))
CMSG("ent name %s\n", *(ui*)(*(ui*)(ent + 0x10) + 0x18));
else if (*(ui*)(*(ui*)(ent + 0x10) + 0x20))
CMSG("ent name %s\n", *(ui*)(*(ui*)(ent + 0x10) + 0x20));
}
*(ui*)(*(ui*)(ent + 0x10) + 0x58) == 0 ? ent = 0 : ent = *(ui*)(*(ui*)(*(ui*)(ent + 0x10) + 0x58));
}
CMSG("injection completed successfuly at %s!\n", (ui)std::to_string(std::time(0)).c_str());
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
if(ul_reason_for_call == 1) CreateThread(0,0,(LPTHREAD_START_ROUTINE)OnInject,0,0,0);
return TRUE;
}
Последнее редактирование: