Я лучше тебя
-
Автор темы
- #1
Салями народ.
Давно не постился на югейме, как настоящий православный грешник.
Давно не постился на югейме, как настоящий православный грешник.
Общая схема на которой мы рассмотрим, как недальновидность кодера чита, может дать разработчику игры нехуевую телегу данных о своем чите.Сегодня мы посмотрим, что может обычный, самый тупой SEH обработчик в связке с возможностями библиотеки dbghelp против твоего говно чита.
- Есть игра.
- Есть твой говно чит.
- В игре есть функция.
- У функции в игре свой SEH обработчик ошибок.
- Твой говно чит хукает эту функцию в игре для своей обработки и извлечения данных для своих анальных моневров.
- Игру обновляют / ты лажаешь в коде. (не важно что именно)
- В твоем коде, который оборачивается в хуке вокруг кода игры возникает ошибка. (неверный оффсет, или еще какая хуйня)
- Обработчик ошибок функции которую ты хукаешь отлавливает эту ошибку.
- Начинается веселье :)
В чем тут хиросима и где ногавсраке?
Начинается все с незамысловатого, обычная ошибка в коде, только вот по традиции всех паблик сорсов, у нехилого большинства читов нету собственных обработчиков для ошибок в функциях, соответственно, ошибка в твоем коде,обработается обработчиком ошибок функции которую ты хукаешь.
То есть - если на пример после обновления игры, в твоем коде какая то ошибка вылетела и котото это заинжектил в игру - игра может это отловить :)
Далее следует незамысловатый код:
Начинается все с незамысловатого, обычная ошибка в коде, только вот по традиции всех паблик сорсов, у нехилого большинства читов нету собственных обработчиков для ошибок в функциях, соответственно, ошибка в твоем коде,обработается обработчиком ошибок функции которую ты хукаешь.
То есть - если на пример после обновления игры, в твоем коде какая то ошибка вылетела и котото это заинжектил в игру - игра может это отловить :)
Далее следует незамысловатый код:
Код:
#define SEH seh_filter(GetExceptionInformation())
//некий игровой класс
class SomeClassInGame
{
public:
//некая игровая функция которую ты нежно хукаешь, думая что никто ж не узнает
int funcForHook( int al, int lpParam)
{
int hReturn = 0;
//обработка, код который ты заменяешь на свой. (чисто для примера)
__try
{
if (al > lpParam)
{
hReturn = al + lpParam;
}
else
{
hReturn = al * lpParam;
}
//условно ошибка которая возникает в твоем коде в случае резкого обновления игры или твоей ошибки
throw new exception;
}
__except (SEH)
{
//to do error
}
return hReturn;
}
};
А теперь сам SEH обработчик:
Код:
int seh_filter(_EXCEPTION_POINTERS* ex)
{
//если флаг контекста потока в котором произошла ошибочка ДЕБУГ РЕГИСТЕР
if (ex->ContextRecord->ContextFlags == CONTEXT_DEBUG_REGISTERS)
{
//ты дебагер тебе бан, лох.
return 0;
}
//для получения модуля в котором произошла ошибка
HMODULE hModule = NULL;
size_t moduleSize = 0;
//берем адресс ошибки в коде, и получаем базовый адресс модуля который выделил
//память под код в котором возникла ошибка
void* lpAddress = (void*)ex->ExceptionRecord->ExceptionAddress;
MEMORY_BASIC_INFORMATION mbi = { 0 };
//Получаем инфо о странице памяти по которой ошибка.
if (VirtualQuery(lpAddress, &mbi, sizeof(mbi)))
{
hModule = (HMODULE)mbi.AllocationBase;
//делаем магическое дрючево))
//если по адресу в памяти возникла ошибка, значит это КОД. по тому что не может быть ошибки по адресу в котором ничего нет
//за вычетом ошибки доступа, когда программа пытается получить доступ к участку памяти который уже освобожден.
//проверяем пе хидеры
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE)
{
//ЭТО ВИДИМЫЙ МОДУЛЬ БЕЗ ЗАТЕРТЫХ ЗАГОЛОВКОВ
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD)(dosHeader)+(dosHeader->e_lfanew));
if (ntHeader)
{
//тапаем размер твоей длл чита.
moduleSize = ntHeader->OptionalHeader.SizeOfImage;
}
}
else
{
//ЭТО СКРЫТЫЙ КОД, ЗАИНЖЕКЧЕННЫЙ МАППИНГОМ, ЛИБО ШЕЛЛКОД.
//ЛИБО ДЛЛ С ЗАТЕРТЫМИ ЗАГОЛОВКАМИ, ЧТО ТАК ЧТО СЯК ОДИН ХУЙ ЭТО ЧИТ.
//ТЕБЕ БАН
}
}
//Берем контекст потока для трасировки фрейма потока.
PCONTEXT ctx = ex->ContextRecord;
BOOL result;
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
STACKFRAME stack;
DWORD64 displacement = 0;
DWORD disp;
char buffer[sizeof(SYMBOL_INFO) + 2000 * sizeof(TCHAR)];
char name[MaxNameLen];
char module[MaxNameLen];
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
memset(&stack, 0, sizeof(STACKFRAME));
#if !defined(_M_AMD64)
stack.AddrPC.Offset = (*ctx).Eip;
stack.AddrPC.Mode = AddrModeFlat;
stack.AddrStack.Offset = (*ctx).Esp;
stack.AddrStack.Mode = AddrModeFlat;
stack.AddrFrame.Offset = (*ctx).Ebp;
stack.AddrFrame.Mode = AddrModeFlat;
#endif
//извлекаем
SymInitialize(process, NULL, TRUE);
for (ULONG frame = 0; ; frame++)
{
#if defined(_M_AMD64)
result = StackWalk64(IMAGE_FILE_MACHINE_AMD64, process, thread, &stack, ctx, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL);
#else
result = StackWalk(IMAGE_FILE_MACHINE_I386, process, thread, &stack, ctx, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL);
#endif
if (!result)
break;
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = 2000;
//фишка dbghelp в том что можно попытатся прочитать
//имя функции в которой возникла ошибка :)))
BOOL SymStatus = SymFromAddr(process, (ULONG)stack.AddrPC.Offset, &displacement, pSymbol);
//Если модуль есть :)
if (hModule != NULL)
{
//берем его имя :)
K32GetModuleBaseNameA(GetCurrentProcess(), hModule, module, MaxNameLen);
if (SymStatus)
{
//если имя функции есть, мы его выведем
DWORD offset = (DWORD)pSymbol->Address;
printf("%s -> %s, %s!0x%0X.\n", module, pSymbol->Name, offset);
}
else
{
//если имя функции нема значит скорей всего твой ссаный чит под пакером, по тому что нечего больше бля там такого быть не может.
}
//теперь твой бан, если размер длл определен вместе с базовым адрессом, мы можем сдампить длл
//даже заинжекченую маппингом прямо из памяти.
if (hModule && moduleSize)
{
BYTE* const buffer = new BYTE[moduleSize];
DWORD dwModuleBase = (DWORD)hModule;
if (!ReadProcessMemory(process, (void*)dwModuleBase, buffer, 0x400, NULL))
{
std::cout << "Error: " << GetLastError() << std::endl;
std::cout << "Cannot read module base" << std::endl;
}
const IMAGE_NT_HEADERS32* pNTHeader = (IMAGE_NT_HEADERS32*)(buffer + ((IMAGE_DOS_HEADER*)buffer)->e_lfanew);
const IMAGE_OPTIONAL_HEADER32* pOptionalHeader = (IMAGE_OPTIONAL_HEADER32*)&pNTHeader->OptionalHeader;
unsigned int i = 0;
IMAGE_SECTION_HEADER* pSecHeader;
unsigned int bufPtr = pOptionalHeader->SizeOfHeaders;
for (pSecHeader = IMAGE_FIRST_SECTION(pNTHeader); i < pNTHeader->FileHeader.NumberOfSections; ++i, ++pSecHeader)
{
pSecHeader->Misc.VirtualSize = pSecHeader->SizeOfRawData;
if (!memcpy(buffer + bufPtr, pSecHeader, sizeof(IMAGE_SECTION_HEADER)))
{
std::cout << "Error: " << GetLastError() << std::endl;
std::cout << "Cannot copy memory" << std::endl;
}
bufPtr += sizeof(IMAGE_SECTION_HEADER);
if (!ReadProcessMemory(process, (void*)(pOptionalHeader->ImageBase + pSecHeader->VirtualAddress), buffer + pSecHeader->PointerToRawData, pSecHeader->SizeOfRawData, NULL))
{
std::cout << "Error: " << GetLastError() << std::endl;
std::cout << "Cannot read headers" << std::endl;
}
}
//дампим тупо в файл чтоб даже в иде открыть можно было
std::ofstream file;
file.open("C:/dump.dll", std::ios_base::binary);
file.write((const char*)buffer, moduleSize);
file.close();
}
}
else
{
//если модуля нэма. ну штош, он наверно скрыт и тебе уже бан.
//потому что ошибка из скрытого модуля явно не к добру, не думаешь?
}
}
SymCleanup(process);
return EXCEPTION_EXECUTE_HANDLER;
}
Зависимости:
Код:
#include <windows.h>
#include <iostream>
#include "dbghelp.h"
#pragma comment(lib,"Dbghelp.lib")
#include <psapi.h>
#pragma comment(lib,"psapi")
А теперь, вот тебе список фактов:
- Когда возникнет ошибка обработчик запишет весь стек действующего потока, в котором эта ошибка возникла
- обработчик попытается записать линию и нити ошибки чтобы сделать снимок и получить: имя файла в котором произошла ошибка, номер строчки кода в котором произошла ошибка, имя функции в которой произошла ошибка, пример: ошибка: { КОД ОШИБКИ } { main.cpp+0x45 АДРЕСС ОШИБКИ } { НОМЕР СТРОЧКИ КОДА }
- если имя файла и номер строки кода не получаются - значит длл не подключена к твоему обработчику кода и она внешняя - считай ЧУЖАЯ.
- если не получается имя функции в которой произошла ошибка длл под пакером.
- если взять адресс ошибки и получить AllocationBase по этому адрессу и он не будет являтся BaseAddress ни одного видимого модуля в публично доступном списке модулей (Ldr) - модуль скрыт.
- если по адресу AllocationBase полученного выше нет ПЕ заголовков - они затерты, либо это шеллкод, либо чит. что в обоих случаях палево :)
Это просто обычный обработчик ошибок, а уже похоже на античит.
Представьте какой вам пиздец, если юзать подобные штуки в полную силу.
И не надо мне телегу загонять типо я ПЕ хидеры потру и все меня не видно, очень даже видно в памяти. Даже драйверный инжектор палится таким тупым методом. Действительно, без хидеров гораздо сложнее сдампить, токо вот никому кроме чита хидеры затирать и не надо, это гарантия того что тебя можно банить, нету хидеров = чит, для игры так точно. А если хидеры на месте и длл скрыта? Если ее в Ldr списке нету, а в памяти есть? тоже палево.
Выводы о том как с этим жить сделаете для себя сами.
Дополнительные материалы для самообразования по теме:
Представьте какой вам пиздец, если юзать подобные штуки в полную силу.
И не надо мне телегу загонять типо я ПЕ хидеры потру и все меня не видно, очень даже видно в памяти. Даже драйверный инжектор палится таким тупым методом. Действительно, без хидеров гораздо сложнее сдампить, токо вот никому кроме чита хидеры затирать и не надо, это гарантия того что тебя можно банить, нету хидеров = чит, для игры так точно. А если хидеры на месте и длл скрыта? Если ее в Ldr списке нету, а в памяти есть? тоже палево.
Выводы о том как с этим жить сделаете для себя сами.
Дополнительные материалы для самообразования по теме:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.