Версия для печати темы
DF2 :: ФОРУМЫ _ HD-мод _ patсher_x86
Автор: baratorch 21 Oct 2016, 13:41
Патчер - это инструмент для модификации любого исполняемого кода (не только героев). Патчер делает модинг максимально стандартизованным, удобным и безопасным. А так же помогает достичь максимальной совместимости между несколькими независимо разрабатываемыми модификациями одного кода (пример - Хд, Хота и ХВ-рулз).
С хд и хотой поставляется уже патчер версии 4.2.2
в нем теперь нет понятия неотменяемого патча.
появились методы WriteAsmPatch и WriteAsmHook, которые позволяют писать конструкции типа:
Код
_PI->WriteAsmHook(0x4F8662,
"push 1",
"call %d", Sleep,
"_ExecDefault",
0);
Код
_PI->WriteAsmPatch(0x59D715,
"MOVZX ESI,BYTE PTR DS:[EAX + EBX - 0x159]",
"MOV EAX, DWORD PTR DS:[EBX + 0x60]",
"TEST EAX,EAX",
"JNZ SHORT 0x59D72A",
"CMP DWORD PTR DS:[EBX + 0x74], 1",
"JE SHORT 0x59D735",
"CMP EAX, 1",
"JNZ SHORT 0x59D7A9",
"CMP DWORD PTR DS:[EBX + 0x74], 2",
"JNZ SHORT 0x59D7A9",
"PUSH DWORD PTR DS:[EBX + 0x6C]",
"PUSH DWORD PTR DS:[EBX + 0x68]",
"MOV ECX, DWORD PTR DS:[EBX + 0x64]",
"PUSH ESI",
"CALL 0x4E54B0",
"MOV ECX, DWORD PTR DS:[EBX + 0x64]",
"MOVSX ECX, WORD PTR DS:[ECX + 0x18]",
"CMP EAX, ECX",
"JG SHORT 0x59D762",
"MOV EDX, DWORD PTR DS:[EDI + 0x8]",
"MOVZX EAX, BYTE PTR DS:[EDX + EBX - 0x159]",
0);
Так же в новом патчере практически устранена разница между Code и Data патчами. Т.е. Code патч всегда пишет код именно так как мы задумали, без неочевидных конвертаций относительных адресов.
И еще ряд мелких правок и багфиксов.
***
Кому-нибудь это вообще интересно?
Расписывать ли мне здесь общую документацию, изменения и документацию к новому функционалу?
Кто-нибудь, кроме меня, Сава и феанора еще пользуется патчером? feanor, тебе это интересно?
Просто у меня весьма ограничено свободное время и совершенно не хочется его тратить впустую.
На вог-форуме есть тема по патчеру, но, к сожалению, цель с которой я эту тему там создавал - не достигнута.
Эра так и не встала на рельсы патчера. Берсу не до эры и тем более не до патчера, поэтому писать туда смысла не вижу.
Автор: Etoprostoya 21 Oct 2016, 14:10
А patcher_x64 сложно будет сделать?
Автор: igrik 21 Oct 2016, 14:18
Я пользуюсь патчером. И да - мне это интересно.
Но пользуюсь я им не настолько глубоко, чтобы делать что-то грандиозное.
Автор: baratorch 21 Oct 2016, 16:43
Цитата(Etoprostoya @ 21 Oct 2016, 16:10)
А patcher_x64 сложно будет сделать?
У меня лично в нем нет потребности и я не знаю особенностей x64
И если учесть что надо будет писать другие
1. дизассемблер длин опкодов, который сейчас в патчере то и не все наверняка x86 опкоды понимает (я про sse, avx). Имеющийся дизассемблер длин писал не я. Я нашел в интернете максимально компактный написанный на asm и скопипастил его в проект, я даже не понимаю как он работает (слишком ассемблерный ассемблер
2. Возможно функцию MemCopyCode, которая копирует x86 код, трансформируя относительные адреса и конвертируя при необходимости short джампы в long
3. ассемблер. Который опять же писал не я. Я использовал исходник однострочного ассемблера OllyDbg 1.04 который доступен по GPL
добавив в него работу с метками (т.е. сделав многострочным) и пару псевдоинструкций. Этот ассемблер тоже не понимает инструкций новее MMX и 3DNow!
4. мосты для хуков,
то да, для меня сделать сложно. Но вообще - реально.
Основные проблемы, короче, - это дизассемблер длин и ассемблер, остальное - уже решаемо мной.
Причем дизассемблер длин нужен компактный и быстрый, писанный на асм/си/си++.
А ассемблер тоже нужен с минимальнейшим функционалом, лишь бы все инструкции понимал. + должен быть написан на си/си++. Я когда искал подходящего кандидата для x86 натыкался либо на каких-то громоздких суперфункциональных чудовищ либо на ущербов, которые даже не все 86ые инструкции переваривали. Ассемблер из OllyDbg - идеален для наших задач, но я нашел его не сразу. Насколько мне известно подобного для х64 - нет (в смысле - открытых исходников).
В принципе можно обойтись и без ассемблера, как без него обходился патчер x86 до версии 4.0. Он нужен только чтобы полностью повторить имеющийся сейчас функционал.
Автор: XEPOMAHT 21 Oct 2016, 16:44
Теоретически не представляю как пользоваться patсher_x86, да хотя бы как просто подключить его к третьим Героям без HD-мода или ХотА. Да и когда отсутствует разобранный диассамблированный код третьих Героев, не знаю, что смогу наменять в Героях, скорее добавлю какие-нибудь баги и вылеты. И на каком языке что-то писать под patсher_x86 (ERM-костылями неудобно, а Си-крест-крест - это слишком высокоуровнево для низкоуровневых представителей человечества) Поэтому для моддинга ERA/MOP only, patсher_x86 is not. И документация к patсher_x86 вряд ли поможет зелёным новичкам типа меня - это уже бесполезно в силу древности игры и минимальному интересу к моддингу у игроков в Героев в целом (может быть patсher_x86 может поднять интерес к моддингу Героев, но вероятность этого крайне низка - эта вещь сделана для программистов/хакеров, а не для обычных моддеров, лично для меня patсher_x86 не представляет интереса, когда есть более понятные и доступные для использования в третьих Героях платформы ERM/WERD), тем более нужно им понадобиться очень подробная документация с примерами "patсher_x86 для чайников" - может ли такую написать профессиональный программист (по себе могу сказать, что у меня ушло много месяцев на расжёвывания документации по ERM, вряд ли получится разобраться в документации patсher_x86, если она всё-таки будет написана)?
Автор: baratorch 21 Oct 2016, 16:55
XEPOMAHT, да, pather_x86 - это инструмент именно для программиста, причем такого, кто готов реверсить код, работая с отладчиками и дизассемблерами.
просто патчер облегчает (ускоряет!) работу, по реверсингу в том числе.
Мне и интересно сколько здесь таких.
Автор: t800 21 Oct 2016, 17:39
Цитата(baratorch @ 21 Oct 2016, 19:55)
XEPOMAHT, да, pather_x86 - это инструмент именно для программиста, причем такого, кто готов реверсить код, работая с отладчиками и дизассемблерами.
просто патчер облегчает (ускоряет!) работу, по реверсингу в том числе.
Мне и интересно сколько здесь таких.
Здравствуйте! А каким образом Ваш патчер ускоряет работу по ререрсингу? Просто летом в свою IDA поставил плагин от James Koppel https://www.hex-rays.com/contests/2011/index.shtml им можно прямо в дебагере патчить и сразу смотреть что получилось.
ЗЫ Если еще если вы дадите инструкцию с каким-нибудь примерам как ползоваться и что можно интересного сделать Вашим патчером, то если хотете я мог бы мог сделать видеоурок про Ваш парчер. Вот.
Автор: Etoprostoya 21 Oct 2016, 17:42
В составе Object File Converter есть дизассемблер с открытым исходным кодом http://www.agner.org/optimize/#objconv Я с ним детально не разбирался, но там очень широкий (если не полный, вплоть до всяких FMA, AVX, AVX512) диапазон распознаваемых и дизассемблируемых команд.
Вот сейчас взглянул на фрагмент кода (opcodes.cpp):
Код
// Primary opcode map. This is the root of the opcode lookup tree
SOpcodeDef OpcodeMap0[256] = {
// name instset prefix format dest. source1 source2 source3 EVEX MVEX link options
{"add", 0 , 0xC50 , 0x13 , 0x1 , 0x1001, 0 , 0 , 0 , 0 , 0 , 0 }, // 00
{"add", 0 , 0x1D50 , 0x13 , 0x9 , 0x1009, 0 , 0 , 0 , 0 , 0 , 0 }, // 01
{"add", 0 , 0 , 0x12 , 0x1001, 0x1 , 0 , 0 , 0 , 0 , 0 , 0 }, // 02
{"add", 0 , 0x1100 , 0x12 , 0x1009, 0x9 , 0 , 0 , 0 , 0 , 0 , 0 }, // 03
{"add", 0 , 0 , 0x41 , 0xA1 , 0x21 , 0 , 0 , 0 , 0 , 0 , 0 }, // 04
{"add", 0 , 0x1100 , 0x81 , 0xA9 , 0x28 , 0 , 0 , 0 , 0 , 0 , 0x80 }, // 05
{"push es", 0x8000, 0x2 , 0x1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, // 06
{"pop es", 0x8000, 0x2 , 0x1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, // 07
{"or", 0 , 0xC50 , 0x13 , 0x1 , 0x1001, 0 , 0 , 0 , 0 , 0 , 0 }, // 08
{"or", 0 , 0x1D50 , 0x13 , 0x9 , 0x1009, 0 , 0 , 0 , 0 , 0 , 0 }, // 09
{"or", 0 , 0 , 0x12 , 0x1001, 0x1 , 0 , 0 , 0 , 0 , 0 , 0 }, // 0A
{"or", 0 , 0x1100 , 0x12 , 0x1009, 0x9 , 0 , 0 , 0 , 0 , 0 , 0 }, // 0B
{"or", 0 , 0 , 0x41 , 0xA1 , 0x31 , 0 , 0 , 0 , 0 , 0 , 0 }, // 0C
{"or", 0 , 0x1100 , 0x81 , 0xA9 , 0x39 , 0 , 0 , 0 , 0 , 0 , 0x80 }, // 0D
Код
// Opcode map for EVEX 0F 3A 39. Indexed by W bit
SOpcodeDef OpcodeMap121[] = {
{"vextracti32x4",0x20,0x868200,0x53 , 0x406 , 0x1203, 0x31 , 0 , 0x20 , 0 , 0 , 0 }, // 0F 3A 39
{"vextracti64x2",0x20,0x869200,0x53 , 0x406 , 0x1203, 0x31 , 0 , 0x20 , 0 , 0 , 0 }}; // 0F 3A 39
// Opcode map for 0F 3A 18. Indexed by EVEX
SOpcodeDef OpcodeMap122[] = {
{"vinsertf128",0x19 ,0x9F8200, 0x59 , 0x1250, 0x1250, 0x450 , 0x31 , 0x30 , 0 , 0 , 0 }, // 0F 3A 18
{0, 0x123 , 0 , 0x59 , 0 , 0 , 0 , 0 , 0 , 0 , 0xC , 0 }};
Если разобраться с этим, то будет полная коллекция команд для дизассемблера и ассемблера.
Ну и вообще, у Агнера Фога много хороших книжек на тему оптимизации ассемблерной, сишной, вместе с примерами и библиотеками.
Автор: feanor 21 Oct 2016, 17:52
Цитата
feanor, тебе это интересно?
В принципе, интересно, но если инклуд будет комментирован на уровне прошлых версий - можно и обойтись, если не до того.
Автор: baratorch 22 Oct 2016, 00:53
Положил на дропбокс патчер 4.2.3 (отличается от 4.2.2 меньшим размером) и обновленный хэдер для Си++:
https://dl.dropboxusercontent.com/u/56675299/patcher_x86%204.2.zip
Итак, изменения версии 4.2 относительно 2.8:
[!] Переработана структура хранения примененных патчей.
Теперь патчи группируются не по адресу установки, как было, а по динамическим диапазонам (отрезкам): начальный адрес - конечный адрес.
В одном таком динамическом отрезке хранятся все пересекающиеся патчи.
Если применяется один патч поверх другого со смещением или отличающийся по размеру - то этот патч помещается в тот же отрезок, при этом отрезок расширяется (меняются его начальный адрес и конечный адрес)
Пока (в версии 4.2), отрезки при применении патчей - создаются, расширяются. Но не уменьшаются и не удаляются при отмене.
Т.е. возможна ситуация когда в одной стопке хранятся не пересекающиеся патчи (когда патч пересекающий их обоих отменен).
В связи с этим теперь:
- можно отменить любой патч, в том числе и перекрытый другим со смещением. FIXED патчей теперь нет.
- методы, GetLastPatchAt GetFirstPatchAt, все зависимые (UndoAllAt и пр.) теперь ищут патч не по адресу установки, а по попаданию в существующий диапазон (в patcher_x86.cpp хэдере используется термин "в окрестности адреса address" вместо "по адресу address").
(методы BlockAt и BlockAllExcept по прежнему оперируют адресами установки)
- изменились форматы дампа и лога патчера.
дамп:
Код
[ ][ ][ ] 3: (00401510 05 HiHook 0000001843 - HD.HotA), (00401510 05 HiHook 0000001845 - HD.HotA), (00401510 05 HiHook 0000002968 - HD.Af)
[ ][s][t] 2: (00424389 04 Patch 0000003595 - HotA), (00424389 06 LoHook 0000003596 - HotA)
[ ][s][t] 2: (0042440B 03 Patch 0000003597 - HotA), (0042440B 05 LoHook 0000003598 - HotA)
[a][s][t] 2: (005A8048 05 HiHook 0000002731 - HD.HotA), (005A8014 64 Patch 0000005395 - HotA)
[a][s][ ] 2: (005F946E 03 Patch 0000001782 - HD.HotA), (005F9470 01 Patch 0000003184 - HD.True32)
[a] значит в стопке есть патчи не совпадающие по адресам установки
[s] значит в стопке есть патчи не совпадающие по размеру
[t] - по типу
число после типа патча - это общая глобальная очередность применения.
в логе ворнинги теперь выглядят так:
Код
WARNING! [a][s][t]: Applying LoHook (00432E91 10 - HotA) over already applied Patch (00432E97 04 - HotA).
[!] теперь для установки EXTENDED_(и SAFE_) CDECL_ HiHook-хуков можно использовать и __stdcall функции (возможность использовать __cdecl функции сохранена)
- это было продекларировано для более ранних версий патчера, но с какой-то версии сломалось и не работало.
Таким образом для EXTENDED_ хука хук-функция имеет единый вид: ? __stdcall new_func(HiHook* hook, ?)
[-] исправлен баг с применеием-отменой-применением хуков поверх длинных патчей (размером больше 5, на практике это серии NOP'ов)
[!] изменена часть мостов хай-хуков, позволяющая устанавливать хуки на рекурсивно выполняющиеся функции. Теперь для не рекурсивных вызовов выполняется один (максимально быстрый и безопасный) код, а для рекурсивных - другой. Поскольку не рекурсивных функций обычно 99+% , такой подход очень сильно повлиял на быстродействие и стабильность.
[ ] теперь патчер жрет на ~ 1.2 - 1.5 мегабайта оперативы меньше (в сравнение с версиями 2.7, 2.8, ...).
[ ] практически устранена разница между CODE_ и DATA_ патчами. Т.е. Code патч всегда пишет код именно так как мы задумали, без неочевидных конвертаций относительных адресов. Таким образом вызовы PatcherInstance::Write(a, d, CODE_) и PatcherInstance::Write(a, d, DATA_) теперь идентичны.
[+] добавлены новые методы PatcherInstance: WriteAsmPatch и WriteAsmHook (а так же CreateAsmPatch и CreateAsmHook):
Код
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// WriteAsmPatch пишет патч по адресу address
// возвращаeт указатель на патч
// аргументы ... - это строки, содержащие код на ассемблере
// распознаются все инструкции, распознаваемые OllyDbg 1.04 (вплоть до MMX и amd 3DNow! включительно)
// ВНИМАНИЕ! в отличие от OllyDbg, целые числа не имеющие префикса 0x или постфикса h читаются как ДЕСЯТЕРИЧНЫЕ!
// поэтому не забывайте писать шеснадцатеричные числа явно
// В одной строке может быть несколько инструкций, разделенных символом ';'
// Код на ассемблере может содержать метки;
// объявление метки - label_name: (имя метки, двоеточие),
// использование label_name (имя без двоеточия);
// максимальная длина имени метки - 39 символов, имя может содержать буквы, цифры, символы '_' и '.';
// код может содержать псевдоинструкцию times (пишет заданное количество раз следующую инструкцию)
// например, результатом "times 3 nop" будет код 90 90 90
// код может содержать псевдоинструкцию _ExecCopy <адрес>, <размер> (пишет код из памяти по адресу <адрес> размером <размер>)
// код может содержать псевдоинструкции db <число>, dw <число>, dd <число или метка>.
// Строка может содержать формат-символы %d. В этом случае за строкой должно следовать соответствующее количество четырехбайтовых чисел (знаковые/беззнаковые/адреса/...)
// ВНИМАНИЕ! последним аргументом (строкой) должен обязательно быть 0 (NULL)!
// абстрактный пример: WriteAsmPatch(0x112233, "begin: call %d", MyFunc, "jmp begin", "jne dword [%d]", 0xAABBCC, 0);
inline Patch* WriteAsmPatch(_ptr_ address, ...)
////////////////////////////////////////////////////////////
// WriteAsmHook пишет по адресу address примитивный хук
// а именно LoHook без вызова высокоуровневой функции.
// тело хука пишется прямо в вызове CreateHexHook или WriteHexHook
// таким же образом как пишется патч с помощью WriteAsmPatch (см. WriteAsmPatch)
// В отличие метода WriteAsmPatch здесь код может содержать (а может не содержать) псевдокоманду _ExecDefault,
// котроая выполняет затертый хуком код
// ВНИМАНИЕ! в коде не может быть больше одной псевдокоманды _ExecDefault
// возвращает указатель на LoHook хук
// ВНИМАНИЕ! последним аргументом (строкой) должен обязательно быть 0 (NULL)!
// абстрактный пример: WriteAsmHook(0x112233, "cmp eax, 0; jne SkipDefault; _ExecDefault; jmp End; SkipDefault: mov ecx, 2; End: retn", 0);
inline LoHook* WriteAsmHook(_ptr_ address, ...)
***
в хэдере в методе
virtual LoHook* __stdcall WriteLoHook(_ptr_ address, _LoHookFunc_ func) = 0;
изменен тип func с void* на _LoHookFunc_, чтобы
в MS VC++ компиляторах (в VC++ 2010 и новее) методу можно было скармливать лямбды:
Код
_PI->WriteLoHook(0xAABBCC, [](LoHook* h, HookContext* c) -> int
{
o_MsgBox("Hello from lambda LoHook!");
return EXEC_DEFAULT;
});
так же для MS VC++ 2010 и новее
я сделал такой макрос (через лямбду опять же):
Код
#define _DO_JOIN2( a1, a2 ) a1##a2
#define JOIN2( a1, a2 ) _DO_JOIN2( a1, a2 )
#define _DO_JOIN3( a1, a2, a3 ) a1##a2##a3
#define JOIN3( a1, a2, a3 ) _DO_JOIN3( a1, a2, a3 )
#define HOOK_CALLTYPE__stdcall 0
#define HOOK_CALLTYPE__thiscall 1
#define HOOK_CALLTYPE__fastcall 2
#define HOOK_CALLTYPE__cdecl 3
#define HOOKFUNC_CALLTYPE_00 __stdcall
#define HOOKFUNC_CALLTYPE_01 __fastcall
#define HOOKFUNC_CALLTYPE_02 __fastcall
#define HOOKFUNC_CALLTYPE_03 __cdecl
#define HOOKFUNC_CALLTYPE_10 __stdcall
#define HOOKFUNC_CALLTYPE_11 __stdcall
#define HOOKFUNC_CALLTYPE_12 __stdcall
#define HOOKFUNC_CALLTYPE_13 __stdcall
#define HOOKFUNC_CALLTYPE_20 __stdcall
#define HOOKFUNC_CALLTYPE_21 __stdcall
#define HOOKFUNC_CALLTYPE_22 __stdcall
#define HOOKFUNC_CALLTYPE_23 __stdcall
#define __ARGS(...) __VA_ARGS__
#define EXTENDED_ARGS(...) HiHook* hook, __VA_ARGS__
#define HOOK_ARGS_00 __ARGS
#define HOOK_ARGS_01(this_, ...) this_, _dword_ ___no_used, __VA_ARGS__
#define HOOK_ARGS_02 __ARGS
#define HOOK_ARGS_03 __ARGS
#define HOOK_ARGS_10 EXTENDED_ARGS
#define HOOK_ARGS_11 EXTENDED_ARGS
#define HOOK_ARGS_12 EXTENDED_ARGS
#define HOOK_ARGS_13 EXTENDED_ARGS
#define HOOK_ARGS_20 EXTENDED_ARGS
#define HOOK_ARGS_21 EXTENDED_ARGS
#define HOOK_ARGS_22 EXTENDED_ARGS
#define HOOK_ARGS_23 EXTENDED_ARGS
#define INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__
#define INTERNAL_EXPAND(x) x
#define INTERNAL_GET_ARG_COUNT_PRIVATE(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, count, ...) count
#define EXPAND_ARGS_PRIVATE_1(...) INTERNAL_EXPAND(INTERNAL_GET_ARG_COUNT_PRIVATE(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3))
#define EXPAND_ARGS_PRIVATE_2(...) INTERNAL_EXPAND(INTERNAL_GET_ARG_COUNT_PRIVATE(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3))
#define RIGHT_CALLTYPE_0(...) 0
#define RIGHT_CALLTYPE_1(...) EXPAND_ARGS_PRIVATE_1(INTERNAL_ARGS_AUGMENTER(__VA_ARGS__))
#define RIGHT_CALLTYPE_2(...) EXPAND_ARGS_PRIVATE_2(INTERNAL_ARGS_AUGMENTER(__VA_ARGS__))
#define RIGHT_CALLTYPE_3(...) 3
#define Create_HiHook(address, t, st, rt, ct, name, args, body) \
[]() -> HiHook*\
{\
static HiHook* inline_hook;\
typedef rt(ct *_DefaultFunc_)args; \
typedef rt(JOIN3(HOOKFUNC_CALLTYPE_, st, JOIN2(RIGHT_CALLTYPE_, HOOK_CALLTYPE##ct) args) *_HookFunc_)(JOIN3(HOOK_ARGS_, st, JOIN2(RIGHT_CALLTYPE_, HOOK_CALLTYPE##ct) args) args); \
_HookFunc_ func_name = [](JOIN3(HOOK_ARGS_, st, JOIN2(RIGHT_CALLTYPE_, HOOK_CALLTYPE##ct) args) args) -> rt body;\
inline_hook = _PI->CreateHiHook(address, t, st, JOIN2(RIGHT_CALLTYPE_, HOOK_CALLTYPE##ct) args, func_name); \
return inline_hook;\
}()
#define Write_HiHook(address, t, st, rt, ct, name, args, body) \
[]() -> HiHook*\
{\
static HiHook* inline_hook;\
typedef rt(ct *_DefaultFunc_)args; \
typedef rt(JOIN3(HOOKFUNC_CALLTYPE_, st, JOIN2(RIGHT_CALLTYPE_, HOOK_CALLTYPE##ct) args) *_HookFunc_)(JOIN3(HOOK_ARGS_, st, JOIN2(RIGHT_CALLTYPE_, HOOK_CALLTYPE##ct) args) args); \
_HookFunc_ func_name = [](JOIN3(HOOK_ARGS_, st, JOIN2(RIGHT_CALLTYPE_, HOOK_CALLTYPE##ct) args) args) -> rt body;\
inline_hook = _PI->WriteHiHook(address, t, st, JOIN2(RIGHT_CALLTYPE_, HOOK_CALLTYPE##ct) args, func_name); \
return inline_hook;\
}()
#define DefaultFunc ((_DefaultFunc_)inline_hook->GetDefaultFunc())
#define OriginalFunc ((_DefaultFunc_)inline_hook->GetOriginalFunc())
чтобы можно было ставить "быстрые инлайн" хайхуки:
Код
Create_HiHook(0xAABBCC, SPLICE_, EXTENDED_,
int, __thiscall, FuncName, (int in, int in2),
{
//...
return DefaultFunc(in, in2);
}
)->ApplyInsert(0);
правда пользоваться ими для написания больших хуков неудобно, потому что IntelliSence не работает как надо внутри.
***
позже буду обновлять первый пост - добавлять в него актуальную информацию, ссылки, примеры.
Автор: feanor 22 Oct 2016, 03:15
Лямбды, ня!
Автор: baratorch 25 Oct 2016, 08:05
Цитата(t800 @ 21 Oct 2016, 19:39)
Здравствуйте! А каким образом Ваш патчер ускоряет работу по ререрсингу?
Несколько примеров из личного опыта:
1. Дано:
В ИДА мы нашли конструктор интересующего нас объекта, в конструкторе нашли виртуальную таблицу, отреверсили определенную функцию F (т.е. +- поняли что она делает) в этой виртуальной таблице.
Найти: откуда в программе эта функция F вообще вызывается?
Решение: ставим с помощью патчера SPLICE_ EXTENDED_ хук на эту функцию F
Внутри хука делаем запись в лог (например в ХД я использую вывод в консоль) результат hook->GetReturnAddress()
так же при этом мы можем в лог писать значения входных аргументов и результат выполнения функции F, например.
Запускаем программу (Героев), совершаем действия, смотрим лог.
2. Нам нужно в отладчике (OllyDbg) поставить брэйкпоинт на чтение или изменение определенного поля определенной структуры в определенный момент (т.е. после выполнения определенных действий).
Решение: ставим хук на нужное место в коде (это место, понятно, должно быть отреверсено), делаем вывод адреса нтересующего нас поля (обычно адрес - динамический), нужной структуры, например с помощью MessageBox
- что одновременно "ставит на паузу" выполнение программы (Героев), позволяя нам поставить бряк в отладчике на нужный адрес. Постановка на паузу особенно актуальна, если интересующее нас поле ДО интересующего нас момента может меняться в программе хренову тучу раз.
Нажимаем ОК в мессадж-боксе - смотрим где сработал бряк.
3. Установка элементарной точки трассировки:
Код
int __stdcall LoHook_TracePoint(LoHook* h, HookContext* c)
{
static int counter = 0;
D_OUT(g, "TracePoint at %X %d", h->GetAddress(), counter++); // это вывод в отладочную консоль
return EXEC_DEFAULT;
}
inline void TP(_ptr_ address)
{
_PI->WriteLoHook(address, LoHook_TracePoint);
}
//далее в месте инициализации хуков пишем:
TP(0xABCDEF);
TP(0xAABBCC);
TP(0xBADF00D);
// и каждый раз при выполнении кода по этим адресам мы будем знать что он выполняется
// т.е. мы будем знать при каких и после каких наших действий в программе интересующий нас код выполняется.
Этого можно было бы добиться установкой бряка в отладчике. Но бряк останавливает выполнение программы, и в случае когда код выполняется много раз в короткий промежуток времени, такой метод (установка ьряка) совсем не юзабелен.
Думаю, те, кто занимается реверсингом эти методы (о)ценят. Хотя возможно я не все знаю о возможностях Olly и IDA и их инструментами можно достичь того же. Если так, то просветите меня, пожауйста.
Цитата
Просто летом в свою IDA поставил плагин от James Koppel https://www.hex-rays.com/contests/2011/index.shtml им можно прямо в дебагере патчить и сразу смотреть что получилось.
я плагин не смотрел, но позволяет ли он менять оригинальный код на новый большей длины? позволяет ли писать новый код не на асме а на с++, скажем?
Если нет, то этот плагин совсем несравним с патчером.
UPD.
сейчас в сети нашел как поставить брейкпоинт на чтение/зменение памяти программно из нашего кода
- в сочетании с Примером 2 вообще удобнота будет.
Автор: igrik 25 Oct 2016, 17:55
А в чем принципиальная разница Hi и Lo хуков?
И еще вопрос: можно ли как-то вывести диалог, с выбором одного из 7-ми элементов. Элементы - изображения ресурсов (дерево и т.д.). (Хотя думаю вопрос не совсем в теме)
Автор: t800 25 Oct 2016, 21:43
Цитата(baratorch @ 25 Oct 2016, 11:05)
Цитата
Просто летом в свою IDA поставил плагин от James Koppel https://www.hex-rays.com/contests/2011/index.shtml им можно прямо в дебагере патчить и сразу смотреть что получилось.
я плагин не смотрел, но позволяет ли он менять оригинальный код на новый большей длины? позволяет ли писать новый код не на асме а на с++, скажем?
Нет не позволяет, только каманды asm можно писать
Цитата(baratorch @ 25 Oct 2016, 11:05)
Цитата(t800 @ 21 Oct 2016, 19:39)
Здравствуйте! А каким образом Ваш патчер ускоряет работу по ререрсингу?
Несколько примеров из личного опыта:
1. Дано:
В ИДА мы нашли конструктор интересующего нас объекта, в конструкторе нашли виртуальную таблицу, отреверсили определенную функцию F (т.е. +- поняли что она делает) в этой виртуальной таблице.
Найти: откуда в программе эта функция F вообще вызывается?
Решение: ставим с помощью патчера SPLICE_ EXTENDED_ хук на эту функцию F
Внутри хука делаем запись в лог (например в ХД я использую вывод в консоль) результат hook->GetReturnAddress()
так же при этом мы можем в лог писать значения входных аргументов и результат выполнения функции F, например.
Запускаем программу (Героев), совершаем действия, смотрим лог.
Может я что-то не понял, а разве по Блок Схеме в IDA нельзя просто по стрелочке глянуть откуда функция F вызывается?
Автор: tolich 25 Oct 2016, 23:39
Цитата(t800 @ 25 Oct 2016, 21:43)
Может я что-то не понял, а разве по Блок Схеме в IDA нельзя просто по стрелочке глянуть откуда функция F вызывается?
Только статические вызовы функций. Динамические вызовы, т.е., через указатель на функцию и вызовы виртуальных функций в статике в принципе не отследить.
Автор: baratorch 26 Oct 2016, 08:57
Цитата
А в чем принципиальная разница Hi и Lo хуков?
HiHook - это "высокоуровневый" хук. Он может ставиться только на функцию.
SPLICE_ - на саму функцию.
CALL_ - на ее конкретный явный вызов (call 0xAABBCC или call dword ptr [0xAABBCC])
FUNCPTR_ - на указатель, т.е. например на позицию в виртуальной таблице или таблице импорта, или на колбэк, передаваемый аргуменом в другую функцию).
SLPICE_ - используется если нам всегда и везде нужно измененное поведение функции, откуда бы она ни вызывалась.
СALL_ - если нам нужно измененное поведение функции только в этом конкретном вызове.
FUNCPTR_ чаще применяется когда по-другому функцию не захучить (функция из таблицы импорта)
если мы ставим FUNCPTR_ хук на функцию из таблицы импорта, то в рамках игры получается тот же эффект, что от SPLICE_ хука.
Если поставим FUNCPTR_ хук на функцию в виртуальной таблице, то наша измененная функция будет вызываться только тем объектом - чья виртуальная таблица. Хотя сама функция при этом может входить в другие виртуальные таблицы других объектов, и на их поведение наш FUNCPTR_ хук уже влиять не будет.
Используя высокоуровневый хук, мы заставляем игру вызывать вместо ее функции - нашу. Наша функция при этом имеет (должна иметь) тот же интерфейс что и оригинальная, т.е. тот же возвращаемый тип и те же аргументы.
Здесь мы совершенно не запариваемся ни о каких низкоуровневых вещах - содержимом регистров и состоянием стэка, и т.п. Нам совершенно не нужно знать ассемблерный контекст вызова. Поэтому я назвал этот тип Хука - высокоуровневым.
LoHook - это, соответственно, "низкоуровневый" хук. Его мы можем поставить вообще на любое место в коде. При этом мы получаем возможность работать с регистрами процессора как с переменными, используя их в нашем (допустим С++) коде и можем менять адрес возврата.
LoHook - гораздо более мощный и универсальный инструмент, в сравнение с HiHook'ом. Им можно решать любые задачи (даже те что решают SPLICE_ и CALL_ хайхуки).
Но я в ХД использую в основном именно ХайХуки - ибо с ними код гораздо более читаемый, удобный для правок, безопасный и минимально конфликтен с другими модификациями.
ЛоуХуки стараюсь использовать как можно меньше. Я их использую в крайних случаях, когда это максимально эффективно (в плане объема работ и скорости внедрения изменений).
Цитата
И еще вопрос: можно ли как-то вывести диалог, с выбором одного из 7-ми элементов. Элементы - изображения ресурсов (дерево и т.д.). (Хотя думаю вопрос не совсем в теме)
можно, конечно. Может быть найду время и выложу UI SDK для героев с примерами.
Автор: igrik 26 Oct 2016, 10:24
Цитата(baratorch @ 26 Oct 2016, 08:57)
Цитата(igrik)
И еще вопрос: можно ли как-то вывести диалог, с выбором одного из 7-ми элементов. Элементы - изображения ресурсов (дерево и т.д.). (Хотя думаю вопрос не совсем в теме)
Можно, конечно. Может быть найду время и выложу UI SDK для героев с примерами.
Это было бы идеально.
Автор: AlexSpl 17 Sep 2017, 20:25
Цитата
Патчер - это инструмент для модификации любого исполняемого кода (не только героев).
Кто-нибудь может привести пример, как использовать патчер для моддинга других игр (например, Героев 2)?
Автор: baratorch 17 Sep 2017, 22:09
Цитата(AlexSpl @ 17 Sep 2017, 22:25)
Цитата
Патчер - это инструмент для модификации любого исполняемого кода (не только героев).
Кто-нибудь может привести пример, как использовать патчер для моддинга других игр (например, Героев 2)?
Сейчас я в отпуске. Вернусь, покажу пример модинга героев 2. Я же начал делать ХД мод для двойки, но запнулся о локализацию, просто обломало ее делать. И работа дальше не пошла, хотя двойка (экзешник с сигнатурами) - благодатнейшая платформа для модинга.
Автор: AlexSpl 17 Sep 2017, 22:28
Было бы очень здорово! Патчить вручную не самое приятное занятие, а возможности патчера я уже успел оценить
Автор: Berserker 08 Nov 2017, 16:38
Цитата
На вог-форуме есть тема по патчеру, но, к сожалению, цель с которой я эту тему там создавал - не достигнута.
Если не ошибаюсь, в то время, как функции установки заплаток Эры с самого начального этапа поддерживали рекурсию, на хуках патчера я столкнулся с багом. После поддержка рекурсии была добавлена, но вместо pushad-подобного вступления была портянка команд на сохранение каждого регистра. Комп у меня был не ахти какой быстрый, решил тогда повременить. А потом не до этого стало. Пишу по памяти ))
Имеется сейчас актуальный pas-файл для подключения патчера и ссылка на сам патчер последней версии?
Автор: baratorch 09 Nov 2017, 22:23
в патчере в мосте лоу-хука до сих пор портянка команд на сохранение каждого регистра
Для все той же обратной совместимости.
Более того, в коде моста много данных в регистр/стек попадают через кучу (временные переменные).
сейчаc код моста вот такой:
Код
"mov dword [%d], esp", &esp_register,
"pushfd",
"pop dword [%d]", &flags_register,
"sub esp, %d", stack_delta,
"push dword [%d]", &flags_register,
"push %d", address + size,
"push edi; push esi; push ebp",
"push dword [%d]", &esp_register,
"push ebx; push edx; push ecx; push eax",
"push esp", //context
"push %d", this,
"call %d", func,
"cmp eax, 0", // SKIP_DEFAULT
"jne exec_default",
// SKIP_DEFAULT
"pop eax; pop ecx; pop edx; pop ebx",
"pop dword [%d]", &esp_register,
"pop ebp; pop esi; pop edi",
"pop dword [%d]", &return_address,
"popfd",
"mov esp, [%d]", &esp_register,
"jmp dword ptr [%d]", &return_address,
//EXEC_DEFAULT
"exec_default:",
"pop eax; pop ecx; pop edx; pop ebx",
"pop dword [%d]", &esp_register,
"pop ebp; pop esi; pop edi",
"pop [%d]", &return_address,
"popfd",
"mov esp, [%d]", &esp_register,
"tail_code: times %d nop", MAX_COPIED_HOOKPATCH_DEFAULT_CODE,
"jmp dword ptr [%d]", &return_address,
Порядок регистров, адреса возврата и регистра флагов здесь продиктован лишь обратной совместимостью.
Да, можно изменив порядок этого всего внутри HookContex сделать короче и красивее.
Но мне лично лень ломать голову как это сделать
я не ASM-нинзя, здесь скорее кто-то вроде MasterOfPuppets нужен.
Предложите ваше решение и я добавлю в патчер "NewFastLoHook" хук. а старый LoHook останется для обратной совместимости.
Но вообще от этой страшной медленной портянки никто, кроме тебя не страдает.
Да и начиная с версии 4 используя PatcherInstance::WriteAsmHook или Patcher::WriteAsmCode можно легко написать низкоуровневый хук точно и тонко самому определив код его моста.
Цитата
Имеется сейчас актуальный pas-файл для подключения патчера и ссылка на сам патчер последней версии?
актуального pas-файла нет, так как он уже несколько лет (!) никому не нужен.
можно использовать старый, но, очевидно, не будет доступен добавленный позднее функционал (например Asm-патчи и Asm-хуки)
Здесь последний патчер версии 4.2.8, хэдер для C++ версии 4.2 и pas-файл версии 2.1:
http://heroes3towns.com/HD/Patcher_x86_SDK.zip
Автор: Berserker 10 Nov 2017, 23:33
Спасибо, старого файла хватит вполне, сам добавлю при необходимости.
Самый простой переходник:
Код
pushad
push esp;
mov eax, [адрес обработчика]
call eax
test eax, eax // выполнять ли результат по умолчанию
jz skip_default
popad
add esp, 4
затёртые команды + nop
push адрес возврата
ret
skip_default:
popad
ret
Я так понимаю, обращение к стеку гораздо быстрее с точки зрения процессора (кэш, предсказание), чем к куче в совсем другой области памяти. Но профилирования не проводил.
Автор: baratorch 11 Nov 2017, 16:13
озадачился я вопросом скорости выполнения моста
состряпал тест вот с таким кодом:
Код
t0 = GetTickCount();
for (int i = 0; i < 500000000; i++)
{
__asm
{
pushad
popad
}
}
printf("pushad... : %d\n", GetTickCount() - t0);
t0 = GetTickCount();
for (int i = 0; i < 500000000; i++)
{
__asm
{
push edi
push esi
push ebp
push esp
push ebx
push edx
push ecx
push eax
pop eax
pop ecx
pop edx
pop ebx
pop esp
pop ebp
pop esi
pop edi
}
}
printf("push edi...: %d\n", GetTickCount() - t0);
t0 = GetTickCount();
for (int i = 0; i < 500000000; i++)
{
__asm
{
push eax
push ecx
push edx
push ebx
push esp
push ebp
push esi
push edi
pop edi
pop esi
pop ebp
pop esp
pop ebx
pop edx
pop ecx
pop eax
}
}
printf("push eax...: %d\n", GetTickCount() - t0);
http://heroes3towns.com/HD/test.exe
результат несколько неожиданный
на моем рабочем ноуте такой:
pushad... : 3074
push edi...: 2386
push eax...: 2590
на лобби сервере для Хоты:
pushad... : 6719
push edi...: 4547
push eax...: 4625
* можно посмотреть в отладчике что тест выполняется честно
* при перестановке местами вариантов результаты теста не меняются
то есть pushad получается самым медленным.
а именно та портянка, которая используется сейчас в патчере - самая быстрая.
***
приведенный мной выше код моста я сократил и убрал перемещение значений esp и регистра флагов через кучу:
Код
"push esp", //esp0
"pushfd",
"sub esp, %d", stack_delta,
"push dword [esp + %d]", stack_delta, //fd
"push %d", address + size,
"push edi; push esi; push ebp",
"push dword [esp + %d]", stack_delta + 0x18, //esp0
"push ebx; push edx; push ecx; push eax",
"push esp", //context
"push %d", this,
"call %d", func,
"cmp eax, 0", // SKIP_DEFAULT
"pop eax; pop ecx; pop edx; pop ebx",
"pop ebp",
"pop ebp; pop esi; pop edi",
"pop dword [%d]", &return_address,
"jne exec_default",
// SKIP_DEFAULT
"popfd",
"mov esp, [esp - 0x18]",
"jmp dword ptr [%d]", &return_address,
//EXEC_DEFAULT
"exec_default:",
"popfd",
"mov esp, [esp - 0x18]",
"tail_code: times %d nop", MAX_COPIED_HOOKPATCH_DEFAULT_CODE,
"jmp dword ptr [%d]", &return_address,
но как сделать перемещение адреса возврата не через кучу я не представляю,
кажется что сохранив функционал этого не сделать..
вобщем теперь переживать из-за размера моста можно будет меньше.
кстати в дампе патчера есть записи
bridge memory - это память выделенная патчером под все мосты.
bridges sizes sum - это суммарный размер всех мостов.
Автор: Berserker 11 Nov 2017, 16:18
Привет, спасибо за тест . Удивлён несколько. Хотя в тесте и нет доступа к памяти в куче (.
Дорабатываю сейчас Эру до поддержки скомпилированных map-карт. Вижу, что патчер используется уже давно в качестве обязательного:
(* Remove Erm trigger "BeforeSaveGame" call *)
Core.p.WriteDataPatch($7051F5, ['9090909090']);
Не использовались только переходники на LoHook. Гляну, можно ли безболезненно подменить одни вызовы другими. Если вызываемая функция получает один и тот же контекст в качестве аргумента, имеет соглашение stdcall, то должно сработать.
Автор: baratorch 11 Nov 2017, 16:49
Цитата(Berserker)
Самый простой переходник:...
Ну ты привел код из эры. Я его итак видел.
Но твой код отличается по функционалу:
во-первых, твой код будет портить регистр флагов, а мой нет (причем у меня в хуке можно читать и менять флаги)
во-вторых с твоим мостом нельзя будет внутри хука делать push (на вог-форуме я уже писал об этом)
в-третьих я не очень понимаю как с твоим кодом внутри хука можно работать с адресом возврата.
у меня на мост посылает команда jmp, а у тебя чтоли call раз адрес возврата уже запушен до начала выполнения моста?
В любом случае если делать возможность полноценной работы с esp (push) внутри хука, то обойтись простыми push/pop с адресом возврата не получится.
Цитата(Berserker)
Если вызываемая функция получает один и тот же контекст в качестве аргумента, имеет соглашение stdcall, то должно сработать.
так не один и тот же контекст же.
у меня в обратном порядке регистры, поэтому и отдельными пушами перемещаются.
Автор: Berserker 11 Nov 2017, 18:08
Странно, что я pushfd/popfd не добавил раньше. Если при заменах call-ов флаги априори и не должны сохраняться, то при перехвате обычных инструкций ещё как должны (.
Адрес возврата менялся через структуру контекста. Context.Ret := новое значение. А вот push-подобая работа со стеком за всё время, если честно, не пригодилась.
С отладочными картами работу закончил (возможность получить человекочитаемый адрес по PE-модулю + смещению вида "Era.3E86C (PoTweak.OnBeforeResetErmFunc in PoTweak.pas on line 117)"). Попробую сейчас перенаправить большую часть перехватов на patcher. Сообщу о проблемах, если будут
PS. Модуль VFS у меня тоже на патчере с тех самых пор. Разве что небольшой рефакторинг делаю, уходя от dword в pointer.
Автор: Berserker 11 Nov 2017, 18:57
Пока что вылеты. Я так понимаю, как LoHook нет метода, возвращающего адрес в рамках моста для вызова оригинальной (замещённой функции) или оригинальных команд с последующим продолжением исполнения, как если бы патча не существовало?.
У меня эти несколько лет Data-патчи уже используют патчер, равно как в достаточно HiHook вызовов. По вылетам смотрю, пока не ясно.
Код
Failed to read data at 33345B20.
EIP: Era.4CE3. Code: C0000005
> Registers
EAX: 0028FD58
ECX: 00000000
EDC: 33345B20
EBX: H3era.0068864C (aGenrltxt_txt)
ESP: 0028FD40
EBP: 0028FD64
ESI: H3era.02808DB4 (dword_6ACEBC + 34979576)
EDI: H3era.0079ECC0 (dword_6ACEBC + 990724)
> Callstack
02C60730
H3era.0055BF05
H3era.0055C02F
H3era.005B946A (A0_Load_genrl_txt_sub_5B9460 + 10)
H3era.004F8133
H3era.0061A964
Kernel32.1336A
Ntdll.398F2
Ntdll.398C5
> Stack
0028FD2C: FFFFFF00
0028FD30: 0028FDA8
0028FD34: 00000005
0028FD38: 00000003
0028FD3C: 00000000
0028FD40*: 33345B20
0028FD44: Era.37193
0028FD48: 0028FD5C
0028FD4C: 0028FE68
0028FD50: Era.371D5
0028FD54: 0028FD64
0028FD58: 00000000
0028FD5C: 00000000
0028FD60: 00000000
0028FD64: 0028FE20
0028FD68: 02C60730
0028FD6C: 029A2A48
0028FD70: 0028FD74
0028FD74: 00000046
0028FD78: H3era.02808DB4 (dword_6ACEBC + 34979576)
0028FD7C: H3era.0067F554 (dword_67F554)
0028FD80: H3era.0068864C (aGenrltxt_txt)
0028FD84: 0028FE1C
0028FD88: 0028FE20
0028FD8C: H3era.02808DB4 (dword_6ACEBC + 34979576)
0028FD90: H3era.0079ECC0 (dword_6ACEBC + 990724)
0028FD94: H3era.004FB10C
0028FD98: 00000206
0028FD9C: H3era.00677D6E (aRb + 2)
0028FDA0: 00000001
0028FDA4: 00000000
0028FDA8: 0000000C
0028FDAC: 00000000
0028FDB0: 00000001
0028FDB4: 00000003
0028FDB8: 80000000
0028FDBC: 00000003
0028FDC0: 00000000
0028FDC4: 0028FDF0
0028FDC8: H3era.00621116 (__openfile + 324)
Лог выше не актуален, ниже использую уже THookContext патчера.
Вот явно кусок патча в куче:
https://yadi.sk/i/KGgPuvxG3Pc9LY
Но возвращает он по адресу в куче куда-то не туда:
https://yadi.sk/i/5vAcbI2y3Pc9Pk
Что я сделал: THookContext сделал синонимом PatchApi.THookContext + подправил ApiHook:
Код
function ApiHook (HandlerAddr: pointer; HookType: integer; CodeAddr: pointer): {n} pointer;
begin
if HookType = HOOKTYPE_BRIDGE then begin
p.WriteLoHook(CodeAddr, HandlerAddr);
end else begin
result := Hook(HandlerAddr, HookType, CalcHookSize(CodeAddr), CodeAddr);
end;
end; // .function ApiHook
Автор: Berserker 11 Nov 2017, 19:19
Поскольку у Эры с плагинами тоже вопрос обратной бинарной совместимости, сохранение флагов и манипулирование стеком не нужны, то предлагаю в перспективе добавить в патчер отдельный метод WriteEraBridgeHook или что-то в этом роде, который будет иметь идентичный функционал + возвращать адрес кода по умолчанию (который в мосте, то бишь в куче = затёртые команды).
Остальное на 90% и так использует патчер.
Но спешить не стоит. Как раз думаю дать возможность функциям установки заплаток связывать заплатки с пользовательским dword. Тогда обработчик (callback) становится делегатом или замыканием, описываемым парой: адрес + указатель на данные.
В любом случае в следующем обновлении Эры я включу последнюю версию патчера. На ней уже и работаю.
Автор: baratorch 11 Nov 2017, 21:01
Цитата(Berserker)
Я так понимаю, как LoHook нет метода, возвращающего адрес в рамках моста для вызова оригинальной (замещённой функции) или оригинальных команд с последующим продолжением исполнения, как если бы патча не существовало?.
Я что-то не понял вопроса.
Всмысле можно ли поставить LoHook который ничего не делает (просто выполняет затертый код)?
Да, можно:
Код
int __stdcall NopFunc(LoHook* h, HookContext* c)
{
return EXEC_DEFAULT;
}
...
_PI->WriteLoHook(0xAABBCC, NopFunc);
или нужно что-то вроде:
Код
int __stdcall Handler(LoHook* h, HookContext* c)
{
какой-то код...
...
выполнить затертое
...
какой-то код...
}
если да, то такое нельзя сделать.
такое есть в WriteAsmHook:
_PI->WriteAsmHook("какие-то команды....; _ExecDefault; какие-то команды...", 0);
Цитата
LoHook нет метода, возвращающего адрес в рамках моста для вызова оригинальной (замещённой функции)
для LoHook нет понятия оригинальной (замещенной) функции.
LoHook устанавливается просто на код. Что там за код под ним - не имеет значения.
Я из кода эры не очень могу понять как устанавливается HOOKTYPE_BRIDGE хук.
В самом мосте адрес возврата у тебя не пушится.
Но в контексте он есть, так?
Значит от попадает в стек до выполнения моста, так?
Значит перенаправление на сам мост из оригинального кода идет посредством call?
У меня же перенаправление на мост идет посредством jmp
а адрес возврата помещается в контекст внутри моста.
Второе. Я правильно понимаю что в твоем HOOKTYPE_BRIDGE мосте, если Handler возвращает EXEC_DEFAULT,
то мост игнорирует адрес возврата из контекста и прыгает обратно туда, куда должен по умолчанию?
У меня же в мост прыгает по адресу возврата из контекста , который мы возможно изменили внутри Handler, в любом случае: вернул ли Handler SKIP_DEFAULT или EXEC_DEFAULT
Автор: baratorch 11 Nov 2017, 21:21
переделал свой тест
добавил в тестируемый код pushfd - popfd
и добавил еще 2 варианта
Код
pushad... : 10172
push edi...: 9781
push eax...: 9906
old... : 12172
new... : 10984
Код
pushad... : 5912
push edi...: 5506
push eax...: 5398
old... : 7566
new... : 6053
old - это то как реализовано перемещение регистров в стек и обратно в последнем опубликованном patcher_x86 4.2.8
через кучу, т.е. некоторые значения проходят путь: регистр -> куча -> стек -> куча -> регистр.
new - это новая реализация: регистр -> стек -> стек -> регистр
По моему очень неплохо для новой, да и старая показывает результат того же порядка. И это 500 000 000 итераций
уж в рамках героев разницей точно можно пренебречь.
Автор: Berserker 11 Nov 2017, 23:35
Цитата
если да, то такое нельзя сделать.
Именно так.
адрес затёртого кода, хранимого в мосте:
[затёртая команда 1]
[затёртая команда 2]
[возврат на адрес после затёртых команд]
Цитата
В самом мосте адрес возврата у тебя не пушится.
У меня мост реализован в виде CALL. Адрес возврата автоматически попадает в стек. Его можно изменить в обработчике через Context.RetAddress := новое значение.
Цитата
Значит перенаправление на сам мост из оригинального кода идет посредством call?
Да.
Цитата
У меня же перенаправление на мост идет посредством jmp
а адрес возврата помещается в контекст внутри моста.
Да, видел.
Цитата
Второе. Я правильно понимаю что в твоем HOOKTYPE_BRIDGE мосте, если Handler возвращает EXEC_DEFAULT,
то мост игнорирует адрес возврата из контекста и прыгает обратно туда, куда должен по умолчанию?
Да, именно так. Затёртый код мог содержать команды, изменяющие ESP, я уже сталкивался с такими багами. Поэтому адрес возврата жёстко забит в MOV EAX, [адрес] при EXEC_DEFAULT.
Цитата
У меня же в мост прыгает по адресу возврата из контекста , который мы возможно изменили внутри Handler, в любом случае: вернул ли Handler SKIP_DEFAULT или EXEC_DEFAULT
Были баги, кода перезаписал места вида ADD ESP, XXX или PUSH XXX или POP XXX. У тебя такие проблемы исключены?
Автор: baratorch 12 Nov 2017, 06:48
Цитата
адрес затёртого кода, хранимого в мосте:
[затёртая команда 1]
[затёртая команда 2]
[возврат на адрес после затёртых команд]
я не понимаю зачем давать пользователю LoHook'a возможность обращаться к адресу затертого кода в мосте
и зачем вызывать такое внутри Handler, ведь на момент вызова этого будет неактуальным и непредсказуемым содержимое регистров.
после return EXEC_DEFAULT в Handler
в мосте происходит следующее:
1. значения всех регистров из контекста (HookContext::eax, ...) копируются в соответствующие регистры
2. выполняется затертый код (затертая команда 1, затертая команда 2, ...)
3. прыжок на HookContext::return_address. Если внутри Handler он не изменялся, то это будет адрес после затёртых команд в исходном коде.
Что тут тебе еще может быть нужно, я не понимаю.
Цитата
Были баги, кода перезаписал места вида ADD ESP, XXX или PUSH XXX или POP XXX. У тебя такие проблемы исключены?
проблем c уcтановкой ЛоуХука на команды изменяющие esp - нет.
Цитата(baratorch)
Код
int __stdcall Handler(LoHook* h, HookContext* c)
{
какой-то код...
...
выполнить затертое
...
какой-то код...
}
Здесь главное то, что после выполнения затертого еще можно выполнить еще какой-то свой код внутри Handler.
Под затертым я имею в виду реально затертое (это может быть джамп другого хука, о котором мы не знаем),
А так-то мы всегда можем в Handler продублировать затираемый оригинальный код манипуляциями с HookContext::eax, ...
Автор: Ben 12 Nov 2017, 10:11
Berserker, вы могли бы подсказать, как использовать патчер для моддинга игры, отличной от Heroes 3, например, Heroes 2, либо для моддинга Heroes 3, но без HD мода ?
Автор: Berserker 12 Nov 2017, 12:32
Бара, поделишься самой последней версией патчера, по которой тесты делал? )
И ещё маленький вопрос, у тебя компилятор может детальный map-файл сгенерировать для patcher_x86.dll?
Цитата
Berserker, вы могли бы подсказать, как использовать патчер для моддинга игры, отличной от Heroes 3, например, Heroes 2, либо для моддинга Heroes 3, но без HD мода ?
Вы знаете, это пришлось бы писать очень длинную инструкцию, чего сейчас делать не могу. Обычно требуется сперва навык работы в отладчике, ручной установке патчей и чуть-чуть программной, чтобы вовсю использовать такие инструменты.
Автор: Ben 12 Nov 2017, 13:34
Цитата(Berserker @ 12 Nov 2017, 12:32)
Вы знаете, это пришлось бы писать очень длинную инструкцию, чего сейчас делать не могу. Обычно требуется сперва навык работы в отладчике, ручной установке патчей и чуть-чуть программной, чтобы вовсю использовать такие инструменты.
Навыки работы в отладчике есть. А если не детальную инструкцию, а какие-то общие принципы/этапы ?
Автор: Berserker 12 Nov 2017, 14:40
У Вас должна быть возможность подгружать свои библиотеки/плагины для изменяемой игры. Поэтому первое, что пишется — это загрузчик. Он либо внедряется в начало исполняемого кода оригинального файла, как Эра:
https://yadi.sk/i/gk4G7ak83PcxrM
(в коде инициализации библиотеки нужно выполнить затёртые команды и вернуться)
Либо загрузчик — внешний исполняемый файл, который вживляет библиотеку в адресное пространство запускаемого процесса оригинальной игры и ждёт, пока библиотека не закончит инициализацию и не вернёт управление, после чего возобновляет основной поток игры. Пример загрузчика:
Код
PROGRAM AngelRun;
USES Win, Utils, APIHook;
CONST
APP_TITLE = 'Angel Run v1.0'; // Заголовок для сообщений приложения
PROCEDURE Err (Mes: STRING);
BEGIN
Win.MessageBox(0, PCHAR(Mes), APP_TITLE, Win.MB_ICONSTOP);
HALT;
END; // .procedure Err
VAR
ExePath: STRING; // Путь к исполняемому файлу
DllPath: STRING; // Путь к внедряемой DLL
PInfo: TProcessInformation; // Информация о процессе
BEGIN
IF ParamCount = 0 THEN BEGIN
Err('Не указан путь к исполняемому файлу');
END; // .if
IF ParamCount = 1 THEN BEGIN
Err('Не указан путь к внедряемой DLL');
END; // .if
ExePath:=ParamStr(1);
IF NOT Utils.FileExists(ExePath) THEN BEGIN
Err('Указанный файл "'+ExePath+'" не существует');
END; // .if
DllPath:=ParamStr(2);
IF NOT Utils.FileExists(DllPath) THEN BEGIN
Err('Указанный файл "'+DllPath+'" не существует');
END; // .if
APIHook.RunProcessWithHookDll(ExePath, DllPath, PInfo);
END.
Код
PROCEDURE RunProcessWithHookDll(ExeFile, DllFile: STRING; VAR PInfo: TProcessInformation);
VAR
si: TStartupInfo;
AsmBlockAddr: POINTER;
Temp: INTEGER;
WriteBuf: ARRAY[0..C_ASMBLOCKSIZE-1] OF BYTE;
Buf: INTEGER;
LibNameAddr: POINTER;
Kernel: INTEGER;
BEGIN
FillChar(si, SizeOf(si), #0);
si.cb:=SizeOf(si);
ExeFile:=GetFullPath(ExeFile);
DllFile:=GetFullPath(DllFile);
CreateProcess(PCHAR(ExeFile), PCHAR(CmdLine), NIL, NIL, FALSE, CREATE_SUSPENDED, NIL, PCHAR(ExtractFilePath(ExeFile)), si, PInfo);
AsmBlockAddr:=VirtualAllocEx(PInfo.hProcess, NIL, C_ASMBLOCKSIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
LibNameAddr:=VirtualAllocEx(PInfo.hProcess, NIL, Length(DllFile)+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Kernel:=LoadLibrary('kernel32.dll');
Buf:=INTEGER(@WriteBuf);
PBYTE(Buf)^:=$68;
PINTEGER(Buf+1)^:=INTEGER(LibNameAddr);
PBYTE(Buf+5)^:=$E8;
PINTEGER(Buf+6)^:=INTEGER(GetProcAddress(Kernel, 'LoadLibraryA'))-INTEGER(AsmBlockAddr)-10;
PBYTE(Buf+10)^:=$68;
PINTEGER(Buf+11)^:=0;
PBYTE(Buf+15)^:=$E8;
PINTEGER(Buf+16)^:=INTEGER(GetProcAddress(Kernel, 'ExitThread'))-INTEGER(AsmBlockAddr)-20;
WriteProcessMemory(PInfo.hProcess, LibNameAddr, PCHAR(DllFile), Length(DllFile)+1, CARDINAL(Temp));
WriteProcessMemory(PInfo.hProcess, AsmBlockAddr, @WriteBuf, C_ASMBLOCKSIZE, CARDINAL(Temp));
WaitForSingleObject
(
CreateRemoteThread(PInfo.hProcess, NIL, 0, AsmBlockAddr, @Temp, 0, CARDINAL(Temp)),
Windows.INFINITE
);
ResumeThread(PInfo.hThread);
END; // .procedure RunProcessWithHookDll
Вот сам загрузчик, принимающий путь к исполняемому файлу и внедряемой библиотеке через командную строку. Антивирусы на этот файл смотрят с нескрываемым диагнозом «вирус», поскольку можно запустить любой процесс, внедрив в него любую библиотеку, что чревато.
https://yadi.sk/d/zBeSI8uk3Pcy8Q
Далее Ваш плагин, используя patcher_x86 или другое средство для установки заплаток устанавливает множество перехватчиков по коду, которые будут генерировать события — вести на Ваши обработчики. Событие: «щелчок мышью», событие «оценка ИИ стоимости объекта» и т.д. Для этого предназначены прежде всего WriteHiHook (вы перехватываете начало функции, можете вызвать оригинальную, можете весь функционал заменить) и WriteLoHook (установить перехватчик в любом месте кода с возможность вызова затёртого кода перед возвратом). Примеры плагинов можете попросить у feanor, Sav, SyDr. Небольшие их модули точно используют патчер.
Вот и всё. Загрузчик — своя библиотека — установка перехватчиков — обработка событий.
Цитата
я не понимаю зачем давать пользователю LoHook'a возможность обращаться к адресу затертого кода в мосте
Редкие случаи. До перевода части кода на патчер это был функционал SPLICE. Если перехватчик установлен на первые команды функции, то можно вызывать как оригинальную функцию, так и перехваченную.
Если перехвачено место, куда идёт несколько прыжков, то адрес в одном из прыжков можно заменить на адрес выполнения затёртого кода, а в другом оставить адрес перехваченного кода. Будут две разные ветви исполнения. Я реально использовал только SPLICE-подобную механику и то, до перехода на патчер.
Тем не менее, в АПИ Эры функция возвращает указатель на выполнение затёртого кода. Как этот указатель будет использоваться — решает пользователь.
Цитата
проблем c уcтановкой ЛоуХука на команды изменяющие esp - нет.
Значит у тебя более продвинутая реализация, это хорошо.
Автор: Ben 12 Nov 2017, 14:56
Спасибо ! Очень интересно !
Автор: Berserker 12 Nov 2017, 15:55
На здоровье! Успехов Вам )
Автор: Ben 12 Nov 2017, 17:08
Цитата(Berserker @ 12 Nov 2017, 14:40)
Далее Ваш плагин, используя patcher_x86 или другое средство для установки заплаток устанавливает множество перехватчиков по коду, которые будут генерировать события — вести на Ваши обработчики.
Плагинов я уже написал определенное количество, но для их подгрузки использовался интерфейс HD мода. Чтобы понять, как подгрузку плагинов выполнить с помощью самого патчера, следует внимательно изучить его исходники ?
Автор: Ben 13 Nov 2017, 10:22
Цитата(Berserker @ 12 Nov 2017, 14:40)
Вот и всё. Загрузчик — своя библиотека — установка перехватчиков — обработка событий.
Ну в общем, понятен принцип. Сами плагины, видимо, могут загружаться либо загрузчиком же, либо библиотекой-патчером, на усмотрение разработчика.
Автор: feanor 13 Nov 2017, 14:08
Цитата
Сами плагины, видимо, могут загружаться либо загрузчиком же, либо библиотекой-патчером, на усмотрение разработчика.
Библиотека-патчер ничего не может загружать по той простой причине, что она библиотека.
Загружать чужой код в общем случае можно либо правкой исходного файла (LoadLibrary куда-то на старт), либо лаунчером с инъекцией кода (разными способами).
(в частном - еще всяческие трюки с поиском уязвимости, типа переполнения буфера)
Автор: Berserker 13 Nov 2017, 16:08
Ben, если речь идёт о системе плагинов, то используйте AngelRun (или любой другой загрузчик dll в чужой процесс), а в самой dll выполните FindFirstFile/FindNextFile/FindClose в определённой папке, загружая все файлы с нужным расширением через LoadLibrary.
Автор: baratorch 14 Nov 2017, 11:23
Здесь последний патчер версии 4.2.9.1, хэдер для C++ версии 4.2 и pas-файл версии 2.1:
http://heroes3towns.com/HD/Patcher_x86_SDK.zip
Автор: Berserker 16 Nov 2017, 22:46
Бара, есть возможность скомпилировать ту же DLL с настройкой генерации detailed *.map-файла в linker?
Open the project's Property Pages dialog box. For details, see Setting Visual C++ Project Properties.
Click the Linker folder.
Click the Debug property page.
Modify the Generate Map File property.
P.S. https://yadi.sk/i/CY2S8Ku63PpTzc
Если я верно понимаю, то LoHook не подходит для многопоточных приложений — гонка данных на запись одного и того же значения в оперативной памяти по абсолютному адресу. Такие баги сложно отловить.
P.S.S. В связи с этим у меня вопрос, являются ли потокобезопасными HiHook-перехватчики? У меня модуль виртуальной файловой системы полностью работает на них. Файловые функции с разных потоков дёргаются. Единственное, что спасает — всё защищено критическими секциями.
Автор: Ben 18 Nov 2017, 18:42
Цитата(Berserker @ 16 Nov 2017, 22:46)
Бара, есть возможность скомпилировать ту же DLL с настройкой генерации detailed *.map-файла в linker?
Open the project's Property Pages dialog box. For details, see Setting Visual C++ Project Properties.
Click the Linker folder.
Click the Debug property page.
Modify the Generate Map File property.
Map файл для версии 2.8:
https://yadi.sk/i/J-ZvmeWu3Pp79m
Автор: Berserker 19 Nov 2017, 13:51
В дополнение к предыдущему посту. Бара, ты используешь VirtualProtect для того, чтобы дать странице памяти права READ_WRITE_EXECUTE, но не возвращаешь оригинальные права обратно после применения патча. А это значит, что любая прямая запись позже в ту же страницу будет успешной, а не приводить к Access Violation. Может быть стоит возвращать странице оригинальные права после внесения правок?
Код
{!} Assert(Utils.IsValidBuf(Dst, Count));
{!} Assert((Src <> nil) or (Count = 0));
result := Count = 0;
if not result then begin
result := Windows.VirtualProtect(Dst, Count, Windows.PAGE_EXECUTE_READWRITE, @OldPageProtect);
if result then begin
Utils.CopyMem(Count, Src, Dst);
result := Windows.VirtualProtect(Dst, Count, OldPageProtect, @OldPageProtect);
end; // .if
end; // .if
Автор: baratorch 19 Nov 2017, 21:25
Berserker, в патчере целиком и полностью забито на многопоточность.
Точнее в патчере:
1. нельзя создавать/ставить/отменять патчи/хуки в несколько потоков.
2. нельзя выполнять код с LoHook и HiHook (кроме DIRECT_) хуками в несколько потоков.
можно в несколько потоков выполнять Asm и DIRECT_ HiHook хуки.
3. нельзя пользоваться методами VarInit и VarFind в несколько потоков.
Цитата
В дополнение к предыдущему посту. Бара, ты используешь VirtualProtect для того, чтобы дать странице памяти права READ_WRITE_EXECUTE, но не возвращаешь оригинальные права обратно после применения патча.
Вообще-то обратно возвращаю.
Автор: Berserker 19 Nov 2017, 22:26
Понял, спасибо за разъяснения. Думаю, стоит это указать в первом посте или документации, что при необходимости поддержки многопоточности нужно использовать Asm и DIRECT_ HiHook-хуки.
Цитата
Вообще-то обратно возвращаю.
Отлично, значит переведя свои WriteCodeAt на Write я получу свои патчи в журналах патчера, не потеряв функциональности хуков Эры. С SetUnhandledExceptionFilter уже разобрался (теперь работает и твой, и мой обработчик). Скорее всего, бинарные патчи тоже пропущу через патчер.
А как насчёт map-файла? Можешь скомпилировать библиотеку с генерацией оного?
Автор: baratorch 20 Nov 2017, 03:12
Цитата
А как насчёт map-файла? Можешь скомпилировать библиотеку с генерацией оного?
map сделаю к следующей версии патчера, которая выйдет на днях.
Автор: Berserker 20 Nov 2017, 13:10
Если что, как работает DIRECT_ не ясно из доков:
DIRECT_ - применение в паскале/делфи не предполагается, в hpp тоже не нашёл )
Автор: baratorch 20 Nov 2017, 15:09
В DIRECT_ HiHook наша замещающая функция должна 100% соответствовать соглашению о вызовах замещаемой.
т.е. если мы ставим хук на __thiscall функцию, то наша должна быть тоже __thiscall, а точнее принимать первый аргумент в ecx, остальные в стеке.
если мы ставим хук на __fastcall, то наша должна принимать аргументы в ecx, edx и стеке.
если на __cdecl, то наша функция должна возвращать управление по retn, а не по retn (4 * колво аргументов в стеке) как это делают __stdcall, __fastcall, __thiscall
В VC++ нет проблем с объявлением __stdcall, __cdecl, __fastcall функций
__thiscall объявить нельзя, но вместо нее можно использовать __fastcall (a1, no_used, a2, ...)
Не знаю как с этим обстоят дела в Делфи/Паскале. С __stdcall проблем не должно быть, __cdecl вроде тоже поддерживается, а вот насчет __fastcall и __thiscall я не уверен.
DIRECT_ HiHook не передает указатель на хук в замещающую функцию и не создает никаких мостов, он передает управление сразу прямо на нашу замещающую функцию.
Автор: baratorch 22 Nov 2017, 16:07
Патчер версии 4.3 + map-файл: http://h3hota.com/HD/Patcher_x86_SDK.zip
Автор: Berserker 24 Nov 2017, 23:36
Бара, зацени: http://rgho.st/7pBPttP9k
Код
instances(44): 'D:\Heroes 3\era.dll', 'MonDescription', 'HD.Plugin.QuicksandMines', 'D:\Heroes 3\Mods\WoG\EraPlugins\erm_hooker.era', 'WoG Patcherizer', 'WoG', 'remove exe protection.bin', 'New_NPC_Dlg', 'adventure menu button.bin', 'always summon ship.bin', 'disable dracolich block.bin', 'dismiss last stack.bin', 'display primary skills over 99.bin', 'dl buttons fix.bin', 'hero screen button.bin', 'inferno gates teleports to any town.bin', 'magic button fix.bin', 'no gifts from allies.bin', 'no load game confirmation.bin', 'no real time trigger.bin', 'no retreat request.bin', 'no secondary skills limit.bin', 'phoenix buttons.bin', 'skin.bin', 'transfer last stack.bin', 'spellbook.bin', 'acredit.bin', 'ai sod radius.bin', 'enable next hero button.bin', 'enable town info.bin', 'fix erm ca b2 command.bin', 'fix erm check syntax.bin', 'fix erm ss a command.bin', 'fix erm tr r roads.bin', 'fix towers damage.bin', 'mp3 44khz patch.bin', 'no erm he f redraw.bin', 'no erm ow r redraw.bin', 'no erm scripts turning off.bin', 'no memory and hdd checks.bin', 'no multiplayer notes.bin', 'remove black screens.bin', 'skeleton transformer fix.bin', 'zvslib.bin',
places count: 1433
patches count: 1460
Все патчи создаются через патчер. Бинарные патчи применяются с созданием уникального экземпляра патчера для каждого файла. По F11 или вылету в папку Debug\Era попадает и список патчей.
Автор: hippocamus 25 Nov 2017, 00:08
Не вполне понимаю, что это значит, но ты, Берс, видимо смог победить некую недружелюбную систему. Заставил HD кушать bin-файлы? Она раньше это делала, а потом перестала, да.
В общем, поздравляю!
Автор: Berserker 25 Nov 2017, 03:13
Зачем же, если Эра их и так кушает? )) Скорее теперь Эра использует patcher в 95% случаев, причём с хорошей детализацией.
А поддержку bin-файлов, думаю, Бара выпилил сознательно. Это живой чит для игры, для которой он разрабатывает мультиплеерные средства и античит.
Автор: igrik 26 Sep 2018, 09:13
baratorch, а как вообще отменять патчи/хуки, созданные другой dll по определенному адресу? Например:
Мне необходимо отменить LoHook по адресу 0x5F4C99, созданный в Some.dll (которая загружена ранее, чем моя и у которой я знаю параметр "CreateInstance"), и установить по этому адресу свой патч в My.dll:
Код
_P = GetPatcher();
_MY = _P->CreateInstance("my");
_SOME = _P->GetInstance("some");
if (_SOME) {
_SOME->GetLastPatchAt(0x5F4C99)->Destroy();
}
// и применяю свой патч
_MY->WriteDword(0x5F4C99 +3, (_dword_)&myArray +4);
Форум Invision Power Board (http://nulled.cc)
© Invision Power Services (http://nulled.cc)