Участник
-
Автор темы
- #1
итак в этом туторе мы будем делать вбе(рисуем под ногами героя кольцо(ну либо любой другой партикль на ваш выбор) когда он засвечен врагами(будь то варды или инвизный герой рядом или еще что)).
состоит из двух частей: получение информации о том, видит ли нас враг и создании партиклей.
начнем с первой части. дота не хранит информацию о том, видит ли в конкретный момент времени враг какого-либо юнита, она лишь совершает какие-то действия при изменении данного параметра, то есть, если враг УВИДЕЛ сущность, то что-то там произошло, если враг ПЕРЕСТАЛ ВИДЕТЬ сущность, что-то еще произошло. то есть мы не можем просто взять какой-нибудь нетвар и из этого сделать вывод видит ли кого-то враг(исключение локал игрок, у него в сущности хранится эта инфа). но мы можем хукнуть коллбек, который вызывается при изменении нужного нам нетвара. коллбек называется OnTeamVisibilityChanged, находим с хрефа.
(на скрине подписано REGISTERNETVARCHANGECALLBACK но это не он, на самом деле он ниже в конце функции там где call [r10+b8], r10+b8 это и есть registernetworkchangecallback чето там)
тут два варианта. из скрина очевидно что загружается указатель на функцию и куда-то суется. мы можем либо пойти реверсить и смотреть че куда там эта фигня суется и потом просто заменить там указатель и назначить свой коллбек(как в вмт хуке), либо можем просто забайтпатчить(что дешевле по времени). я выбрал второй вариант потому что нифига не нашел и желания особо там рыться не было.
давайте определим "байтпатч".
байтпатч это когда мы просто заменяем байтики в памяти, которые отвечают за инструкции данной функции.
хук это когда мы заставляем какую-то функцию(или любой другой объект) передать управление в наши руки.
совмещаем два понятия и получаем "байтпатч хук":
заменяем байтики в функции так, чтобы получились инструкции, передающие управление нам в руки(например jmp, call и т.д.)
остановимся на jmp.
я сижу на х64, поэтому адреса 8 байт. моя длл находится в куче(адрес 0000019D88413500 например)(мануал мап инжект), поэтому чтобы допрыгнуть оттуда из PE(адрес 00007FFB2EA0C4EB например), нужно 8 байт(разница между двумя адресами слишком огромная и в 4 байта не влезет).
следовательно используем две инструкции: mov rax, [absolute 8 byte address]; jmp rax, которые в байтах будут 48 a1 <8 bytes absolute address> ff e0, то есть 12 байт.
теперь давайте разберемся, где же тут лучше хукнуть.
так как мы своим хуком засираем rax, то нужно бы хукнуть перед тем местом, где оригинальная функция сама что-то пишет в rax. такое место присутствует на оффсете +0x29. поэтому нам нужно хукнуть перед этой инструкцией. идеальное место находится на оффсете 0x1b, там лежат 4 инструкции которые в сумме как раз дают 12 байт(даже если бы оно было не так, ничего страшного, мы бы просто нопнули(заменили на 0x90) лишний шлак. тут просто так сложились обстоятельства что никаких лишних действий нам выполнять не надо). наша цель: заменить эти 12 байт на mov rax, [blablabla]; jmp rax, в нашем [blablabla] вызывать MyHook, выполнить те оригинальные 4 инструкции которые мы перезаписали(естественно предварительно их скопировав из оригинала перед байтпатчем) и выполнить прыжок обратно на оригинал после нашего хука(оригинал+0x1b+12).
то есть так оно должно выглядеть после нашей магии:
и наша обертка куда мы прыгаем из оригинала
сначала делаем обертку(то куда мы прыгаем из оригинала), которая состоит из вызова нашей собственной функции которая делает грязные дела + оригинальных инструкций которые затерли самим прыжком + трамплина(прыжок обратно в оригинал):
тут много вариантов.мы либо просто вызываем VirtualAlloc с rwx, либо ваще что душе угодно. я решил сделать говнофункцию не несующую смысловую нагрузку, снять с странички где она находится протект(поставить rwx), и пробайтпатчить ее: сделать там вызов MyHook, выполнить там инструкции с 12 оригинальных байт, выполнить там прыжок обратно на оригинал.
дальше сама функция MyHook:
то есть мы берем rcx, находим сущность которая имеет такой адрес в нашем менеджере, смотрим на *(int*)r8 и в зависимости от значения записываем в нашу сущность видит ли ее враг или нет
помимо этого нам нужно пристально следить за тем,чтобы супер умный компилятор не зарал регистры которые он не собирается восстанавливать, такие как r10 и r11 в моем случае. у вас может быть по-другому, смотрите сами. если видите в вашей собранной функции(откройте вашу длл в x64dbg и найдите функцию по символу) инструкции типа mov r10/mov r11/mov <любой другой регистр который по окончанию функции не восстанавливается(push/pop)> то сочувствую. подрубаем asm и ручками сохраняем данные регистры. заходим в визуалку нажимаем пкм по проекту -> build dependencies ->build customizations -> галочку ставим .masm, создаем Source File с названием <название>.asm пишем там наш шлак типа:
потом в коде где-нибудь extern "C" u64 getr11(); extern "C" void setr1011(u64,u64); и т.д.
в начале функции сохраняем загрязняемые регистры, в конце функции восстанавливаем их. вполне возможно что как только вы внесете малейшие изменения в код функции MyFunc компилятор пересоберет с засиранием уже других регистров... готовьтесь морально.
ну в общем подводим итог байтпатч хука:
в оригинале делаем трамплин: mov rax, [trampoline]; jmp rax; еще нужно учитывать что вы таким хуком засираете rax, поэтому мы и хукаем перед тем как функция сама запишет в rax - в тот момент rax ей нафиг не нужен и не зависимо от того засран он или нет он примет новое значение.
в трамплине: вызываем MyHook, выполняем оригинальные инструкции, прыгаем на оригинал
в MyHook: находим сущность по указателю, чекаем новое значение нетвара, пишем в нашем менеджере что сущность видна/ не видна
дальше партикли. на этот раз не будем использовать саму систему партиклей напрямую, а воспользуемся готовеньким от вальвов, идем в
видим тут кучу хрефов, ищем их в client.dll, и видим как и что нам нужно делать. просто копипастим кароче с asm в с++
находим по строке какую-нибудь функцию с ссылки выше(ищем без мени класса, то есть не Particles.CreateParticle, а ищем просто CreateParticle), а потом ищем вокруг всякие указатели, если видим там какието символы из v8(GetCurrentIsolate или че как там эта фигня называется), если видим там какието указатели с RTTI с явно говорящими именами(CDOTA_ParticleManager например), то поздравляю вы нашли функцию. либо статик анализ(напрягаем глазки и всматриваемся), либо ставите бп и подрубаете джаваскриптовый шлак через впк файл(в соседнем разделе тема есть по этому поводу), что впринципе должно ускорить процесс и сделать его приятнее для вашего мозга и для ваших глаз.
выглядит как-то так(цвет радиус настраиваемы. можно ваще любой другой партикль запихать вместо обычного кольца. можно вообще вместо партиклей на экране рисовать какую-нибудь иконку или еще какую-нибудь хрень(на панораме например))
фулл код:
естественно там могут быть какието баги/краши/лики и т.д. мб где-то чето недоглядел(хотя у меня вроде все норм работает), код предназначен для учебных/развлекательных целей а не инжектить в пабе и джебашить. ну и разумеется вак словите если в пабе будете с этим дерьмом гонять не отрубив вак. так что все тесты проводим в демке/лобби на вальвовском сервере с ботами или друзьями.(на скрине стокгольм лобби с ботами, от паба практически ничем в техническом плане не отличается разве что вак не дают)
следующий тутор наверно будет по панораме(ability cooldowns или называйте как хотите, всякие итембары и т.д.)
создаем новый проект в визуалке и потихоньку по одной копипастим системы приводя их в приятный на ваш взгляд вид.
если есть вопросы спрашивайте.
коллбек работает на любую сущность(крип герой блаблабла) у которой есть данный нетвар.
ну и да забыл упомянуть, если сущность только что заспавнилась( например -createhero), то коллбек не сработает на нее при спавне. то есть как только она создалась, вы не сможете сказать, видит ее враг или нет. мб у нее нетвар хранится в нормальном виде(после коллбека нетвар m_iTaggedAsVisibleByTeam заполняется бесполезным мусором), если честно не чекал(главное не на локалхосте тестить, там нетвар всегда хранится в нормальном виде). в теории такие ситуации возможны разве что если вы реконнектнетесь в катку а какая-то из сущностей уже будет просвечена, и вы не сможете определить просвечена она или нет так как коллбек не сработал(на практике не тестил, можете почекать сами, либо коллбек все-таки сработает, либо нетвар еще не превратился в мусор). ну кароче мне лень тестить, заходите в лобби(в каком-нибудь стокгольме) с другом(чтобы сервер не лег после вашего лива) подождите пока союзник-бот просветится, ставьте паузу, ливайте, заходите обратно и чекайте значение нетвара/вызывается ли коллбек и можете отписаться сюда о своих наблюдениях.
состоит из двух частей: получение информации о том, видит ли нас враг и создании партиклей.
начнем с первой части. дота не хранит информацию о том, видит ли в конкретный момент времени враг какого-либо юнита, она лишь совершает какие-то действия при изменении данного параметра, то есть, если враг УВИДЕЛ сущность, то что-то там произошло, если враг ПЕРЕСТАЛ ВИДЕТЬ сущность, что-то еще произошло. то есть мы не можем просто взять какой-нибудь нетвар и из этого сделать вывод видит ли кого-то враг(исключение локал игрок, у него в сущности хранится эта инфа). но мы можем хукнуть коллбек, который вызывается при изменении нужного нам нетвара. коллбек называется OnTeamVisibilityChanged, находим с хрефа.
(на скрине подписано REGISTERNETVARCHANGECALLBACK но это не он, на самом деле он ниже в конце функции там где call [r10+b8], r10+b8 это и есть registernetworkchangecallback чето там)
тут два варианта. из скрина очевидно что загружается указатель на функцию и куда-то суется. мы можем либо пойти реверсить и смотреть че куда там эта фигня суется и потом просто заменить там указатель и назначить свой коллбек(как в вмт хуке), либо можем просто забайтпатчить(что дешевле по времени). я выбрал второй вариант потому что нифига не нашел и желания особо там рыться не было.
давайте определим "байтпатч".
байтпатч это когда мы просто заменяем байтики в памяти, которые отвечают за инструкции данной функции.
хук это когда мы заставляем какую-то функцию(или любой другой объект) передать управление в наши руки.
совмещаем два понятия и получаем "байтпатч хук":
заменяем байтики в функции так, чтобы получились инструкции, передающие управление нам в руки(например jmp, call и т.д.)
остановимся на jmp.
я сижу на х64, поэтому адреса 8 байт. моя длл находится в куче(адрес 0000019D88413500 например)(мануал мап инжект), поэтому чтобы допрыгнуть оттуда из PE(адрес 00007FFB2EA0C4EB например), нужно 8 байт(разница между двумя адресами слишком огромная и в 4 байта не влезет).
следовательно используем две инструкции: mov rax, [absolute 8 byte address]; jmp rax, которые в байтах будут 48 a1 <8 bytes absolute address> ff e0, то есть 12 байт.
теперь давайте разберемся, где же тут лучше хукнуть.
так как мы своим хуком засираем rax, то нужно бы хукнуть перед тем местом, где оригинальная функция сама что-то пишет в rax. такое место присутствует на оффсете +0x29. поэтому нам нужно хукнуть перед этой инструкцией. идеальное место находится на оффсете 0x1b, там лежат 4 инструкции которые в сумме как раз дают 12 байт(даже если бы оно было не так, ничего страшного, мы бы просто нопнули(заменили на 0x90) лишний шлак. тут просто так сложились обстоятельства что никаких лишних действий нам выполнять не надо). наша цель: заменить эти 12 байт на mov rax, [blablabla]; jmp rax, в нашем [blablabla] вызывать MyHook, выполнить те оригинальные 4 инструкции которые мы перезаписали(естественно предварительно их скопировав из оригинала перед байтпатчем) и выполнить прыжок обратно на оригинал после нашего хука(оригинал+0x1b+12).
то есть так оно должно выглядеть после нашей магии:
и наша обертка куда мы прыгаем из оригинала
сначала делаем обертку(то куда мы прыгаем из оригинала), которая состоит из вызова нашей собственной функции которая делает грязные дела + оригинальных инструкций которые затерли самим прыжком + трамплина(прыжок обратно в оригинал):
тут много вариантов.мы либо просто вызываем VirtualAlloc с rwx, либо ваще что душе угодно. я решил сделать говнофункцию не несующую смысловую нагрузку, снять с странички где она находится протект(поставить rwx), и пробайтпатчить ее: сделать там вызов MyHook, выполнить там инструкции с 12 оригинальных байт, выполнить там прыжок обратно на оригинал.
C++:
u64 MyShitFunction() {
u64 a = __rdtsc();
u64 b = __readgsqword(0);
u64 c = __readgsqword(1);
u64 d = __readgsqword(2);
u64 e = __readgsqword(2);
u64 f = __readgsqword(3);
return a + b + c + d + e + f;
//пишем все что угодно главное чтобы она размером была как минимум 5(call MyHook) + 12+-(оригинальные байты которые мы перезаписали, в нашем случае 12 байт но могло быть больше) + 12(прыжок обратно на оригинал) байт
}
...
//MyShitF это адрес MyShitFunction, TVCHook это адрес нашего MyHook
// тут вместо 0x1b используется оффсет 0x1a потому что сига начинается на 1 байт позже чем со скринов
//то есть кароче OnTVC это адрес функции + 1 конкретно в моем случае потому что я так захотел(не помню почему:D)
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(MyShitFunction, &mbi, sizeof(mbi));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);
*(char*)(MyShitF) = 0xe8;
//call <4 byte rel>
*(int*)(MyShitF + 1) = TVCHook - MyShitF - 5;
//нам нужно прыгнуть с x1 на x2, то есть:
//x1+offset = x2
//offset = x2 - x1
//и из этого еще вычитаем размер инструкции - так как у нас call(e8 <4 byte rel>) то это 5 байт
MemCopy(MyShitF + 5, OnTVC + 0x1a, 12);
//копируем оригинал
*(short*)(MyShitF + 5 + 12) = 0xa148;
//mov rax, [8 byte abs]
TVC_BACK_JUMP = OnTVC + 0x1a + 12;
//то есть нам нужно прыгнуть на оригинал+оффсет на котором мы перезаписали + размер нашей перезаписи(12 байт размер хука)
*(void**)(MyShitF + 5 + 12 + 2) = &TVC_BACK_JUMP;
//у нас инструкция mov rax, [8 byte abs], то есть происходит дереференс, то есть нам нужно запихать сюда именно адрес указателя где будет хранится адрес куда нужно прыгнуть, а не сам этот адрес куда нужно прыгнуть
*(short*)(MyShitF + 5 + 12 + 2 + 8) = 0xe0ff;
//jmp rax
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &mbi.Protect);
//все пробайтпатчили rwx нам не нужен больше
...
//теперь сама функция в доте
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(OnTVC, &mbi, sizeof(mbi));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);
TVC_HOOK_JUMP = MyShitFunction;
*(short*)(OnTVC + 0x1a) = 0xa148;
//mov rax, []
*(void**)(OnTVC + 0x1a + 2) = &TVC_HOOK_JUMP;
//именно указатель на наш трамплин
*(short*)(OnTVC + 0x1a + 2 + 8) = 0xe0ff;
//jmp rax
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &mbi.Protect);
C++:
u64 TVC_HOOK(u64 rcx, u64 rdx, u64 r8, u64 r9) {
g_r10 = getr10();//g_r10 это глобальная 8 байт переменная
g_r11 = getr11();
if (Heroes) {
for (Hero& hero : *Heroes) {
if (hero.entity == rcx) {
hero.VisibleToEnemy = (*(int*)r8 == 14 || *(int*)r8 == 30);
break;
}
}
}
setr1011(g_r10, g_r11);//регеню r10 и r11
setFC(rcx, rdx, r8, r9);//регеню rcx,rdx,r8,r9 потому что __fastcall. сам код функции просто ret и она ничего не делает. нас интересует лишь тот факт что перед вызовом компилятор засунет наши аргументы куда надо.
return 0;
}
помимо этого нам нужно пристально следить за тем,чтобы супер умный компилятор не зарал регистры которые он не собирается восстанавливать, такие как r10 и r11 в моем случае. у вас может быть по-другому, смотрите сами. если видите в вашей собранной функции(откройте вашу длл в x64dbg и найдите функцию по символу) инструкции типа mov r10/mov r11/mov <любой другой регистр который по окончанию функции не восстанавливается(push/pop)> то сочувствую. подрубаем asm и ручками сохраняем данные регистры. заходим в визуалку нажимаем пкм по проекту -> build dependencies ->build customizations -> галочку ставим .masm, создаем Source File с названием <название>.asm пишем там наш шлак типа:
Код:
.CODE
setFC PROC
ret
setFC ENDP
getr10 PROC
mov rax, r10
ret
getr10 ENDP
setr1011 PROC
mov r10, rcx
mov r11, rdx
ret
setr1011 ENDP
getr11 PROC
mov rax, r11
ret
getr11 ENDP
END
в начале функции сохраняем загрязняемые регистры, в конце функции восстанавливаем их. вполне возможно что как только вы внесете малейшие изменения в код функции MyFunc компилятор пересоберет с засиранием уже других регистров... готовьтесь морально.
ну в общем подводим итог байтпатч хука:
в оригинале делаем трамплин: mov rax, [trampoline]; jmp rax; еще нужно учитывать что вы таким хуком засираете rax, поэтому мы и хукаем перед тем как функция сама запишет в rax - в тот момент rax ей нафиг не нужен и не зависимо от того засран он или нет он примет новое значение.
в трамплине: вызываем MyHook, выполняем оригинальные инструкции, прыгаем на оригинал
в MyHook: находим сущность по указателю, чекаем новое значение нетвара, пишем в нашем менеджере что сущность видна/ не видна
дальше партикли. на этот раз не будем использовать саму систему партиклей напрямую, а воспользуемся готовеньким от вальвов, идем в
Пожалуйста, авторизуйтесь для просмотра ссылки.
видим тут кучу хрефов, ищем их в client.dll, и видим как и что нам нужно делать. просто копипастим кароче с asm в с++
находим по строке какую-нибудь функцию с ссылки выше(ищем без мени класса, то есть не Particles.CreateParticle, а ищем просто CreateParticle), а потом ищем вокруг всякие указатели, если видим там какието символы из v8(GetCurrentIsolate или че как там эта фигня называется), если видим там какието указатели с RTTI с явно говорящими именами(CDOTA_ParticleManager например), то поздравляю вы нашли функцию. либо статик анализ(напрягаем глазки и всматриваемся), либо ставите бп и подрубаете джаваскриптовый шлак через впк файл(в соседнем разделе тема есть по этому поводу), что впринципе должно ускорить процесс и сделать его приятнее для вашего мозга и для ваших глаз.
выглядит как-то так(цвет радиус настраиваемы. можно ваще любой другой партикль запихать вместо обычного кольца. можно вообще вместо партиклей на экране рисовать какую-нибудь иконку или еще какую-нибудь хрень(на панораме например))
фулл код:
Пожалуйста, авторизуйтесь для просмотра ссылки.
естественно там могут быть какието баги/краши/лики и т.д. мб где-то чето недоглядел(хотя у меня вроде все норм работает), код предназначен для учебных/развлекательных целей а не инжектить в пабе и джебашить. ну и разумеется вак словите если в пабе будете с этим дерьмом гонять не отрубив вак. так что все тесты проводим в демке/лобби на вальвовском сервере с ботами или друзьями.(на скрине стокгольм лобби с ботами, от паба практически ничем в техническом плане не отличается разве что вак не дают)
следующий тутор наверно будет по панораме(ability cooldowns или называйте как хотите, всякие итембары и т.д.)
создаем новый проект в визуалке и потихоньку по одной копипастим системы приводя их в приятный на ваш взгляд вид.
если есть вопросы спрашивайте.
коллбек работает на любую сущность(крип герой блаблабла) у которой есть данный нетвар.
ну и да забыл упомянуть, если сущность только что заспавнилась( например -createhero), то коллбек не сработает на нее при спавне. то есть как только она создалась, вы не сможете сказать, видит ее враг или нет. мб у нее нетвар хранится в нормальном виде(после коллбека нетвар m_iTaggedAsVisibleByTeam заполняется бесполезным мусором), если честно не чекал(главное не на локалхосте тестить, там нетвар всегда хранится в нормальном виде). в теории такие ситуации возможны разве что если вы реконнектнетесь в катку а какая-то из сущностей уже будет просвечена, и вы не сможете определить просвечена она или нет так как коллбек не сработал(на практике не тестил, можете почекать сами, либо коллбек все-таки сработает, либо нетвар еще не превратился в мусор). ну кароче мне лень тестить, заходите в лобби(в каком-нибудь стокгольме) с другом(чтобы сервер не лег после вашего лива) подождите пока союзник-бот просветится, ставьте паузу, ливайте, заходите обратно и чекайте значение нетвара/вызывается ли коллбек и можете отписаться сюда о своих наблюдениях.
Последнее редактирование: