💪 Похищаем CheatEngine на C++ с нуля. Часть 0 - Введение.

🌐 Введение

Данной статьей я хочу продемонстрировать процесс погружения в неизвестную сферу, методы и средства для сбора информации. Чтобы раз и навсегда закрыть вопрос какие материалы “почитать”, ведь, действуя по гайду “я повторил, но ничего не понял”.

Что именно будем делать? Попробуем без особых знаний залезть в исходники софта для отладки и модификации кода, под названием Cheat Engine. Он создавался годами, а наша задача — апроприировать эти знания за короткий промежуток времени!

👤 Что такое Cheat Engine?

Cheat Engine — это мощный инструмент для реверс-инжиниринга и модификации кода. Хотя он позиционируется как “Чит Движок”, он фактически способен конкурировать с любыми дебаггерами по ряду причин:

  • 🔒 Оснащён собственным драйвером, который даёт более высокий уровень прав и скрытность.
  • 🕹 Обладает встроенным гипервизором, который можно загрузить прямо в рантайме ОС.
  • 🌟 Бесплатный и открытый исходный код делает его удобным для изучения.

💪 Зачем писать свой Cheat Engine?

Мы поняли, что это классная штука, она бесплатная, открытая и она работает. Так зачем же его писать самим?!Главное - 🎓 Это сложная и интересная задача. Ну и я ни на что не намекаю, но …😉

💪 Похищаем CheatEngine на C++ с нуля. Часть 0 - Введение.

Не сложно догадаться, что завести такой пет-проект в портфолио будет не плохо.

🔄 План работы

Чтобы создать минимально рабочий прототип, нам нужно реализовать ключевые функции:

  • 🔐 Подключение к процессу.
  • 🤖 Чтение и сканирование памяти.
  • 🛠 Редактирование памяти.
  • 🔄 Таблица с найденными адресами.
  • 📝 Дизассемблирование кода.
  • 🏢 Отображение списка загруженных библиотек.
  • 🌧 Отладка (брейки, стек вызовов, регистры).

🔍 Исследуем исходники Cheat Engine

Открываем репозиторий Cheat Engine и видим несколько важных директорий:

💪 Похищаем CheatEngine на C++ с нуля. Часть 0 - Введение.
  • Cheat Engine — основной код.
  • DBKKernel — проект драйвера.
  • DBVM — гипервизор.
  • lua — движок для скриптов.

Так как драйвер и гипервизор мы пока трогать не будем, откроем основной проект.

😅 Тут нас ждёт сюрприз: Cheat Engine написан на Паскале!

  • .lfm — GUI формы.
  • .pas — исходники с функциями. 🔖 (они-то нам и нужны)

🌟 Разбираемся с кодом

Заглянув в основной проект, можно заметить, что там ку-у-у-у-ча файлов, но пугаться не стоит, так как главных всего-то ничего! А именно…

🔄 ProcessList.pas Этот файл нужен для сбора и хранения информации о процессах.

procedure GetProcessList(ProcessList: TStrings; NoPID: boolean=false; noProcessInfo: boolean=false);

Перегрузка процедуры для управления выводом информации о процессах.

💪 Похищаем CheatEngine на C++ с нуля. Часть 0 - Введение.
💪 Похищаем CheatEngine на C++ с нуля. Часть 0 - Введение.
💪 Похищаем CheatEngine на C++ с нуля. Часть 0 - Введение.

Чтобы продвинуться дальше, просто забиваем в поиск по файлам название функции GetProcessList, это приведет нас к следующему файлу и еще одной интересной функции…

🔄 MainUnit.pas (Это главный файл с логикой для GUI форм) Здесь вызывается Open_Process, который используется во всех интерфейсах дебаггера (Kernel, DBVM).

🔄 CEFuncProc.pas Тут находится реализация Open_Process.

🔄 NewKernelHandler.pas Содержит ключевые функции, такие как:

function ReadProcessMemory(...);function WriteProcessMemory(...);

🔄 Disassembler.pas Файл на 16 000 строк, отвечающий за дизассемблирование кода.

🚀 Итоги

Теперь мы знаем как: ✅ Получать список процессов. ✅ Открывать хендл к процессу. ✅ Читать и записывать память.

Следующий шаг — разобраться с отладчиком и дизассемблером! 💪

Функции отладчика находятся все в том же NewKernelHandler.pas, они так же представлены перегрузками для разных интейфейсов: winapi, driver, server и т.д. Но в данном случае мы пока обратим внимание на winapi и уже после будет шаг за шагом разбирать другие методы работы.

Ключевыми будут 👇

// Получение адреса функции GetThreadContext из библиотеки WindowsKernel и присваивание его переменной GetThreadContext GetThreadContext:=GetProcAddress(WindowsKernel,'GetThreadContext');

// Получение адреса функции SetThreadContext из библиотеки WindowsKernel и присваивание его переменной SetThreadContext SetThreadContext:=GetProcAddress(WindowsKernel,'SetThreadContext');

🤏 Именно через SetThreadContext мы и будем устанавливать аппаратные точки останова, изменяя регистр DRx (Debug Registers).

🔹 Как установить хардверный бряк через SetThreadContext?

  • 1 Использовать GetThreadContext, чтобы получить текущий контекст потока.
  • 2 Изменить один из DR0–DR3 (адрес бряка).
  • 3 Настроить DR7 для активации бряка.
  • 4 Применить изменения через SetThreadContext.

🔹 Пример кода

CONTEXT ctx; ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

GetThreadContext(hThread,&ctx);

ctx.Dr0 = targetAddress;// Адрес для бряка

ctx.Dr7 |=1;// Включаем бряк

SetThreadContext(hThread,&ctx);

📌 Важно: бряк привязан к конкретному потоку, а не всему процессу.

👁👁👁 Там же видим

// 🔍 Получение адреса функции Wow64GetThreadContext из библиотеки WindowsKernel и присваивание его переменной Wow64GetThreadContext Wow64GetThreadContext:=GetProcAddress(WindowsKernel,'Wow64GetThreadContext');

// 🔍 Получение адреса функции Wow64SetThreadContext из библиотеки WindowsKernel и присваивание его переменной Wow64SetThreadContext Wow64SetThreadContext:=GetProcAddress(WindowsKernel,'Wow64SetThreadContext');

SuspendThread:=GetProcAddress(WindowsKernel,'SuspendThread');// ⏸ Получение адреса функции SuspendThread из библиотеки WindowsKernel, которая используется для приостановки выполнения потока

ResumeThread:=GetProcAddress(WindowsKernel,'ResumeThread');// ▶ Получение адреса функции ResumeThread из библиотеки WindowsKernel, которая используется для возобновления выполнения ранее приостановленного потока

WaitForDebugEvent:=GetProcAddress(WindowsKernel,'WaitForDebugEvent');// ⏳ Получение адреса функции WaitForDebugEvent из библиотеки WindowsKernel, которая используется для ожидания события отладки в процессе или потоке

ContinueDebugEvent:=GetProcAddress(WindowsKernel,'ContinueDebugEvent');// 🔄 Получение адреса функции ContinueDebugEvent из библиотеки WindowsKernel, которая используется для продолжения выполнения после обработки события отладки

DebugActiveProcess:=GetProcAddress(WindowsKernel,'DebugActiveProcess');// 🛠 Получение адреса функции DebugActiveProcess из библиотеки WindowsKernel, которая используется для начала отладки процесса по его идентификатору

Здесь же видим непонятный WindowsKernel, пробуем поискать в файле и находим:

WindowsKernel: Thandle;// 🏗 Переменная, хранящая дескриптор ядра операционной системы Windows

Но это только объявление, пробуем прощелкать далее и находим определение:

WindowsKernel:=LoadLibrary('Kernel32.dll');// 📦 Попытка загрузить библиотеку ядра Windows (Kernel32.dll) и сохранение дескриптора в переменную WindowsKernel.// ❌ Если библиотека не найдена, то WindowsKernel будет равен 0.

Этого достаточно для базовой работы с процессами. Остается поглядеть, что там в Disassembler’е.Как мы помним, там 16 000 строк кода, и все, что они делают, так это проверяют каждый байт на константное значение.Если совпадает, то устанавливают строковую мнемонику (ADD, MOV и др.).

Рассмотрим поближе. У нас есть базовый объект дизассемблера, defaultDisassebler:

defaultDisassembler:=TDisassembler.create;// 🛠 Создаем объект по умолчанию и присваиваем его глобальной переменной.

Который инициализируется методом create в классе TDisassembler,далее на участок памяти вызывается функция disassemble, которая возвращает строку.Начинается функция на 1624 строке, а заканчивается на 15710 строке.

Как уже выше упомянуто, почти все эти строки занимает switch,который проверяет байты и отдает назад название инструкции.

case memory[0] of //opcode

$00 : begin

//🏹🏹

if (aggressivealignment and (((offset) and $f)=0) and (memory[1]<>0) ) or ((memory[1]=$55) and (memory[2]=$89) and (memory[3]=$e5)) then

begin

description:='Filler'; lastdisassembledata.opcode:='db'; LastDisassembleData.parameters:=inttohex(memory[0],2);

end

else

begin

description:='Add';

//🏹🏹

lastdisassembledata.opcode:='add';

lastdisassembledata.parameters:=modrm(memory,prefix2,1,2,last)+r8(memory[1]); inc(offset,last-1);

end;

end;

$01 : begin....

Думаю, на этом можно закончить введение, более подробно рассмотрим каждый из методов уже на практике, когда начнем писать свой mega-omega чит-движок с плюшками.

На этом откланяюсь, а все претензии и пожелания можно писать сюда 👉 https://t.me/osiechan/52, здесь же можно скачать исходные файлы с комментариями на каждой строке (да-да, даже на 16 000 строк) и pdf статьи.

А прокачать свой навыки чито-строителя можно на бесплатном, открытом курсе по созданию бота для мморпг 👉 https://t.me/osiechan/41.

Спасибо за внимание :з.

2
Начать дискуссию