Я лучше тебя
-
Автор темы
- #1
Шалом.
Сегодня детально поговорим про модификации функций и отдельных их частей.
Для начала, разберемся с терминологией:
Хук - модификация участка памяти приложения, принимающего участие в работе исполняемого кода на любом из этапов работы приложения.
Хук дубликат - подмена участка исполняемого кода с целью искажения конечных и промежуточных результатов выполнения. (подмена функции/части кода функции)
Для конкретного примера используем допотопную штуку.
Сегодня детально поговорим про модификации функций и отдельных их частей.
Для начала, разберемся с терминологией:
Хук - модификация участка памяти приложения, принимающего участие в работе исполняемого кода на любом из этапов работы приложения.
Хук дубликат - подмена участка исполняемого кода с целью искажения конечных и промежуточных результатов выполнения. (подмена функции/части кода функции)
Для конкретного примера используем допотопную штуку.
Код:
void* Create_Hook(
BYTE *src, //Адресс по которому будет установлен хук
const BYTE *dst, //Адресс блока с дубликатом
const int len) //Размер нашего хука
{
BYTE *jmp;
DWORD dwback;
DWORD jumpto, newjump;
VirtualProtect(src,len,PAGE_READWRITE,&dwback);
if(src[0] == 0xE9)
{
jmp = (BYTE*)malloc(10);
jumpto = (*(DWORD*)(src+1))+((DWORD)src)+5;
newjump = (jumpto-(DWORD)(jmp+5));
jmp[0] = 0xE9;
*(DWORD*)(jmp+1) = newjump;
jmp += 5;
jmp[0] = 0xE9;
*(DWORD*)(jmp+1) = (DWORD)(src-jmp);
}
else
{
jmp = (BYTE*)malloc(5+len);
memcpy(jmp,src,len);
jmp += len;
jmp[0] = 0xE9;
*(DWORD*)(jmp+1) = (DWORD)(src+len-jmp)-5;
}
src[0] = 0xE9;
*(DWORD*)(src+1) = (DWORD)(dst - src) - 5;
for(int i = 5; i < len; i++)
src[i] = 0x90;
VirtualProtect(src,len,dwback,&dwback);
return (jmp-len);
}
Начнем работу с постановки задачи.
Допустим я взламываю игру и в ней есть функция, которая возвращает float, а я хочу возвращать свой float вместо того, который рассчитывает игра, хочу подменить функцию целиком.
Путем реверса машинного кода, про который поговорим в будущем, я выбрал для себя функцию
ее прототип выглядит так:
Прописываем в коде, свой прототип.
Как это работает? почему аргументов стало два?
Начинается все с typedef, далее указывается тип данных, после указывают соглашение о вызовах, __thiscall, __stdcall, __fastcall, __cdecl и так далее, соглашение о вызовах нужно указывать то, которое соответствует оригинальной функции.
Общий шаблон:
Допустим я взламываю игру и в ней есть функция, которая возвращает float, а я хочу возвращать свой float вместо того, который рассчитывает игра, хочу подменить функцию целиком.
Путем реверса машинного кода, про который поговорим в будущем, я выбрал для себя функцию
ее прототип выглядит так:
Так как же написать свою функцию, которая заменит эту? очень просто.float __thiscall WeaponBase::GetDamage(bool)
Прописываем в коде, свой прототип.
Код:
typedef float (__thiscall* _getDamage)(WeaponBase*, bool);
Начинается все с typedef, далее указывается тип данных, после указывают соглашение о вызовах, __thiscall, __stdcall, __fastcall, __cdecl и так далее, соглашение о вызовах нужно указывать то, которое соответствует оригинальной функции.
- __thiscall - функция в классе (именно по этому и добавляется 1 аргумент, это указатель на тот самый класс к которому принадлежит функция)
- __fastcall - передает часть параметров и результат выполнения через регистры.
- __stdcall - для WINAPI функций.
Общий шаблон:
Опытным путем, в удобном месте прописываем хук.typedef Тип (Соглашение * имя)(Аргументы);
Код:
//Наш прототип
typedef float (__thiscall* _getDamage)(WeaponBase*, bool);
//Экземпляр прототипа, на случай если захотим вернуть оригинальную функцию
_getDamage Damage;
//наш дамаг
float myDamage = 200.f;
bool Damage_enable;
//наша функция дубликат, которая заменит оригинал
float __thiscall myGetDamage(WeaponBase* Weapon, bool b1)
{
//если наша функция дамага в меню допустим, включена, возвращаем свой дамаг
if(Damage_enable)
return myDamage;
//иначе код дойдет сюда, и мы вернем оригинал!
return Damage(Weapon, b1);
}
Вроде прописали, теперь устанавливаем хук.
Код:
DWORD Адресс_начала_нашей_функции = 0x530D52;
Damage = (_getDamage)Create_Hook(
(BYTE*)Адресс_начала_нашей_функции, //Адресс оригинальной функции в памяти приложения
(BYTE*)myGetDamage, //наша функция-дубликат
5); //Размер хука
Что произойдет?
Когда игра вызовет нужную нам функцию, в самом ее начале будет стоять наш хук, который заставит исполняемый код сделать безусловный прыжок на нашу функцию-дубликат, которая при нужных нам условиях вернет коду игры нужное нам значение.
Почему размер хука 5 байт?
5 байт, это минимум который нужен для совершения того самого безусловного прыжка на нашу функцию-дубликат. То есть 0xE9 = jmp и 4 байта адресс.
Меняем задачу:
Я не хочу подменить всю функцию, хочу изменить лишь кусок.
Для этого придется возится с асм инструкциями и регистрами, чтобы достичь понимания работы функции и понять в каком месте правильней всего подменить данные. В моем случае, это произойдет в конце функции:
Когда игра вызовет нужную нам функцию, в самом ее начале будет стоять наш хук, который заставит исполняемый код сделать безусловный прыжок на нашу функцию-дубликат, которая при нужных нам условиях вернет коду игры нужное нам значение.
Почему размер хука 5 байт?
5 байт, это минимум который нужен для совершения того самого безусловного прыжка на нашу функцию-дубликат. То есть 0xE9 = jmp и 4 байта адресс.
Меняем задачу:
Я не хочу подменить всю функцию, хочу изменить лишь кусок.
Для этого придется возится с асм инструкциями и регистрами, чтобы достичь понимания работы функции и понять в каком месте правильней всего подменить данные. В моем случае, это произойдет в конце функции:
Код:
.text:005310F6 fld dword ptr [ebp-10h]
.text:005310F9 mov ecx, [ebp-0Ch]
.text:005310FC pop ebx
.text:005310FD mov large fs:0, ecx
.text:00531104 mov esp, ebp
.text:00531106 pop ebp
.text:00531107 retn 4
Анализируя псевдокод в IDA Pro я понял что результат запишется по адресу регистра ebp - 0x10, в инструкции fld dword ptr [ebp-10h] следовательно опишу свою функцию для подмена этой инструкции.
Теперь смотрю, сколько байт в памяти занимает нужная мне инструкция, fld dword ptr [ebp-10h] , 3 байта, для хука нужно минимум 5, следовательно, хук будет подменять сразу две инструкции, чтобы часть кода не была утеряна.
То есть, подменим две инструкции,
То бишь, мы прыгнем на инструкцию pop ebx (берем ее адресс, и отнимаем от адреса инструкции fld dword ptr [ebp-10h])
Получится 005310FC - 005310F6, ставим калькулятор Windows в режим программист, тип в hex и считаем, ура, вышло 6 байт.
Из этого выходит:
Теперь смотрю, сколько байт в памяти занимает нужная мне инструкция, fld dword ptr [ebp-10h] , 3 байта, для хука нужно минимум 5, следовательно, хук будет подменять сразу две инструкции, чтобы часть кода не была утеряна.
То есть, подменим две инструкции,
и следующую за ней:fld dword ptr [ebp-10h]
Сумарно получится 6 байт. (просто посчитать число байт инструкций в хексе, D9 45 F0 8B 4D F4)mov ecx, [ebp-0Ch]
То бишь, мы прыгнем на инструкцию pop ebx (берем ее адресс, и отнимаем от адреса инструкции fld dword ptr [ebp-10h])
Получится 005310FC - 005310F6, ставим калькулятор Windows в режим программист, тип в hex и считаем, ура, вышло 6 байт.
Из этого выходит:
Код:
//Наш дамаг
float myDamage = 200.f;
//Включена ли функция?
bool Damage_enable;
//Адресс нужной нам инструкции:
DWORD dwDamage_address = 0x005310F6;
//Адресс кода на который мы вернемся после изменения промежуточного результата выполнения
DWORD dwReturnDamage_address = dwDamage_address + 0x6;
_declspec(naked)int Damage_hook(void)
{
//Если наша функция включена
if (Damage_enable)
{
//выполняем нужные нам инструкции
_asm fld dword ptr[myDamage]
_asm mov ecx, [ebp - 0Ch]
_asm jmp dword ptr[dwReturnDamage_address]
}
else //если нет
{
//Выполняем оригинальные
_asm fld dword ptr[ebp - 10h]
_asm mov ecx, [ebp - 0Ch]
_asm jmp dword ptr[dwReturnDamage_address]
}
}
Ставим хук:
По тому что мы подменяем 6 байт, нам не нужно чтобы поток ушел в рекурсию, снова и снова прыгая на наш хук.
Общий шаблон:
Почему ретурн через 6 байт?Create_Hook((BYTE*)dwDamage_address, (BYTE*)Damage_hook, 6);
По тому что мы подменяем 6 байт, нам не нужно чтобы поток ушел в рекурсию, снова и снова прыгая на наш хук.
Общий шаблон:
Код:
//Адресс подменяемой инструкции
DWORD someAddress = 0;
//someAddress + Точное число байт, размер всех подменяемых инструкций приложения минимум 5,
//если 5 не хватает берем больше, однако не забываем добавить инструкции которые взяли, чтобы исполняемый код не терялся.
DWORD returnSomeAddress = someAddress + 5;
//состояние функци
bool state;
//хук-дубликат учатска кода
_declspec(naked)int someName(void)
{
//Если наша функция включена
if (state)
{
//выполняем измененные инструкции
//и прыгаем на адресс, который сразу после подменяемых нами инструкций в оригинальной памяти
//чтобы продолжить выполнение функции
_asm jmp dword ptr[returnSomeAddress]
}
else //если нет
{
//Выполняем оригинальные
//и делаем тоже самое. (прыгаем на продолжение)
_asm jmp dword ptr[returnSomeAddress]
}
}
Что нам дает это знание?
Возможности изменять результат выполнения кода в различных приложениях. Вариантов применения миллиарды. На пример, у вас есть конкурент, у которого лаунчер с привязкой. Вы копаясь в его лаунчере, нашли функцию генерации ключа, который проверяется в бд, зная как работают хуки вы сможете подменить ключ на уже активированный и постебать паренька.
Все зависит от вашей фантазии и желания разьебать что нибудь.
Все вопросы и замечания выслушаю в комментариях.
Дополнительные обучающие материалы из внешних источников: (кликабельные ссылки)
Писалось на скорую руку и без компилятора, если что-то интересно, кидайте свои примеры, помогу разобраться подробней. Спасибо за внимание.
Возможности изменять результат выполнения кода в различных приложениях. Вариантов применения миллиарды. На пример, у вас есть конкурент, у которого лаунчер с привязкой. Вы копаясь в его лаунчере, нашли функцию генерации ключа, который проверяется в бд, зная как работают хуки вы сможете подменить ключ на уже активированный и постебать паренька.
Это только несколько вариантов применения этих знаний из бесконечного множества.Хукая винапи, можно даже вытягивать указатели из экстернал читов. Если самому обновлять лень)
Все зависит от вашей фантазии и желания разьебать что нибудь.
Все вопросы и замечания выслушаю в комментариях.
Дополнительные обучающие материалы из внешних источников: (кликабельные ссылки)
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
Писалось на скорую руку и без компилятора, если что-то интересно, кидайте свои примеры, помогу разобраться подробней. Спасибо за внимание.