Всем привет. Решил сделать перевод достаточно старой темы. Конечно очень многие знают английский язык и сами смогут это перевести, но надеюсь на то, что данная тема будет полезна кому-то. Автор данной статьи: @JD96 Помощь с переводом: @Ch1rk0v - vk.com/chirkovnh (не реклама). Если вы нашли ошибку пожалуйста отпишите в лс, исправлю. - О хуках- Хуки это одни из самых важных вещей в интернал читах и больше всего читов детектятся потому-что метод хука палится анти читом или же сигнатура чита задетекчена анти читом. По этому тебе нужно знать в чем заключается разница между разными методами хукинга и как и для чего они могут использоваться. Перейдем к наиболее популярным и различным методам хукинга)). - Byte patching (.text section) - Скорость выполнения: 10 баллов Уровень скилла шобы хукнуть: 2 балла Риск обнаружения: 5 - 7 балла Byte patching самый простой способ чтобы хукнуть какую либо функцию. Часто используются библиотеки для хуков, такие как Microsoft Detours. Некоторые анти-читы до сих пор не сканят .text section, но большинство анти читов на данный момент все таки вам дадут по ебучке, так что не рекомендую хукать таким образом, разве что какие-то мемные игры. Есть разные способы перенаправить поток кода. Можно разместить обычную инструкцию JMP (размером 5 байт) или попробовать выполнить hotpatching с использованием короткого JMP (размером 2 байта) в какое-либо место, где есть больше места для 5-байтового JMP. Можно разместить инструкцию CALL, которая воркает так же, как JMP, но перед пушем помещает обратный адрес в стек. Так же можно просто поместить адрес в стек, а затем вызвать RETN, который перейдет к последнему адресу в стеке и, следовательно, будет вести себя как JMP. Пример этого метода : JMP 0xDEADBEEF (short and long) CALL 0xDEADBEEF PUSH 0xDEADBEEF RETN Я обнаружил, что GameGuard сканирует только на те прямые манипуляции, о которых я упоминал. Вы можете сделать гораздо больше! Вы можете вызвать исключение. Во-первых, вы должны будете зарегистрировать фильтр исключений (AddVectoredExceptionHandler, SetUnhandledExceptionFilter), который улавливает это исключение. Затем вам нужно как-то триггернуть исключение. Например, вы можете разместить точку остановки INT3 (брикпоинт) которая представляет собой однобайтный 0xCC. Затем в своем фильтре вы можете перенаправить указатель инструкции (EIP) на свою собственную функцию: LONG WINAPI UnhandledExceptionFilter(EXCEPTION_POINTERS *pExceptionInfo) { //Check exception type if(pExceptionInfo->ExceptionRecord->ExceptionCode == UNK) { //Check exception address if(pExceptionInfo->ExceptionRecord->ExceptionAddress == 0xDEADBEEF) { dwEndsceneJMP = (DWORD)pExceptionInfo->ExceptionRecord->ExceptionAddress + 5; //Save the address where we return after our hook pExceptionInfo->ContextRecord->Eip = (DWORD)hkEndscene; //Change instruction pointer - will jump to our hook return EXCEPTION_CONTINUE_EXECUTION; //Copies all registers from pExceptionInfo to the real registers } } return EXCEPTION_CONTINUE_SEARCH; //Keep searching for any exceptions } Это работает со всеми видами исключений, вам просто нужно вызвать их, либо разыменовав нулевой указатель, либо разделив на ноль: xor eax, eax div eax mov eax, dword ptr ds:[0] ... Эти перенаправления могут быть размещены повсюду, в большинстве случаев они размещаются в начале функции, поэтому стек и регистры полностью правильны и готовы к использованию через typedefs.. Можно так же поместить перенаправление ниже / глубже в функцию, которая затем вызывается хуком Midfunction. Для этого вам нужны некоторые дополнительные знания ассемблера, потому что стек и регистры обычно не идеальны для использования, и вам нужно получить параметры другим способом. Тутор по midfunction хукингу будет переведен чуть позже если вам зайдет эта тема. - IAT/EAT Hooking - Скорость выполнения: 10 баллов Уровень скилла шобы хукнуть: 3 балла Риск обнаружения: 5 баллов Этот метод хукинга базирован на том как PE-файлы работают в Windows. Это "Import/Export Address Table". - ссылка на вики прочитайте.( кликабельно ) Эта адресная таблица содержит указатель на API и корректируется загрузчиком PE при выполнении файла. Вы можете либо зациклить всю таблицу, найти функцию и перенаправить ее, либо найти ее вручную с помощью OllyDbg или IDA. Основная идея состоит в том, что вы заменяете определенный API на свою подключенную функцию. Это не только хорошо для простого подключения API, но также может использоваться для подключения например DirectX. pEnterCriticalSection = *(EnterCriticalSection_t*)hCriticalSection; *(DWORD*)hCriticalSection = (DWORD)nEnterCriticalSection; Как видите, это простое перенаправление указателя. Забавно то, что этот старый метод до сих пор не обнаруживается некоторыми античитами, включая HackShield и XignCode(не актуальные анти читы на сегодняшний день) не рекомендую использовать данный метод сейчас. - VMT Hooking / Pointer redirections - Скорость выполнения: 10 баллов Уровень скилла шобы хукнуть: 3 - 5 балла Риск обнаружения: 3 балла Один из лучших методов хукинга (бл*ть не знаю как адаптировать это слово иначе), потому что нет API или базового способа обнаружения этих хуков.. Большинство античитов обнаруживают хуки VMT на D3D-устройстве движка, но это не то, что мы хотим делать в любом случае. Почти у каждого движка есть внутренний класс рендеринга, который можно подключить. Например, вы можете просто хукнуть Endscene с помощью детуров и логнуть returnaddress . Когда вы проверяете код по returnaddress, вы найдете функцию, которая вызывает Endscene. Теперь найдите ссылку на эту функцию и немного реверсните, вы, скорее всего, получите указатель в разделе .data, который представляет виртуальную таблицу. Эти таблицы содержат просто адреса функций и могут быть легко заменены даже без использования VirtualProtect, поскольку .data обычно имеет флаги Read/Write. Вот небольшой пример хука D3DXFont::DrawTextA на довольно неизвестном движке: DWORD pNewVtable[18]; DWORD dw1 = *(DWORD*)0x10235BA8; DWORD dw2 = *(DWORD*)(dw1 + 0x86C); DWORD dw3 = *(DWORD*)dw2; //DWORD pNewVtable[18]; DWORD dw1 = *(DWORD*)0x10235BA8; DWORD dw2 = *(DWORD*)(dw1 + 0x86C); DWORD dw3 = *(DWORD*)dw2; //D3DXFont memcpy(pNewVtable, (void*)dw3, 18 * 4); //VT has 18 functions -> 18*4 bytes oDrawTextA = (tDrawTextA)pNewVtable[14]; //Index: 14 == DrawTextA pNewVtable[14] = (DWORD)hkDrawTextA; *(DWORD*)dw2 = (DWORD)pNewVtable;D3DXFont memcpy(pNewVtable, (void*)dw3, 18 * 4); //VT has 18 functions -> 18*4 bytes oDrawTextA = (tDrawTextA)pNewVtable[14]; //Index: 14 == DrawTextA pNewVtable[14] = (DWORD)hkDrawTextA; *(DWORD*)dw2 = (DWORD)pNewVtable; Как видите, он полностью основан на движке и требует некоторого базового понимания того, как работают VTables. Ваш хук может быть задетекчен только в том случае, если провайдер античита добавит ваш VTable в список сканирования. Конечно, есть миллионы способов наебать это, и не все таблицы VT включены в этот список. Так что проявите изобретательность, и ваш хук останется андетект. - HWBP Hooking - Скорость выполнения: 6 баллов Уровень скилла шобы хукнуть: 6 баллов Риск обнаружения: 4 балла Мы уже говорил раньше о брикпоинтах (точка остановки) но в этот раз мы не хотим какие-либо байты в .text section. Как я сказал раньше вам так же необходимо разместить обработчик исключений, чтобы перехватить исключение! Их можно разместить отдельно для каждого треда, но это также означает, что нам нужен хендел треда. Некоторые античиты скрывают все треды с помощью руткитов, но это не значит, что мы не можем попасть в тред! Здесь я собираюсь представить вам два способа установки брикпоинтов, сначала простой метод, просто зацикливая все треды: HANDLE hThreadSnap = INVALID_HANDLE_VALUE; THREADENTRY32 te32; hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hThreadSnap) { te32.dwSize = sizeof(THREADENTRY32); if (!Thread32First(hThreadSnap, &te32)) { CloseHandle(hThreadSnap); return 0; } do { if (te32.th32OwnerProcessID == GetCurrentProcessId() && te32.th32ThreadID != GetCurrentThreadId()) //Ignore threads from other processes AND the own thread of course { HANDLE hThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME, 0, te32.th32ThreadID); if (hThread) { CONTEXT context; context.ContextFlags = CONTEXT_DEBUG_REGISTERS; SuspendThread(hThread); //Suspend the thread so we can safely set a breakpoint if (GetThreadContext(hThread, &context)) { context.Dr0 = 0; //Dr0 - Dr3 contain the address you want to break at context.Dr1 = 0; //You dont have to set them all context.Dr2 = 0; context.Dr3 = 0; //Those flags activate the breakpoints set in Dr0 - Dr3 //You can either set break on: EXECUTE, WRITE or ACCESS //The Flags I'm using represent the break on execute context.Dr7 = (1 << 0) | (1 << 2) | (1 << 4); SetThreadContext(hThread, &context); } ResumeThread(hThread); CloseHandle(hThread); } } } while (Thread32Next(hThreadSnap, &te32)); CloseHandle(hThreadSnap); } Второй способ немного другой, но суть такая же. Мы просто хукаем функцию, которая выполняется в том же треде, используя один из других методов, о которых мы уже говорили ранее. Это не должно быть задетекчено мгновенно, но оно МОЖЕТ быть задетекчено через несколько секунд / минут. После того, как мы хукнули тред, мы можем получить его дескриптор с помощью GetCurrentThread (), что делает ненужным цикл для всех потоков. Конечно, вы должны восстановить первый хук, как только вы разместили HWBP, чтобы не было детекта. Этот метод задетекчен такими античитами HackShield и некоторых версий XignCode, отлично работает с GameGuard, Punkbuster, всеми другими античитами gay r3. Вы можете просто хукнуть GetThreadContext (лучше хукнуть версию ntdll), чтобы он не был детект. Многие античиты перезагружают фиксированные версии ntdll, kernel32 и других, поэтому вы не можете напрямую размещать хуки. В этом случае вы можете либо найти эти области памяти, либо перечислить загруженные библиотеки DLL, либо сравнить EAT (экспортную таблицу адресов) - PageGuard Hooking - Скорость выполнения: 1 балл Уровень скилла шобы хукнуть: 8 баллов Риск обнаружения: 1 балл Хуки PageGuard понастоящему стеслово, их почти не детектят античиты. Сначала вам нужно зарегистрировать обработчик исключений. Затем вы должны вызвать исключение, на этот раз пометив все пейдж мемори с помощью PAGE_GUARD с помощью VirtualProtect, что приведет к исключению. Когда вы прочитаете о PAGE_GUARD в msdn, вы обнаружите, что он автоматически удаляется после первого исключения. В нашем обработчике исключений мы теперь устанавливаем одношаговый флаг и пошагово выполняем все инструкции, пока не дойдем до нужного адреса. Мы можем снова изменить EIP, как мы это делали раньше, но теперь мы должны снова пометить пейдж мемори как PAGE_GUARD, иначе хук не сработает снова! Этот метод хукинга работает очень медленно из-за использования одношагового флага и должен использоваться только для функций, которые вызываются очень редко. Вот небольшой пример: DWORD dwOld; SYSTEM_INFO sSysInfo; GetSystemInfo(&sSysInfo); VirtualProtect((void*)0xDEADBEEF, sSysInfo.dwPageSize, PAGE_EXECUTE | PAGE_GUARD, &dwOld); ... LONG WINAPI UnhandledExceptionFilter(EXCEPTION_POINTERS *pExceptionInfo) { if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) { if (pExceptionInfo->ContextRecord->Eip == 0xDEADBEEF) { dwJmpBack = (DWORD)pExceptionInfo->ContextRecord->Eip + 5; pExceptionInfo->ContextRecord->Eip = (DWORD)hkFunction; } pExceptionInfo->ContextRecord->EFlags |= 0x100; //Set single step flag return EXCEPTION_CONTINUE_EXECUTION; } if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { DWORD dwOld; SYSTEM_INFO sSysInfo; GetSystemInfo(&sSysInfo); VirtualProtect((void*)0xDEADBEEF, sSysInfo.dwPageSize, PAGE_EXECUTE | PAGE_GUARD, &dwOld); return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } - Forced Exception hooking - Скорость выполнения: 5 баллов Уровень скилла шобы хукнуть: 8 баллов Риск обнаружения: 2 балла Вы можете принудительно создавать исключения в программе, манипулируя указателями и сохраненными значениями. Например, вы можете захватить указатель устройства игры и установить для него значение null, а затем подождать в обработчике исключений, пока программа не выдаст исключение. Само исключение должно быть разыменованием нулевого указателя, просто выполните свои действия в перенаправленном обработчике EIP, а затем сбросьте исходные значения и продолжите выполнение. Поскольку теперь указатель снова в порядке, он будет выполняться, пока вы снова не установите указатель в нуль. Есть еще много способов использовать это. Вам может потребоваться большая работа, чтобы исправить все исключения, что требует большого скилла. Примера нет, но вы держитесь. Спасибо всем за прочтение статьи, ждите еще постов.