20120817

Direct3D - Часть 0: Вступительная

Как я написал чуток ниже, писать нашу менюшку мы будем на С++. Поэтому в качестве первой части видео мы сделаем простейшее приложение с использованием Direct3D и всё что оно будет уметь - показывать окно, красить его заданным цветом и корректно завершаться.

Ребята, если вы до этого никогда не писали на плюсах (а я и сам почти никогда на них не писал) - не переживайте, он сильно похож на C# - оба имеют синтаксис, очень похожий на их далёкого предка - С.

Ну, тем хардкорщикам, которые до этого писали только на ассемблере, придётся немного попривыкнуть. :D

Его и будем ломать в дальнейшем. :)

[Ссылка] на видеоурок.
[Ссылка] на исходный код + скомпилированную программу.

PS: Всё-таки выложу исходник ещё и тут - прокомментирую:


#include //Подключаем необходимые заголовочные файлы, чтобы оно соображало,
#include //где какие функции, что какие структуры обозначают и всё такое

#pragma comment(lib, "d3d9.lib") //До кучи добавляем библиотечку d3d

LPDIRECT3D9 d3d; //Объявляем глобальную переменную для объекта d3d
LPDIRECT3DDEVICE9 d3ddev; //Такую же, но для устройства d3d

void CleanD3D() //Функция, завершающая работу D3D
{
d3ddev->Release(); //У обоих объектов вызываем функцию "УбитьСебя"
d3d->Release();
}

void RenderFrame() //Функция, рисующая что-нибудь на экране
{
        //Говорим нашему устройству d3d очистить наше окно цветом FF0000 - красным!
d3ddev->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0);
d3ddev->BeginScene(); //Типа начинаем рисовать
d3ddev->EndScene(); //Сразу заканчиваем, так ничего и не нарисовав
d3ddev->Present(0, 0, 0, 0); //Меняем местами буфер на экране и тот, который мы заполнили цветом
}

//Функция, инициализирующая D3D
void InitD3D(HWND hWnd) //hWnd - аргумент, идентификатор нашего окна
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); //Создаём объект d3d, запихиваем в переменную
D3DPRESENT_PARAMETERS d3dpp; //Объявляем переменную со структурой настроек
ZeroMemory(&d3dpp, sizeof(d3dpp)); //Очищаем выделенную под неё память
d3dpp.Windowed = 1; //Говорим, что рисовать будем в окне
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; //В один буфер рисуем, когда первый уже отобразили
        //Создаём устройство d3d, скармливая ему идентификатор нашего окна
        //Результат тоже записываем в глобальную переменную
d3d->CreateDevice(0, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev);
}

//Функция, управляющая нашим окном. Аргументы:
//hWnd - идентификатор окна
//message - сообщение для обработки
//wParam и lParam - дополнения к сообщению
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message) //Смотрим, что за сообщение
{
case WM_DESTROY: //Если "Убить окно"
{
PostQuitMessage(0); //Пишем завещание, говорим что окно умирает
return 0; //Возвращаем 0
}
}
return DefWindowProc(hWnd, message, wParam, lParam); //Иначе - возвращаем значение по умолчанию
}

//Точка входа в программу. Аргументы:
//hInstance - идентификатор процесса в системе
//hPrevInstance - идентификатор процесса-родителя (если есть)
//Оставшиеся два устарели и не используются, так что там всегда 0
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc; //Объявляем переменную под структуру описания нашего окна
ZeroMemory(&wc, sizeof(WNDCLASSEX)); //Очищаем под неё память
wc.cbSize = sizeof(WNDCLASSEX); //Задаём её размер
wc.style = CS_HREDRAW | CS_VREDRAW; //Говорим, как окно будет рисоваться
wc.lpfnWndProc = WindowProc; //Говорим, какая функция будет окном управлять
wc.hInstance = hInstance; //Указываем идентификатор процесса-родителя окна
wc.hCursor = LoadCursor(0, IDC_ARROW); //Использовать будем этот курсор-стрелочку
wc.lpszClassName = L"WindowClass"; //Имя структуры, описывающей окно
RegisterClassEx(&wc); //Регистрируем структуру в системе
HWND hWnd = CreateWindowEx(0, L"WindowClass", L"Test D3D9", 0, 0, 0, 640, 480, 0, 0, hInstance, 0); //Создаём окно с заголовком "Test D3D9" в верхнем левом углу экрана (0:0) размером в 640х480 пикселей, получаем его идентификатор
ShowWindow(hWnd, nCmdShow); //Показываем окно на экране
InitD3D(hWnd); //Инициализируем d3d
MSG msg; //Объявляем переменную под хранение сообщений для окна
while(1) //Делаем бесконечный цикл
{
while(PeekMessage(&msg, 0, 0, 0, 1)) //В нём в цикле получаем сообщения
{
TranslateMessage(&msg); //Переводим их в понятный окну формат
DispatchMessage(&msg); //Отправляем на растерзание процедуре окна
}
if(msg.message == WM_QUIT) //Если получили сообщение к выходу
{
break; //Прерываем цикл
}
RenderFrame(); //Иначе - рисуем при помощи d3d
}
CleanD3D(); //Если цикл прервался - завершаем работу d3d
return msg.wParam; //И выходим
}

20120816

Direct3D: Финишная прямая

Вплотную, уже практически сопя в затылок, подбираемся к раскурочиванию графической составляющей игр!

Завтра будет Видео №0, до его просмотра рекомендую скачать и установить две вещи:

1. [Visual C++ Express] - на момент написания этого поста, 2010 версия.
2. [DirectX SDK] - на момент написания этого поста, версия от июня 2010 года.

На первом пункте особо останавливаться не буду. Скажу только, что без С++ нам пока что не обойтись. Почему? Потому что DirectX, при помощи которого и создаются 99% игр под Windows - это набор интерфейсов ([API]) для работы с графикой, выполненный в виде сборника [COM]-объектов. Страшные слова заставляют написать, о чём же это, в кратце - а о том, что есть вещь под названием [ООП] - Объектно-ориентированное программирование. Это такой подход, при котором любая вещь в программе - переменная, функция или ещё что-то - является объектом.

В детали я вдаваться не очень хочу, т.к. это довольно нудно и не очень нужно для нашей затеи, но вся штука в том, что лучше всего на работу с такой задачей подходит именно С++.

Не бойтесь, для особо терминальных случаев (как мой, к примеру) можно будет всё переписать и на более низкоуровневых языках (скажем, ассемблере ;), равно как и на аналогах (например, C#).

Моя основная задача - продемонстрировать и объяснить принцип, а вот на каком языке реализовывать идею и какими мелками рисовать картину - это уже дело вашего вкуса, я тут не в праве своё мнение навязывать. :)

20120815

Взлом по заявкам - Prince of Persia: Sands of Time - Часть 1

Итак, товарищ с форума [gamehacklab] под именем LogDog попросил меня записать видеоурок по взлому замечательной игры про принца в шароварах. Попросил он несколько опций, так что это будет серия видеоуроков.

Т.к. я не знаю, насколько круто LogDog умеет ломать игр (может круче меня и он просто приколоться решил), то объяснять буду максимально подробно.

[Ссылка] на первую часть.

PS: Если кого-то смутил момент с float / 4 байта целое, попробую объяснить:

Что float, что 4 байта целое занимают в памяти 4 байта памяти. По сути, это просто два разных представления содержимого одного и того же адреса, т.е. компьютер знает и понимает только байты - всякие там 4 байта, 8 байт, double, float - компьютеру до лампочки, так что можно найти 4 байта целое а потом выяснится, что это на самом деле float - с человеческой точки зрения видеть 100.0 понятнее, чем 34576858, ну и наоборот - вместо 0.6+11Е куда понятнее видеть ровно 500, к примеру.

20120813

Структуры! Структуры!

Новый [видеоурок], в котором я вам показываю ещё одну отличную фичу Cheat Engine - возможность наглядно сравнивать структуры. Под классическую музыку!

Ломаю на примере обучающей программы, которую можно найти в папке установки СЕ - под рукой не оказалось игры. Но это не так важно, важно понимание способа взлома (а способ очень мощный, гарантирую!).

20120715

Продолжаем бороться с DMA.

Или встречайте [новый видеоурок]!

Демонстрирую небольшую фишку, когда надо "заморозить" значение, а указатель искать лень или не получается. Ещё, кстати, код отлаживаем в OllyDbg - вдруг кому-то кроме меня интересен этот замечательный отладчик.

Ну и небольшую фишку по ассемблерным скриптам в СЕ тоже покажу.

20120710

Чтобы не скучать.

Чуваки, пока я продолжаю разбираться с D3D, чтобы не было сильно скучно - попробуем сломать что-нибудь эдакое, нестандартное. Мне вот очень хочется углубиться в тему игрового ИИ - поведения врагов, например.

С СЕ мы уже более-менее разобрались, так что отлаживать я буду в основном в Olly - это не страшно, т.к. отладчики все похожие, но мне просто так удобнее.

Может быть, кстати, покажу ещё пару фич СЕ, про которые возможно забыл.

Если что - в комментариях пишем, какую бы опцию хотелось (помимо стандартных бессмертий и всяких бесконечных патронов) - попробую сделать.

20120704

Продолжаем болтать про графику и D3D.

В общем, ребята, как вы могли заметить - мои знания постепенно подходят к концу, так что графику я изучаю, можно сказать, вместе с вами. Что мы уже знаем?

Есть некоторый интерфейс (назовём его набором функций) для отрисовки графики. Командуешь ему команды - он выполняет. Если представить абстрактно, то выглядит это примерно так:


На картинке буковка "А" - это наша программа, "В" - это видеокарта+монитор, а D3D в симпатичном овале выполняет роль сломанного телефона - наша программа не умеет работать с железками, но может об этом попросить D3D.

Одна проблема - на всех одной D3D не хватит, поэтому каждая программа, его использующая, грузит себе (в своё адресное пространство) свою собственную:


Тут, тащемта, 0x40000 - точка входа нашей программы (с неё весь её код начинается), а по адресу 0x70000 (для примера, ибо на самом деле адрес выбирается исходя из настроения Билла Гейтса и фазы Сатурна) грузится наша личная копия D3D.

Так, библиотеку в своё адресное пространство загрузили. А что нужно программе сделать, чтобы нарисовать что-нибудь на экран? Инициализацию! Нужно вызвать специальную штуку, чтобы D3D проснулся и понял, что мы хотим рисовать: В этом нам поможет хитрая функция [CreateDevice], которая после своего выполнения предоставляет нам указатель. Что за указатель, да? Я тоже сначала ни черта не понимал, да и сейчас понимаю плохо. Если очень тупо и так, как это представляю себе я, то возвращают нам указатель на виртуальный объект, представляющий собой нашу видеокарту+монитор. У него есть список функций, примерно так:


Собственно, 0x123 и так далее - это не адреса, а смещения. И получается так - если мы хотим вызывать, скажем, Function 2, рисующую (для примера) прямоугольник, то мы делаем такой финт ушами:

call [pDevice+0x456]

Типа, взяли указатель на наш видеоадаптер, прибавили смещение до нужной нам функции и это дело вызвали. Такая вот хитрожопая конструкция называется таблицей виртуальных функций. И когда мы хотим вызвать ту же EndScene() или BeginScene(), то мы пишем так (C#):

pDevice.EndScene();

А на самом деле происходит так:

call [pDevice+endSceneOffset]

Круто? Круто!

Вспомним теперь, что нам необходимо для рисования. В игре есть цикл, который всё время делает вот так:

BeginScene();
DrawEverything();
EndScene();

Типа, начали рисовать, нарисовали всё, закончили рисовать. И опять начали. И так далее.

Наша задача - вклиниться между DrawEverything() и EndScene(), дорисовать там что-нибудь своё, а затем - вызвать оригинальную EndScene(), чтобы игра так ничего и не поняла. Для этого нам требуется заменить оригинальный вызов EndScene в той самой таблице на наш собственный:

HookedEndScene()
{
   DrawMenu();
   EndScene();
}

Типа, игра лезет за EndScene(), натыкается на нашу подлянку, наша подлянка рисует меню (DrawMenu()), а затем вызывает оригинальную EndScene().

Такая вот штука называется Хук (англ. Hook). Если задуматься, то это по смыслу очень похоже на инъекцию кода.

Собственно, наш план - получить адрес оригинальной EndScene() в адресном пространстве процесса игры (потому что именно в него грузится копия d3d.dll), заменить её на подлянку. Как получить адрес? Берём и создаём новый девайс (типа инициализироваться хотим):

CreateDevice();

Получаем указатель. Прибавляем к указателю смещение до EndScene() (смещения мы знаем, в отличие от указателя) - и вуаля! Можно заменять. Ненужный уже после этого указатель мы уничтожаем.

Собственно, я пока продолжу экспериментировать, а вы комментируйте, если что-то не совсем ясно. Старался объяснять максимально понятно, но я и сам не до конца ещё всё это понимаю, а сделать уже очень хочется. :)

Дописываем наш трейнер на C#. Инъекции кода. Часть 2.

Вторая часть будет в качестве небольшого дополнения к [первой]. Почему небольшой? Да потому, что для динамического выделения памяти нам нужно изменить в коде одну строчку. Нам понадобится новая WinAPI-функция - [VirtualAllocEx]:

В общем-то всё, что она делает - принимает в аргументах хэндл процесса, размер выделяемого куска памяти в байтах, ну и пару флагов, определяющих способ выделения и нужные права. Возвращает она что? Правильно! Адрес выделенного куска, куда уже можно записывать.

Так что мы просто берём и значение переменной caveAddress, хранящей адрес нашего кейва, присваиваем нужный вызов:

caveAddress = (Int32)(VirtualAllocEx(handle,IntPtr.Zero,(uint)(valueon.Length+5),AllocationType.Commit,MemoryProtection.ExecuteReadWrite));


Соответственно, выделить нам нужно память размером с наш скрипт + ещё пять байт для прыжка обратно в код игры (1 байт для jmp и 4 - для адреса). Штучка (Int32) нужна для конвертации в нужный тип данных (по-умолчанию функция возвращает IntPtr), иначе нам будет не посчитать буфер для записи.

Всё, что нам потребуется после этого (когда мы захотим отключить функцию трейнера) - это:

1. Записать старую инструкцию на место (value off -> addressFrom).
2. Очистить выделенную под кейв память при помощи [VirtualFreeEx].

Как пользоваться вторым пунктом (а заодно - что нужно для записи) - узнаём на [pinvoke.net]. На всякий случай скажу, что для очистки нам нужен флаг Decommit.

Дописываем наш трейнер на C#. Инъекции кода. Часть 1.

Один мой зритель на ютубе подметил, что трейнер наш не совсем полноценен. Исправим это недоразумение!

В этом видеоуроке вы узнаете, как можно создавать инъекции кода, используя для этого свободную память игры.

[Ссылка].


Исходный код:

static void MakeCave()
{
   if (pID != 0)
   {
      Console.WriteLine("Process Id: " + pID);
      var handle = OpenProcess(ProcessAccessFlags.All, false, pID);
      if (handle != IntPtr.Zero)
      {
         Console.WriteLine("Process handle: " + handle.ToString());
         var caveAddress = 0x004002A8;                    
         var valueon = new byte[] { 0xD9, 0x9E, 0x5C, 0x43, 0x0F, 0x00 };
         var valueoff = new byte[] { 0xD9, 0x9E, 0x5C, 0x43, 0x0F, 0x00 };
         var addressFrom = 0x00416494;
         var addressTo = addressFrom + valueoff.Length;
         var buffer = new byte[valueoff.Length];
         buffer[0] = 0xE9;                    
         var f = BitConverter.GetBytes(caveAddress-addressFrom-5);                    
         Array.Copy(f, 0, buffer, 1, f.Length);
         for (var i = 5; i < valueoff.Length; i++)
         {
            buffer[i] = 0x90;
         }
         var dummy = new UIntPtr();
         WriteProcessMemory(handle, (IntPtr)addressFrom, buffer, (uint)buffer.Length, out dummy);
         var caveBuffer = new byte[valueon.Length + 5];
         Array.Copy(valueon, caveBuffer, valueon.Length);
         caveBuffer[valueon.Length] = 0xE9;
         var retAddress = BitConverter.GetBytes(addressFrom-caveAddress-valueoff.Length);
         Array.Copy(retAddress, 0, caveBuffer, valueon.Length + 1, retAddress.Length);
         WriteProcessMemory(handle, (IntPtr)caveAddress, caveBuffer, (uint)caveBuffer.Length, out dummy); 
         CloseHandle(handle);
         Console.WriteLine("Handle closed.");
         Console.ReadKey();
      }
   }
}

ВНИМАНИЕ! В коде допущена логическая ошибка - сначала нужно записывать кейв, а только потом - прыжок на него. Ошибка допущена сознательно и исправляется простой перестановкой нескольких строчек, так что это будет домашним заданием.