Гайд Сущности в доте для чайников

Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
прелюдия, скипайте если вы не совсем чайник и знаете что такое указатель хуки тайпдефы и прочий шлак.
в гайде будут использоваться сокращения для типов 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 например можно спокойно засирать.

хм ну вроде все поехали.
для начала вывод в консоль. берем любой модуль открываем в 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 от начала функции

теперь идем в нашу визуалку
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;
}
 
Последнее редактирование:
CCCV
Пользователь
Статус
Оффлайн
Регистрация
26 Сен 2018
Сообщения
388
Реакции[?]
82
Поинты[?]
2K
Товары в продаже
1
Когда меня спрашивают: "что такое хуки", я примерно так же отвечаю
хук
хук это когда ты перехватываешь вызов функции и выполняешь свой код.
то есть игра вызывает функцию GetSomeValue("abc"), а ты хукнул эту функцию и при вызове этой функции будет выполняться твоя функция hkGetSomeValue, где ты делаешь что угодно(например проверяешь, если первый параметр равен "abc" то вернуть 0) а потом если хочешь вызываешь оригинал.
но слишком долго объяснять, поэтому теперь говорю всем, что хук это хук
(ui param1, ui param2);//то есть компилятор знает что у функции всего два параметра и пихать их надо в rcx и rdx, а r8 и r9 например можно спокойно засирать.
к сожалению, их "засирать" можно не всегда. Они сейвятся при вызове функции, но если функа предполагает вызов с 2я аргументами, а ты вызовешь её с 3я или более, то изменишь данные регистров, которые используются во функе выше и будет краш (не всегда!). Но бывают и исключения, когда от значения регистра ничего не зависит и после вызова оно перезаписывается. Компилятор иногда бывает не очень умным и присваивает значения тем регистрам, которые в дальнейшем будут перезаписаны по нескольку раз, т.е. некоторые инструкции не имеют смысла. К сожалению, найти тот проблемный софт не представляется возможным

если кое-что не учитывать, то гайд для новичков получился годный, но немного сложноватый, ибо без знаний плюсов и устройстве машины ничего не поймут, многое опущено, но опять же повторюсь, что почитать можно)
 
Последнее редактирование:
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
Когда меня спрашивают: "что такое хуки", я примерно так же отвечаю

но слишком долго объяснять, поэтому теперь говорю всем, что хук это хук

к сожалению, их "засирать" можно не всегда. Они сейвятся при вызове функции, но если функа предполагает вызов с 2я аргументами, а ты вызовешь её с 3я или более, то изменишь данные регистров, которые используются во функе выше и будет краш (не всегда!). Но бывают и исключения, когда от значения регистра ничего не зависит и после вызова оно перезаписывается. Компилятор иногда бывает не очень умным и присваивает значения тем регистрам, которые в дальнейшем будут перезаписаны по нескольку раз, т.е. некоторые инструкции не имеют смысла. К сожалению, найти тот проблемный софт не представляется возможным

если кое-что не учитывать, то гайд для новичков получился годный, но немного сложноватый, ибо без знаний плюсов и устройстве машины ничего не поймут, многое опущено, но опять же повторюсь, что почитать можно)
если у функции 2 аргумента то она сама засирает r8 и r9 довольно часто первыми инструкциями, если вызывать функцию с 2 аргументами передавая 4 аргумента ничего плохого не произойдет - так как у функции всего 2 аргумента то 3 и 4 она даже читать не станет,а чаще всего просто перепишет и не будет восстанавливать. и твоя функция перед вызовом понимает что r8 и r9 прежними она уже не увидит поэтому не сует туда ничего важного) . компилятор собирает так чтобы не хранить важные данные в волатильных регистрах а неволатильные восстанавливаются вызываемой функцией. в нормальных условиях можно любую функцию вызывать как угодно глввное чтобы она получила параметры которые ей реально нужны. никогда у меня еще не крашил вызов где параметров больше чем надо.(конкретно в доте на х64)
 
CCCV
Пользователь
Статус
Оффлайн
Регистрация
26 Сен 2018
Сообщения
388
Реакции[?]
82
Поинты[?]
2K
Товары в продаже
1
если у функции 2 аргумента то она сама засирает r8 и r9 довольно часто первыми инструкциями, если вызывать функцию с 2 аргументами передавая 4 аргумента ничего плохого не произойдет - так как у функции всего 2 аргумента то 3 и 4 она даже читать не станет,а чаще всего просто перепишет и не будет восстанавливать. и твоя функция перед вызовом понимает что r8 и r9 прежними она уже не увидит поэтому не сует туда ничего важного) . компилятор собирает так чтобы не хранить важные данные в волатильных регистрах а неволатильные восстанавливаются вызываемой функцией. в нормальных условиях можно любую функцию вызывать как угодно глввное чтобы она получила параметры которые ей реально нужны. никогда у меня еще не крашил вызов где параметров больше чем надо.(конкретно в доте на х64)
помню у меня трабл был в расте, когда хукал некоторые функи, приходилось дополнительно сохранять регистры перед вызовом
 
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
помню у меня трабл был в расте, когда хукал некоторые функи, приходилось дополнительно сохранять регистры перед вызовом
вот хук да, это другой базар особенно байтпатч. там компилятор не знает какие регистры можно использовать а какие нет и приходится руками через masm подтирать за ним) меня еще прикалывает когда ты сохраняешь определенные регистры потом изменил 1 строку кода и компилятор уже совсем другие регистры использует)
 
CCCV
Пользователь
Статус
Оффлайн
Регистрация
26 Сен 2018
Сообщения
388
Реакции[?]
82
Поинты[?]
2K
Товары в продаже
1
вот хук да, это другой базар особенно байтпатч. там компилятор не знает какие регистры можно использовать а какие нет и приходится руками через masm подтирать за ним) меня еще прикалывает когда ты сохраняешь определенные регистры потом изменил 1 строку кода и компилятор уже совсем другие регистры использует)
да, в x64 (именно в расте, не знаю как в других), без патча не обойтись, т.к. адреса 8и-байтные, иногда приходится очень далеко прыгать, а прыжки максимум 4х-байтные, приходится сейвить регистр, записывать в него адрес, куда нужно прыгнуть, потом прыгать на регистр, и при возврате из функции, восстанавливать его
 
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
да, в x64 (именно в расте, не знаю как в других), без патча не обойтись, т.к. адреса 8и-байтные, иногда приходится очень далеко прыгать, а прыжки максимум 4х-байтные, приходится сейвить регистр, записывать в него адрес, куда нужно прыгнуть, потом прыгать на регистр, и при возврате из функции, восстанавливать его
это-то да, а еще бывает что сама твоя хук функция запишет в какой-нибудь r10 и не восстановит, а потом оригинал использует этот r10 и офигевает, а сам прыжок типа mov rax jmp rax я ставлю перед тем как функция-оригинал запишет в rax( то есть хукаю в середине функции а не сразу в начале), то есть в тот момент когда rax ей не нужен, и таким способом rax можно не восстанавливать(ну разумеется не всегда такая возможность есть)
 
CCCV
Пользователь
Статус
Оффлайн
Регистрация
26 Сен 2018
Сообщения
388
Реакции[?]
82
Поинты[?]
2K
Товары в продаже
1
это-то да, а еще бывает что сама твоя хук функция запишет в какой-нибудь r10 и не восстановит, а потом оригинал использует этот r10 и офигевает, а сам прыжок типа mov rax jmp rax я ставлю перед тем как функция-оригинал запишет в rax( то есть хукаю в середине функции а не сразу в начале), то есть в тот момент когда rax ей не нужен, и таким способом rax можно не восстанавливать(ну разумеется не всегда такая возможность есть)
я тут когда изучал хуки, раньше пихал хук внутри функции, естественно, он работал очень криво, ибо там появляются траблы со стеком, когда функа вызвалась, потом вызывается другая из неё (моя, кастомная), ну вообщем первый блин комом был)
потом решил хукать то место, где функа эта вызывается, получилось немного лучше, регистр сейвил прям в коде (executable регионе), лютая хрень была, не удобная, тратил на хук одной функи от часа до дня, минимальный размер хука - 0x23 (35) байт
вот ss этой убогой конструкции:
1591268714440.png
mov [reg_old], r15 // сейвим регистр
mov r15, func // пишем адрес функи в r15
jmp r15 // прыгаем на кастомную функу
------
R
E
G
_
O
L
D
------
mov r15, [reg_old] // восстанавливаем регистр
 
Последнее редактирование:
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
я тут когда изучал хуки, раньше пихал хук внутри функции, естественно, он работал очень криво, ибо там появляются траблы со стеком, когда функа вызвалась, потом вызывается другая из неё (моя, кастомная), ну вообщем первый блин комом был)
потом решил хукать то место, где функа эта вызывается, получилось немного лучше, регистр сейвил прям в коде (executable регионе), лютая хрень была, не удобная, тратил на хук одной функи от часа до дня, размер хука - 0x23 (35) байт
вот ss этой убогой конструкции:
Посмотреть вложение 78742
mov [reg_old], r15 // сейвим регистр
mov r15, func // пишем адрес функи в r15
jmp r15 // прыгаем на кастомную функу
------
R
E
G
_
O
L
D
------
mov r15, [reg_old] // восстанавливаем регистр
фига конструкция огромная хахаха. я например mov rax, [abcdefg]; jmp rax; на abcdefg call hkMyFunc потом оригинальные 12 байт которые я заменил(среди которых как раз и есть mov rax, какаято хрень; что позволяет мне не сейвить rax) и потом прыжок на оригинал.
 
CCCV
Пользователь
Статус
Оффлайн
Регистрация
26 Сен 2018
Сообщения
388
Реакции[?]
82
Поинты[?]
2K
Товары в продаже
1
фига конструкция огромная хахаха. я например mov rax, [abcdefg]; jmp rax; на abcdefg call hkMyFunc потом оригинальные 12 байт которые я заменил(среди которых как раз и есть mov rax, какаято хрень; что позволяет мне не сейвить rax) и потом прыжок на оригинал.
это были мои первые шаги, я абсолютно тогда не разбирался)
сейчас я делаю иначе, у меня есть call oFunc, я пишу вместо oFunc указатель на предварительно выделенный регион памяти + 12 * ind, который выглядит примерно так:
mov rax, fnCustom1 // size 9 // ind 0
jmp rax // size 3
mov rax, fnCustom2 // ind 1
jmp rax
mov rax, fnCustom2 // ind 2
jmp rax
работает так, как нужно, без крашей)
имхо, хуки - невероятно интересная вещь)
 
Начинающий
Статус
Оффлайн
Регистрация
27 Май 2020
Сообщения
15
Реакции[?]
2
Поинты[?]
0
А не пробовал как в Mcdota через Reinit predictables найти энтити лист ?
Пожалуйста, авторизуйтесь для просмотра ссылки.

найти по Reinit Predictables не получится, такая строка только на линуксе есть.

Вот по этой можно найти.
CL: reinitialized %i predictable entities\n


GetHighestEntityIndex там просто инлайнится как я понял, в отличии от линукс версии. Вот эти две строки вместо нее.
НУ или v4 в дизасемблере

Самый первый вызов функции это GetBaseEntity(индекс).
перед вызовом this (EntitySystem) кладется в rcx

text:0000000180810AA0 sub_180810AA0 proc near

.text:0000000180810AA0 push rsi
.text:0000000180810AA2 sub rsp, 30h
.text:0000000180810AA6 mov rax, cs:qword_182EAE0A8 ; Вот тут
.text:0000000180810AAD mov [rsp+38h+arg_0], rbx
.text:0000000180810AB2 mov [rsp+38h+arg_8], rbp
.text:0000000180810AB7 xor ebp, ebp
.text:0000000180810AB9 mov [rsp+38h+arg_10], rdi
.text:0000000180810ABE mov ebx, ebp
.text:0000000180810AC0 mov byte ptr [rcx+100h], 1
.text:0000000180810AC7 mov edi, [rax+2060h] ; И вот тут
.text:0000000180810ACD mov [rsp+38h+var_10], r14
.text:0000000180810AD2 mov r14, rcx
.text:0000000180810AD5 test edi, edi
.text:0000000180810AD7 js loc_180810B94
.text:0000000180810ADD
.text:0000000180810ADD loc_180810ADD:
.text:0000000180810ADD
.text:0000000180810ADD mov [rsp+38h+var_18], r15
.text:0000000180810AE2 lea r15, qword_182E98388
.text:0000000180810AE9 nop dword ptr [rax+00000000h]
.text:0000000180810AF0
.text:0000000180810AF0 loc_180810AF0:
.text:0000000180810AF0 cmp ebx, 0FFFFFFFFh
.text:0000000180810AF3 jl loc_180810B85
.text:0000000180810AF9 mov rcx, cs:qword_182EAE0A8 ; entitySystem указатель.
.text:0000000180810B00 mov edx, ebx
.text:0000000180810B02 add rcx, 10h
.text:0000000180810B06 call sub_180164B30 ; Вот эта функция - GetBaseEntity
.text:0000000180810B0B test rax, rax

Не проверял, но похоже, что все так и есть.

Пример итераций энтити
Пожалуйста, авторизуйтесь для просмотра ссылки.
 
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
А не пробовал как в Mcdota через Reinit predictables найти энтити лист ?
Пожалуйста, авторизуйтесь для просмотра ссылки.

найти по Reinit Predictables не получится, такая строка только на линуксе есть.

Вот по этой можно найти.
CL: reinitialized %i predictable entities\n


GetHighestEntityIndex там просто инлайнится как я понял, в отличии от линукс версии. Вот эти две строки вместо нее.
НУ или v4 в дизасемблере

Самый первый вызов функции это GetBaseEntity(индекс).
перед вызовом this (EntitySystem) кладется в rcx

text:0000000180810AA0 sub_180810AA0 proc near

.text:0000000180810AA0 push rsi
.text:0000000180810AA2 sub rsp, 30h
.text:0000000180810AA6 mov rax, cs:qword_182EAE0A8 ; Вот тут
.text:0000000180810AAD mov [rsp+38h+arg_0], rbx
.text:0000000180810AB2 mov [rsp+38h+arg_8], rbp
.text:0000000180810AB7 xor ebp, ebp
.text:0000000180810AB9 mov [rsp+38h+arg_10], rdi
.text:0000000180810ABE mov ebx, ebp
.text:0000000180810AC0 mov byte ptr [rcx+100h], 1
.text:0000000180810AC7 mov edi, [rax+2060h] ; И вот тут
.text:0000000180810ACD mov [rsp+38h+var_10], r14
.text:0000000180810AD2 mov r14, rcx
.text:0000000180810AD5 test edi, edi
.text:0000000180810AD7 js loc_180810B94
.text:0000000180810ADD
.text:0000000180810ADD loc_180810ADD:
.text:0000000180810ADD
.text:0000000180810ADD mov [rsp+38h+var_18], r15
.text:0000000180810AE2 lea r15, qword_182E98388
.text:0000000180810AE9 nop dword ptr [rax+00000000h]
.text:0000000180810AF0
.text:0000000180810AF0 loc_180810AF0:
.text:0000000180810AF0 cmp ebx, 0FFFFFFFFh
.text:0000000180810AF3 jl loc_180810B85
.text:0000000180810AF9 mov rcx, cs:qword_182EAE0A8 ; entitySystem указатель.
.text:0000000180810B00 mov edx, ebx
.text:0000000180810B02 add rcx, 10h
.text:0000000180810B06 call sub_180164B30 ; Вот эта функция - GetBaseEntity
.text:0000000180810B0B test rax, rax

Не проверял, но похоже, что все так и есть.

Пример итераций энтити
Пожалуйста, авторизуйтесь для просмотра ссылки.
там миллион хрефов на entitySystem и GetBaseEntity, я через ent_find нахожу, через твой вариант тоже можно, можно как угодно. и итерировать можно как в LWSS, но я предпочитаю через m_pNext. как минимум потому что там не нужен HighestIndex
 
Начинающий
Статус
Оффлайн
Регистрация
15 Дек 2018
Сообщения
146
Реакции[?]
9
Поинты[?]
0
А что делает первая функция? (Которая вызывается при ритёрне во второй функции) Как я понял прогоняет циклом память в поисках сигнатуры и как только она найдена, возвращает адрес где она находится?
 
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
А что делает первая функция? (Которая вызывается при ритёрне во второй функции) Как я понял прогоняет циклом память в поисках сигнатуры и как только она найдена, возвращает адрес где она находится?
ты про FPat?
если честно я скопипастил это говно с интернета и не парился как оно работает, но цель данной функции действительно та которую ты описал - ищет в регионе памяти от start_address до end_address последовательность байт которая соответстует байтам из строки сигнатуры и возвращает первое совпадение. ну хотя ща посмотрел на код и вроде работает так:
если нашел в памяти байт который совпал с байтом из сиги, чекаешь следующий байт в памяти и следующий байт из сиги, если совпадает чекаешь следующий и т.д. а если где-то не совпадает то итератор сиги в 0 обратно ставишь(ну то есть начинаешь снова с первым байтом сиги сравнивать) и потом если полностью совпало возвращает этот адрес/либо возвращает 0 если гдето чето не совпало и потом ничего интересного не нашлось.
 
Последнее редактирование:
Начинающий
Статус
Оффлайн
Регистрация
15 Дек 2018
Сообщения
146
Реакции[?]
9
Поинты[?]
0
ты про FPat?
если честно я скопипастил это говно с интернета и не парился как оно работает, но цель данной функции действительно та которую ты описал - ищет в регионе памяти от start_address до end_address последовательность байт которая соответстует байтам из строки сигнатуры и возвращает первое совпадение. ну хотя ща посмотрел на код и вроде работает так:
если нашел в памяти байт который совпал с байтом из сиги, чекаешь следующий байт в памяти и следующий байт из сиги, если совпадает чекаешь следующий и т.д. а если где-то не совпадает то итератор сиги в 0 обратно ставишь(ну то есть начинаешь снова с первым байтом сиги сравнивать) и потом если полностью совпало возвращает этот адрес/либо возвращает 0 если гдето чето не совпало и потом ничего интересного не нашлось.
Да, про него! Спасибо
 
Новичок
Статус
Оффлайн
Регистрация
6 Авг 2018
Сообщения
2
Реакции[?]
0
Поинты[?]
0
Чем отличается версия с символами от обычных .dll?

(libclient.dylib отсутствует по
Пожалуйста, авторизуйтесь для просмотра ссылки.
из ориг поста, нашёл рядом
Пожалуйста, авторизуйтесь для просмотра ссылки.
)
 
Последнее редактирование:
Начинающий
Статус
Оффлайн
Регистрация
15 Дек 2018
Сообщения
146
Реакции[?]
9
Поинты[?]
0
Чем отличается версия с символами от обычных .dll?

(libclient.dylib отсутствует по
Пожалуйста, авторизуйтесь для просмотра ссылки.
из ориг поста, нашёл рядом
Пожалуйста, авторизуйтесь для просмотра ссылки.
)
То что в них есть все символы отладки, то есть можно находить любые функции по их названию, что нельзя сделать в оригинале
 
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
(libclient.dylib отсутствует по
Пожалуйста, авторизуйтесь для просмотра ссылки.
из ориг поста, нашёл рядом
Пожалуйста, авторизуйтесь для просмотра ссылки.
)
да, libclient действительно не в той директории, но подразумевалось что читатель сам догадается потому что точно так же и на виндовсе устроена дота.
dota 2 beta\game\bin\win64\engine2.dll
но клиент.длл в другой директории:
dota 2 beta\game\dota\bin\win64\client.dll

версия с символами отличается тем что в ней есть символы - то есть названия всех глобальных переменных/функций/прочих шняг. символы это впринципе таблица где есть соответствие "по адресу xxx находится переменная с именем blablabla типа type", IDA эту таблицу обрабатывает и вместо голого адреса dword_xxx пишет тебе type blablabla. эти dylibы с символами довольно старые, вроде от 2015(в современных dylibах вырезаны символы, вальвы догадались), так что код в dll слегка отличается но тем не менее все равно эти символы довольно полезны. наименования у вальвов довольно очевидные поэтому тебе не надо гадать что делает sub_xxx и что такое dword_xxx, сразу видишь по названию что делает функция/что хранится в переменной
 
Новичок
Статус
Оффлайн
Регистрация
6 Авг 2018
Сообщения
2
Реакции[?]
0
Поинты[?]
0
Привет, появилось пару нубских вопросов, предупреждаю, только вкатываюсь в плюсы, поэтому некоторых элементарных вещей могу не понимать

1) Первый вопрос про иерархию классов.
у CEntityInstance(родитель C_BaseEntity) есть член CEntityIdentity* m_pEntity, а у CEntityIdentity есть члены C_BaseEntity* m_pEnt и CEntityIdentity* m_pNext. то есть имея первую сущность мы берем ее CEntityIdentity идем к m_pNext и берем оттуда m_pEnt и вот наша следующая сущность
Исходя из цитаты выше и скринов из ReClass'а сделал небольшую диаграмму.

Правильно ли я понимаю, что CEntityIdentity* является мембером и CEntityInstance и C_BaseEntity? Наследует ли CEntityIdentity как-то CEntityInstance или C_BaseEntity?

2) Не совсем понимаю, как найти в ReClass'e нужный класс в памяти. Ты заранее знаешь его точный адресс и находишь просто через поиск?

3) Из инструкций в IDA, найденным по "format: ent_find", ты берёшь байты до , хотя сама инструкция на этой строке заканчивается на 40. Мог быт ты объяснить, пожалуйста, почему ты берёшь именно такой набор байтов?

+ ещё пару вопросов, которые связаны скорее с работой в IDE (использую Visual Studio):

Изначально я попытался заранить код, который относился к вызову Msg из tier0.dll в обычном консольном проекте, поэтому вместо GetModuleHandleA я использовал LoadLibrary из windows.h. В принципе, никаких проблем это у меня не вызвало, .dll заимпортился удачно, и нужный оверлоад успешно выполнился. Дальше я дошёл до кода в спойлере ориг поста, в котором уже импортиться client.dll, который я попытался точно так же импортнуть через LoadLibrary, но почему-то этот импорт валится с ошибкой 126 ("Не найден указанный модуль"). Я предоложил, что LoadLibrary как-то значительно отличается от GetModuleHandleA, решил этот же код заранитьв DLL проекте, у меня не вышло заранить dllmain.cpp, (был бы признателен, если бы подсказали, возможно ли это в принципе), поэтому я просто его сбилдил и через LoadLibrary заимпортил в обычный консольный проект. Тоже ничего не вышло, приложение опять падало на LoadLibrary без каких либо логов. Немного погуглив, я понял, что проблема может быть в том, что client.dll не может найти свои депенденси. IDA показывала, что client.dll импортит dll-файлы из game\bin\win64, почему-то я попробовал закинуть их в рут папку проекта, и внезапно они начали вгружаться в приложение, судя по логам, а само приложение уже начало выдавать ошибки, связанный с прокинутыми файлами. Из этого всего я сделал вывод, что LoadLibrary не может зарезолвить пути зависимостей для client.dll , т.к. почему-то файлы ищутся в корне. На этом этапе я явно чувствую, что изобретаю ненужный велосипед, поэтому решил обратиться за помощью.
 
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
Привет, появилось пару нубских вопросов, предупреждаю, только вкатываюсь в плюсы, поэтому некоторых элементарных вещей могу не понимать

1) Первый вопрос про иерархию классов.


Исходя из цитаты выше и скринов из ReClass'а сделал небольшую диаграмму.

Правильно ли я понимаю, что CEntityIdentity* является мембером и CEntityInstance и C_BaseEntity? Наследует ли CEntityIdentity как-то CEntityInstance или C_BaseEntity?

2) Не совсем понимаю, как найти в ReClass'e нужный класс в памяти. Ты заранее знаешь его точный адресс и находишь просто через поиск?

3) Из инструкций в IDA, найденным по "format: ent_find", ты берёшь байты до , хотя сама инструкция на этой строке заканчивается на 40. Мог быт ты объяснить, пожалуйста, почему ты берёшь именно такой набор байтов?

+ ещё пару вопросов, которые связаны скорее с работой в IDE (использую Visual Studio):

Изначально я попытался заранить код, который относился к вызову Msg из tier0.dll в обычном консольном проекте, поэтому вместо GetModuleHandleA я использовал LoadLibrary из windows.h. В принципе, никаких проблем это у меня не вызвало, .dll заимпортился удачно, и нужный оверлоад успешно выполнился. Дальше я дошёл до кода в спойлере ориг поста, в котором уже импортиться client.dll, который я попытался точно так же импортнуть через LoadLibrary, но почему-то этот импорт валится с ошибкой 126 ("Не найден указанный модуль"). Я предоложил, что LoadLibrary как-то значительно отличается от GetModuleHandleA, решил этот же код заранитьв DLL проекте, у меня не вышло заранить dllmain.cpp, (был бы признателен, если бы подсказали, возможно ли это в принципе), поэтому я просто его сбилдил и через LoadLibrary заимпортил в обычный консольный проект. Тоже ничего не вышло, приложение опять падало на LoadLibrary без каких либо логов. Немного погуглив, я понял, что проблема может быть в том, что client.dll не может найти свои депенденси. IDA показывала, что client.dll импортит dll-файлы из game\bin\win64, почему-то я попробовал закинуть их в рут папку проекта, и внезапно они начали вгружаться в приложение, судя по логам, а само приложение уже начало выдавать ошибки, связанный с прокинутыми файлами. Из этого всего я сделал вывод, что LoadLibrary не может зарезолвить пути зависимостей для client.dll , т.к. почему-то файлы ищутся в корне. На этом этапе я явно чувствую, что изобретаю ненужный велосипед, поэтому решил обратиться за помощью.
1)это два паралелльных класса которые сводит вышестоящий управитель(CEntitySystem)
условно псевдокод CEntitySystem::Add(const char* name){
делаю новую ентити и делаю новую ентитиидентити.
identity = new CEntityIdentity(name);
entity = new CBaseEntity(name);
entity->identity = identity;//в сущности есть указатель на ее идентити
identity->prev = GetLastEntityIdentity();//получаю предыдущую ентитиидентити(ну то есть последняя созданная до этого момента)
if(GetLastEntityIdentity())//ну то есть предыдущая не равна нулю
GetLastEntityIdentity()->next = identity;//предыдущей ентитиидентити ставлю m_pNext в эту идентити
identity->entity = entity;//в идентити есть указатель на сущность.
//в итоге ты итерируешь: CBaseEntity* CEntitySystem::GetFirstEntity()->смотришь нужна ли тебе эта сущность->CEntityIdentity* identity->CEntityIdentity* next->CBaseEntity* ent->смотришь нужна ли сущность->identity->next->ent->...->if(next == 0) break
}
в общем
class Entity{
CEntityIdentity* identity;
}
class CEntityIdentity{
Entity* entity;
CEntityIdentity* next, prev;
}
у каждой сущности есть физический объект(герой, дерево, крип) и описание(структура с именем сущности и еще какими-то данными специфичными для CEntitySystem, то есть CEntityIdentity). физический объект используется всеми кто захочет напрямую, а описание используется только в рамках CEntitySystem.
про наследование хз, CEntityIdentity вроде чето там гдето там наследует у CEntityInstance, посмотри в шеме если надо, но смысла абсолютно нет потому что это тебе ничего не даст)

2)я проитерировал сущностей(ну точнее просто в той функции ent_find поставил брейкпоинт), взял пару указателей, зашел на них в реклассе и там все посмотрел(самое полезное в реклассе это то что там есть RTTI, то есть имена классов, не знаю почему этой фигни нет в том же x64dbg)
3)я беру столько байт сколько нужно чтобы при поиске сиги мне находило только одну сигу а не две не три. ну то есть я могу сигу хоть в два байта сделать но мне таких сиг найдет дофига. поэтому я беру там три-четыре инструкции+ сигу. инструкции можно до конца не дописывать(остановился на байте 6c) потому что и так находит уже только одну сигу.

LoadLibrary это подгрузить длл в память твоего приложения, GetModuleHandleA это получить адрес модуля в памяти твоего приложения.
консольный проект тебе не нужен, тебе нужен длл проект. dllmain.cpp когда компилишь визуалка выдает ошибку что не удалось запустить но не обращай внимания. она пытается запустить дллку как ехешник но очевидно что это у нее не очень получается. просто по дефолту когда компилишь визуалка сразу запускает твою прогу а в случае дллки майкрософты забыли это убрать. ну там правда настройки есть все дела ну карчое не парься что пишет что не удалось запустить потому что не приложение Win32.
чтобы получить Msg, ты берешь модуль tier0.dll через GetModuleHandleA/GetModuleHandleW(A это ASCII, W это wide string, то есть 16 бит(2 байта) используется на 1 символ строки, то есть это русские буковки английские и куча других языков)(так как "tier0.dll" не содержит русских или еще каких-нибудь буковок то ASCII достаточно), дальше вызываешь GetProcAddress(tier0_dll,"символ который ты нашел в ида, галочку в ида на пкм в экспортах сними с show demangled") и вот ты получил адрес функции. берешь и делаешь из нее указатель на функцию(https://yougame.biz/threads/139802/#post-1677036) и вызываешь с параметрами которые ты определил в ида/дебагере/интуитивно
 
Сверху Снизу