20121126

Скоро новый урок, а пока:

Я просто не мог удержаться и не запостить это:


20121124

D3D: Пишем DLL-инжектор, но уже на C++

[Ссылочка] на урок. Разбираем код DLL-инжектора на C++, который я сваял из тестового D3D-приложения.

А [вот] ссылочка на весь проект, выложенная на [GitHub]. Комментарии на английском. Проект будет пилиться вместе с записью уроков, так что можно его считать тестовой площадкой. Для компиляции понадобится MSVS, я использую 2012 Express.

Сейчас буду записывать следующий урок, для затравки - читаем [это] и [это].

20121103

D3D: Пишем DLL-инжектор

[Ссылочка] на урок.
[Ссылочка] на компилятор (версия demo для win x86).

А вот полный код:


OpenLibrary(0, "kernel32.dll")
OpenLibrary(1, "user32.dll")

#PROCESS_ALL_ACCESS = $1F0FFF

#MEM_COMMIT = $1000
#MEM_RESERVE = $2000
#PAGE_READWRITE = $4
Text$ = "test.dll"
dllName = @Text$
dllNameSize = 8
loadLibAddr = GetFunction(0, "LoadLibraryA")
Debug(loadLibAddr)

hProcess = RunProgram("d:\keng_loader\d3d9_lesson0_keng", "", GetCurrentDirectory(), #PB_Program_Open) 

pID = ProgramID(hProcess)

pHandle = CallFunction(0, "OpenProcess", #PROCESS_ALL_ACCESS, 0, pID)

allocAddr = CallFunction(0, "VirtualAllocEx", pHandle, 0, dllNameSize, #MEM_COMMIT+#MEM_RESERVE, #PAGE_READWRITE)
Debug(allocAddr)
CallFunction(0, "WriteProcessMemory", pHandle, allocAddr, dllName, dllNameSize, 0)
CallFunction(0, "CreateRemoteThread", pHandle, 0, 0, loadLibAddr, allocAddr, 0, 0)
CallFunction(0, "CloseHandle", pHandle)

CloseLibrary(0)

CloseLibrary(1)

Почему на бейсике? Потому что моя задача - объяснить алгоритм работы, а реализация на конкретном языке - дело вкуса. Кому-то ассемблер нравится, кому-то Си, кому-то - C# или Delphi. Не переживайте, в видео всё разжёвано.

Монетизация

...если это можно так назвать. :)

Подумал тут - а может, делать трейнеры на заказ? Скажем, символичные 10 центов за опцию. 10 опций - доллар, 12 опций - доллар 20 центов. Ну и бесплатные обновления, если в следующих версиях игр что-то перестанет работать.

Обсуждения и другие варианты - в комментарии.

20121031



На картинке - наглядная демонстрация, как выглядит Virtual Method Table на более низком уровне. Первая строчка - закомментированный вызов той же самой функции, но при помощи макроса, следующие три - уже без макроса.

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

Если по-человечески, то выглядеть это будет как-то вот так:

pd3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

А вот чтобы выковырять адрес функции из pd3d, нам (или сишному компилятору) нужно сделать такой вот финт ушами, как на картинке выше.

PS: Если вы вдруг читаете заголовочные файлы D3D перед сном, то наверняка заметили, что в 99% функций первым аргументом идёт или D3DObject или D3DDevice. Смекаете, а? Можно перехватить любую функцию и получить доступ к актуальному на данный момент объекту\устройству.
Я тут начитался всяких крутых форумов и вот, что получилось в процессе экспериментирования:


Это, товарищи, обыкновенный Prince of Persia: Sands of Time, только вот ему было сказано не заполнять полигоны текстурами, а оставлять только сетку. Wireframe mode, типа.

PS: Я не ожидал, что wallhack и прочие подобные читы делаются настолько просто, пусть и в довольно сыром виде. Вот и будет тема следующих уроков, когда загрузчик для меню допишем. :)

20121030

Если вдруг кому интересно, почему в GetDevice9Methods() идёт вот такое смещение:

present9 = vtablePtr[17] - (DWORD)hD3D9;

То вот картинка:


Там 18*4 (4 - размер DWORD, потому что это таблица указателей), отнимаем один адрес, т.к. это адрес самой таблицы. Так-то.

D3D: Новый алгоритм и работа со шрифтами.

Да-да, видеоурок с подробными объяснениями, но достаточно плохимм звуком. Выложу тут ссылку (видео будет доступно как на youtube, так и на форуме gamehacklab.ru), как только оно обработается.

[Ссылка] на урок.

Собственно, полный исходный код из видео:


#include
#include
#pragma comment(lib,"d3dx9.lib")

typedef IDirect3D9* (__stdcall *DIRECT3DCREATE9)(unsigned int);
typedef long (__stdcall *PRESENT9)(IDirect3DDevice9* self, const RECT*, const RECT*, HWND, void*);

PRESENT9 g_D3D9_Present = 0;
BYTE g_codeFragment_p9[5] = {0, 0, 0, 0, 0};
BYTE g_jmp_p9[5] = {0, 0, 0, 0, 0};
DWORD g_savedProtection_p9 = 0;
DWORD present9 = 0;
bool indicator = 0;
D3DRECT rec = {10, 10, 120, 30};
ID3DXFont *m_font = 0;
RECT fontRect = {10, 15, 120, 120};
D3DCOLOR bkgColor = 0;
D3DCOLOR fontColor = 0;

void DrawIndicator(void* self)
{
        IDirect3DDevice9* dev = (IDirect3DDevice9*)self;
        dev->BeginScene();
D3DXCreateFont(dev, 12, 0, FW_BOLD, 0, 0, 1, 0, 0, 0 | FF_DONTCARE, TEXT("Arial"), &m_font);
if(indicator)
{
bkgColor = D3DCOLOR_XRGB(0, 0, 255);
fontColor = D3DCOLOR_XRGB(0, 255, 255);
}
else
{
bkgColor = D3DCOLOR_XRGB(255, 0, 0);
fontColor = D3DCOLOR_XRGB(255, 0, 0);
}
        dev->Clear(1, &rec, D3DCLEAR_TARGET, bkgColor, 1.0f, 0);
m_font->DrawText(0, "keng.gamehacklab.ru", -1, &fontRect, 0, fontColor);
        dev->EndScene();
}

void GetDevice9Methods()
{
HWND hWnd = CreateWindowA("STATIC","dummy", 0, 0, 0, 0, 0, 0, 0, 0, 0);
HMODULE hD3D9 = LoadLibrary("d3d9");
DIRECT3DCREATE9 Direct3DCreate9 = (DIRECT3DCREATE9)GetProcAddress(hD3D9, "Direct3DCreate9");
IDirect3D9* d3d = Direct3DCreate9(D3D_SDK_VERSION);
    D3DDISPLAYMODE d3ddm;
    d3d->GetAdapterDisplayMode(0, &d3ddm);
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = 1;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = d3ddm.Format;
IDirect3DDevice9* d3dDevice = 0;
    d3d->CreateDevice(0, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3dDevice);
DWORD* vtablePtr = (DWORD*)(*((DWORD*)d3dDevice));
present9 = vtablePtr[17] - (DWORD)hD3D9;
d3dDevice->Release();
d3d->Release();
FreeLibrary(hD3D9);
CloseHandle(hWnd);
}

long __stdcall HookedPresent9(IDirect3DDevice9* self, const RECT* src, const RECT* dest, HWND hWnd, void* unused)
{
BYTE* codeDest = (BYTE*)g_D3D9_Present;
codeDest[0] = g_codeFragment_p9[0];
*((DWORD*)(codeDest + 1)) = *((DWORD*)(g_codeFragment_p9 + 1));
DrawIndicator(self);
DWORD res = g_D3D9_Present(self, src, dest, hWnd, unused);
codeDest[0] = g_jmp_p9[0];
*((DWORD*)(codeDest + 1)) = *((DWORD*)(g_jmp_p9 + 1));
return res;
}

void HookDevice9Methods()
{
HMODULE hD3D9 = GetModuleHandle("d3d9.dll");
g_D3D9_Present = (PRESENT9)((DWORD)hD3D9 + present9);
g_jmp_p9[0] = 0xE9;
DWORD addr = (DWORD)HookedPresent9 - (DWORD)g_D3D9_Present - 5;
memcpy(g_jmp_p9 + 1, &addr, sizeof(DWORD));
memcpy(g_codeFragment_p9, g_D3D9_Present, 5);
VirtualProtect(g_D3D9_Present, 8, PAGE_EXECUTE_READWRITE, &g_savedProtection_p9);
memcpy(g_D3D9_Present, g_jmp_p9, 5);
}

DWORD __stdcall TF(void* lpParam)
{
GetDevice9Methods();
HookDevice9Methods();
return 0;
}

DWORD __stdcall KeyboardHook(void* lpParam)
{
        while(1)
        {
                if(GetAsyncKeyState(VK_F1))  
                {
                        indicator = !indicator;
                        Beep(500,200);
                }
                Sleep(100);
        }    
        return 0;
}

int __stdcall DllMain(HINSTANCE hInst, DWORD  ul_reason_for_call, void* lpReserved)
{
        switch (ul_reason_for_call)  
        {
        case DLL_PROCESS_ATTACH:
CreateThread(0, 0, &TF, 0, 0, 0);
CreateThread(0, 0, &KeyboardHook, 0, 0, 0);
        }
        return 1;
}

Изменения с предыдущей версии:
-Новый алгоритм перехвата, похож на MS Detours - работает при помощи инъекции кода.
-Поддержка (какая-никакая) шрифтов.

В следующем видео будем свой инжектор для этого дела писать. :)

20121029

D3D: Новости

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

Сейчас курочу шрифты и загрузчик, ждите.

Маленькая поправка к коду D3D-хука

Я тут нашёл свои старые записи и выяснилось, что вот это место:


while(1)
        {
                Sleep(100);
                HookVTableFunction((PDWORD*)npDevice, (PBYTE)hkReset, 16);
                HookVTableFunction((PDWORD*)npDevice, (PBYTE)hkPresent, 17);
        }  

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

Я это к тому, что если вдруг вы прочитали код и полностью в него вникли, а не просто ктрлц+ктрлв. :D

Пока что работаю над шрифтами, они и будут темой следующего видео.

20121028

Продолжаем ломать Prince of Persia: Sands of Time

Вроде как, более-менее починил звук на ноутбуке, так что попробую записать следующий урок. На повестке дня - One Hit Kill в PoP:SoT. Да, для самых постоянных читателей задание простенькое, но ведь это взлом по заявкам.

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

Ещё могу на C++, а могу что-нибудь доделать в C#. Отзывы, пожелания, предложения - в комментарии к этой записи.

Параллельно буду углубляться дальше в D3D, план примерно такой:

-Научимся рисовать буквы (реализуем поддержку шрифтов).
-Напишем загрузчик, чтобы не пользоваться каждый раз Cheat Engine.
-Прикрутим основные функции трейнера.

Как вы уже правильно догадались, вся кухня будет происходить именно в DLL, GUI будет выступать только в качестве загрузчика DLL в память игры. Соотсветственно, все функции для взлома будут лежать именно в DLL.

Почему всё это на C++? Потому что D3D - это интерфейс, а в ассемблере и просто C надо будет попотеть, чтобы сделать это так же просто, как в C++. Почему не C#? Потому что C# слишком абстрактен для достаточно низкоуровневых вещей и работе с указателями, но и там тоже можно всё это сделать.

Direct3D - Часть 1: Исходный код и его разбор

Ребята, всем привет!

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

И вот, собственно, следующая часть по работе с графикой, после прочтения которой у нас на руках будут неиллюзорные результаты. Я буду много повторяться - не обращайте внимание.

Почему статья, а не видеоурок? Потому что в основном я буду комментировать и пояснять код, в видео это так легко не сделаешь, зато потом будет видео с результатами.

Итак, что нам понадобится:

1. [Microsoft Visual Studio]. Express, ибо бесплатная, 2010 или 2012 - без разницы. Я использую 2012. Если используете 2012 - качайте версию для desktop-разработки, ага.
2. [DirectX SDK], на момент написания статьи - версия за июль 2010-го. Собственно, набор разработки (Lego) для тех, кто хочет что-нибудь рисовать под виндой.

!!!ВНИМАНИЕ!!!

Сначала ставим студию, потом - SDK!

!!!ВНИМАНИЕ!!!

Открываем студию, получаем вот такое окошко:


Видим манящую кнопку "New Project...", или же прожимаем Ctrl+Shift+N. В появившемся окне выбираем пустой проект на плюсах, внизу вбиваем ему какое-нибудь имя:


Больше ничего не трогаем и прожимаем Next, пока нам не покажут пустое окно студии. Всё верно, кода в нём нет. Сбоку находим "Solution Explorer", жмём на нём мышью, затем - правой кнопкой на "Source Files" и выбираем пункт "Add - New Item...":


Внизу обзываем его "main.cpp", жмём "Add", откроется пустое окно кода. Копируем туда вот это:


#include

typedef IDirect3D9* (STDMETHODCALLTYPE *DIRECT3DCREATE9)(UINT);
typedef HRESULT(WINAPI* tPresent)(LPDIRECT3DDEVICE9 pDevice, CONST RECT*, CONST RECT*, HWND, LPVOID);
typedef HRESULT(WINAPI* tReset)(LPDIRECT3DDEVICE9 pDevice, D3DPRESENT_PARAMETERS* pPresentationParameters);
typedef DWORD   (STDMETHODCALLTYPE *GETPROCESSHEAPS)(DWORD, PHANDLE);

static DWORD vtableFrag9[] = { 0, 0, 0, 0 };
static DWORD* presentPtr = 0;
static DWORD* resetPtr = 0;
static DWORD offsetPresent = 0;
static DWORD offsetReset = 0;
static tPresent g_D3D9_Present = 0;
static tReset g_D3D9_Reset = 0;
LPDIRECT3DDEVICE9 npDevice;

bool indicator = 0;

void DrawIndicator(LPVOID self)
{
        IDirect3DDevice9* dev = (IDirect3DDevice9*)self;
        dev->BeginScene();
        D3DRECT rec = { 10, 10, 30, 30 };
        D3DCOLOR color = 0;
        if(indicator)
        {
                color = D3DCOLOR_XRGB(0, 255, 0);
        }
        else
        {
                color = D3DCOLOR_XRGB(255, 0, 0);
        }
        dev->Clear(1, &rec, D3DCLEAR_TARGET, color, 1.0f, 0);
        dev->EndScene();
}

HRESULT WINAPI hkPresent(LPDIRECT3DDEVICE9 pDevice, CONST RECT* src, CONST RECT* dest, HWND hWnd, LPVOID unused)
{    
        while(!npDevice)
        {
        npDevice = pDevice;
        }
        DrawIndicator(pDevice);
        return g_D3D9_Present(pDevice, src, dest, hWnd, unused);
}

HRESULT WINAPI hkReset(LPDIRECT3DDEVICE9 pDevice, D3DPRESENT_PARAMETERS* pPresentationParameters)
{
        return g_D3D9_Reset(pDevice, pPresentationParameters);
}

BOOL SearchHeap(HANDLE heap)
{
        int vtableLenBytes = sizeof(DWORD)*4;
        PROCESS_HEAP_ENTRY mem;
        mem.lpData = 0;
        while(HeapWalk(heap, &mem))
        {
                if(mem.wFlags == PROCESS_HEAP_UNCOMMITTED_RANGE) continue;
                DWORD* p = (DWORD*)mem.lpData;
                for(int i = 0; i < (int)(mem.cbData/sizeof(DWORD)); i++)
                {
                        if(memcmp(p, vtableFrag9, vtableLenBytes) == 0)
                        {
                                presentPtr = p + 11;
                                resetPtr = p + 10;
                                offsetPresent = *presentPtr;
                                offsetReset = *resetPtr;
                                g_D3D9_Present = (tPresent)((DWORD*)offsetPresent);
                                g_D3D9_Reset = (tReset)((DWORD*)offsetReset);
                                break;
                        }
                        p++;
                }
                if(presentPtr != 0) break;
        }
        return(presentPtr != 0);
}

void CheckAndHookPresent9()
{
        if(presentPtr != 0 && (*presentPtr) == (DWORD)hkPresent) return;
        HANDLE heap = 0;
        HMODULE hKern = GetModuleHandleA("kernel32.dll");    
        GETPROCESSHEAPS getProcessHeaps = (GETPROCESSHEAPS)GetProcAddress(hKern, "GetProcessHeaps");
        if(getProcessHeaps != 0)
        {
                HANDLE heaps[1000];
                int numHeaps = (getProcessHeaps)(1000, heaps);
                for(int k = 0; k < numHeaps; k++)
                {
                        heap = heaps[k];
                        if(SearchHeap(heap)) break;
                }
        }
        else
        {
                heap = GetProcessHeap();
                SearchHeap(heap);
        }
        HeapLock(heap);
        if(presentPtr != 0)
        {
                (*presentPtr) = (DWORD)hkPresent;            
        }
        if(resetPtr != 0)
        {
                (*resetPtr) = (DWORD)hkReset;
        }
        HeapUnlock(heap);
}

void CopyVMT9(DWORD* vtableFrag)
{    
        HWND hWnd = CreateWindowA("STATIC","dummy", 0, 0, 0, 0, 0, 0, 0, 0, 0);
        HMODULE hD3D9 = GetModuleHandleA("d3d9");
        DIRECT3DCREATE9 Direct3DCreate9 = (DIRECT3DCREATE9)GetProcAddress(hD3D9, "Direct3DCreate9");
        IDirect3D9* d3d = Direct3DCreate9(D3D_SDK_VERSION);
        D3DDISPLAYMODE d3ddm;
        d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);
        D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = 1;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
        IDirect3DDevice9* d3dDevice = 0;
        d3d->CreateDevice(0, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3dDevice);  
        DWORD* vtablePtr = (DWORD*)(*((DWORD*)d3dDevice));
        for(int i = 0; i < 4; i++)
        {
                vtableFrag[i] = vtablePtr[i + 6];
        }
        d3dDevice->Release();
        d3d->Release();
        DestroyWindow(hWnd);
}

PBYTE HookVTableFunction(PDWORD* dwVTable, PBYTE dwHook, INT Index)
{
    DWORD dwOld = 0;
    VirtualProtect((void*)((*dwVTable) + (Index*4)), 4, PAGE_EXECUTE_READWRITE, &dwOld);
    PBYTE pOrig = ((PBYTE)(*dwVTable)[Index]);
    (*dwVTable)[Index] = (DWORD)dwHook;
    VirtualProtect((void*)((*dwVTable) + (Index*4)), 4, dwOld, &dwOld);
    return pOrig;
}

bool hooked = 0;
DWORD WINAPI TF(LPVOID lpParam)
{
        CopyVMT9(vtableFrag9);
        CheckAndHookPresent9();
        while(!npDevice && !hooked)
        {
                Sleep(50);
        }
        hooked = !hooked;
        while(1)
        {
                Sleep(100);
                HookVTableFunction((PDWORD*)npDevice, (PBYTE)hkReset, 16);
                HookVTableFunction((PDWORD*)npDevice, (PBYTE)hkPresent, 17);
        }  
        return 0;
}

DWORD WINAPI KeyboardHook(LPVOID lpParam)
{
        while(1)
        {
                if(GetAsyncKeyState(VK_F1))  
                {
                        indicator = !indicator;
                        Beep(500,200);
                }
                Sleep(100);
        }    
        return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
        switch (ul_reason_for_call)  
        {
        case DLL_PROCESS_ATTACH:
                {
                        CreateThread(0, 0, &TF, 0, 0, 0);
                        CreateThread(0, 0, &KeyboardHook, 0, 0, 0);
                }
        case DLL_PROCESS_DETACH:
                break;
        }
        return 1;
}

О, совсем забыл. Нам надо указать, что на выходе мы хотим не exe-файл, а dll. Меню "Project - Properties", вкладка "General", справа ищем "Configuration type: Application (.exe)", радостно меняем его на "Dynamic Library (.dll)", жмём OK.

Попробуем выяснить, собирается ли оно. Меню "Build - Build Solution". После этого внизу должно появиться что-то такое:


Значит, проект собрался и всё отлично. Если студия ругается, что не может найти заголовочный файл d3d9.h, то идём в свойства проекта, находим там вкладку "VC++ Directories", добавляем в "Include Directories" и "Library Directories" соответствующие папки из DirectX SDK, который будет лежать где-то в Program Files на диске цэ.

Итак, вроде всё хорошо, вернёмся к коду. Смотрим на самый главный метод - точку входа:


BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
        switch (ul_reason_for_call)  
        {
        case DLL_PROCESS_ATTACH:
                {
                        CreateThread(0, 0, &TF, 0, 0, 0);
                        CreateThread(0, 0, &KeyboardHook, 0, 0, 0);
                }
        case DLL_PROCESS_DETACH:
                break;
        }
        return 1;
}

Так как мы пишем DLL, то и точка входа у нас называется DllMain, умеет возвращать булевую переменную (ложь или правда), ну а APIENTRY - насколько я помню, соглашение о вызове для Win32-приложений. Лучше этим не забивать голову, да. В аргументах нас интересует только один параметр, ul_reason_for_call, который определяет, что с библиотекой случилось.

В кратце, чем отличается dll от exe? Exe умеет запускаться сам, а вот dll должна быть пропихнута к уже запущенному процессу. Проверкой этого, собственно, метод и занимается - если ul_reason_for_call == DLL_PROCESS_ATTACH (присоединились к процессу), то запускаем два потока, а если DETACH (отсоединились от процесса), то ничего не делаем. После выполнения всегда возвращаем 1, то есть правду.

Если не в курсе про switch-case - почитайте, прикольная штука, я сейчас останавливаться подробно не буду.

Дык вот. Присоединились к процессу, запустили два потока. Что они, собственно, делают? Рассмотрим сначала функцию для потока KeyboardHook, потому что она покороче и попроще:


DWORD WINAPI KeyboardHook(LPVOID lpParam)
{
        while(1)
        {
                if(GetAsyncKeyState(VK_F1))  
                {
                        indicator = !indicator;
                        Beep(500,200);
                }
                Sleep(100);
        }    
        return 0;
}

Всё просто до безобразия. Крутимся в бесконечном цикле, в котором:

1. Если нажата F1, то
2. Меняем значение переменной indicator на противоположное (0\1)
3. Пиликаем динамиком (Beep)
4. Ждём 100 мсек

Если вдруг цикл прерывается - возвращаем 0.

...Ничего не напоминает? Правильно! Эта штука включает и выключает нашу менюшку.

Что же у нас крутится во втором потоке? Давайте посмотрим:


DWORD WINAPI TF(LPVOID lpParam)
{
        CopyVMT9(vtableFrag9);
        CheckAndHookPresent9();
        while(!npDevice && !hooked)
        {
                Sleep(50);
        }
        hooked = !hooked;
        while(1)
        {
                Sleep(100);
                HookVTableFunction((PDWORD*)npDevice, (PBYTE)hkReset, 16);
                HookVTableFunction((PDWORD*)npDevice, (PBYTE)hkPresent, 17);
        }  
        return 0;
}

Ого, а вот тут уже интересно! Метод CopyVMT9, судя по названию, что-то делает с VMT для 9-й версии библиотеки Direct3D, а именно - вот код:


void CopyVMT9(DWORD* vtableFrag)
{    
        HWND hWnd = CreateWindowA("STATIC","dummy", 0, 0, 0, 0, 0, 0, 0, 0, 0);
        HMODULE hD3D9 = GetModuleHandleA("d3d9");
        DIRECT3DCREATE9 Direct3DCreate9 = (DIRECT3DCREATE9)GetProcAddress(hD3D9, "Direct3DCreate9");
        IDirect3D9* d3d = Direct3DCreate9(D3D_SDK_VERSION);
        D3DDISPLAYMODE d3ddm;
        d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);
        D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = 1;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
        IDirect3DDevice9* d3dDevice = 0;
        d3d->CreateDevice(0, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3dDevice);  
        DWORD* vtablePtr = (DWORD*)(*((DWORD*)d3dDevice));
        for(int i = 0; i < 4; i++)
        {
                vtableFrag[i] = vtablePtr[i + 6];
        }
        d3dDevice->Release();
        d3d->Release();
        DestroyWindow(hWnd);
}

Если попроще, то он создаёт пустое и невидимое окно, получает его хэндл, создаёт для него новый D3D-объект и копирует кусочек VMT этого объекта. Затем всё уничтожает, но сохраняет кусочек VMT.

Фишка тут в том, что таблицы методов (VMT) получаются для разных D3D-объектов, ибо одновременно можно использовать только один (а его создаёт и использует игра), но вот по кусочку таблицы можно найти уже созданную ранее таблицу, которая и используется игрой. По механизму действия это похоже на поиск по сигнатуре.

В общем, после выполнения этого метода у нас есть "зацепка" - сигнатура VMT для D3D-объекта, который используется игрой в данный момент. Идём дальше. Что там дальше? Ага, CheckAndHookPresent9. Вот он:


void CheckAndHookPresent9()
{
        if(presentPtr != 0 && (*presentPtr) == (DWORD)hkPresent) return;
        HANDLE heap = 0;
        HMODULE hKern = GetModuleHandleA("kernel32.dll");    
        GETPROCESSHEAPS getProcessHeaps = (GETPROCESSHEAPS)GetProcAddress(hKern, "GetProcessHeaps");
        if(getProcessHeaps != 0)
        {
                HANDLE heaps[1000];
                int numHeaps = (getProcessHeaps)(1000, heaps);
                for(int k = 0; k < numHeaps; k++)
                {
                        heap = heaps[k];
                        if(SearchHeap(heap)) break;
                }
        }
        else
        {
                heap = GetProcessHeap();
                SearchHeap(heap);
        }
        HeapLock(heap);
        if(presentPtr != 0)
        {
                (*presentPtr) = (DWORD)hkPresent;            
        }
        if(resetPtr != 0)
        {
                (*resetPtr) = (DWORD)hkReset;
        }
        HeapUnlock(heap);
}

Этот метод делает довольно тривиальтую штуку - ищет в куче (Heap) процесса указатели на две нужных нам функции - Present и Reset. Ищет он при помощи метода SearchHeap, в котором нам интересна только пара деталей:


BOOL SearchHeap(HANDLE heap)
{
        int vtableLenBytes = sizeof(DWORD)*4;
        PROCESS_HEAP_ENTRY mem;
        mem.lpData = 0;
        while(HeapWalk(heap, &mem))
        {
                if(mem.wFlags == PROCESS_HEAP_UNCOMMITTED_RANGE) continue;
                DWORD* p = (DWORD*)mem.lpData;
                for(int i = 0; i < (int)(mem.cbData/sizeof(DWORD)); i++)
                {
                        if(memcmp(p, vtableFrag9, vtableLenBytes) == 0)
                        {
                                presentPtr = p + 11;
                                resetPtr = p + 10;
                                offsetPresent = *presentPtr;
                                offsetReset = *resetPtr;
                                g_D3D9_Present = (tPresent)((DWORD*)offsetPresent);
                                g_D3D9_Reset = (tReset)((DWORD*)offsetReset);
                                break;
                        }
                        p++;
                }
                if(presentPtr != 0) break;
        }
        return(presentPtr != 0);
}

Откуда взялись смещения 10 и 11? А всё просто. VMT - это таблица указателей, в которой указатели на функции всегда расположены в одинаковом порядке. Собственно, 10 и 11 - это порядковые номера функций.

У нас есть кусочек VMT, мы идём и ищем такой же кусочек в куче процесса. Как только нашли - отсчитываем от него 10 - это будет указатель на Reset, отсчитываем 11 - указатель на Present.

И всё, нужные указатели нашли, осталось только их поменять на свои. Этим будет заниматься HookVTableFunction:


PBYTE HookVTableFunction(PDWORD* dwVTable, PBYTE dwHook, INT Index)
{
    DWORD dwOld = 0;
    VirtualProtect((void*)((*dwVTable) + (Index*4)), 4, PAGE_EXECUTE_READWRITE, &dwOld);
    PBYTE pOrig = ((PBYTE)(*dwVTable)[Index]);
    (*dwVTable)[Index] = (DWORD)dwHook;
    VirtualProtect((void*)((*dwVTable) + (Index*4)), 4, dwOld, &dwOld);
    return pOrig;
}

Она принимает указатель на текущее устройство D3D, указатель на новую функцию и порядковый номер функции. Вы спросите, откуда же мы возьмём устройство D3D? А всё просто. В методе CheckAndHookPresent9 мы уже заменили указатели на оригинальные функции нашими собственными. Интересовать нас будет изменённая функция hkPresent:


HRESULT WINAPI hkPresent(LPDIRECT3DDEVICE9 pDevice, CONST RECT* src, CONST RECT* dest, HWND hWnd, LPVOID unused)
{    
        while(!npDevice)
        {
        npDevice = pDevice;
        }
        DrawIndicator(pDevice);
        return g_D3D9_Present(pDevice, src, dest, hWnd, unused);
}

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

Как выглядело раньше:

Игра - D3DDevice - VMT-Present - D3D9.DLL

Как выглядит теперь:

Игра - D3DDevice - VMT-hkPresent - hook.dll - D3D9.DLL

А вот и метод DrawIndicator, который банально рисует "менюшку":


void DrawIndicator(LPVOID self)
{
        IDirect3DDevice9* dev = (IDirect3DDevice9*)self;
        dev->BeginScene();
        D3DRECT rec = { 10, 10, 30, 30 };
        D3DCOLOR color = 0;
        if(indicator)
        {
                color = D3DCOLOR_XRGB(0, 255, 0);
        }
        else
        {
                color = D3DCOLOR_XRGB(255, 0, 0);
        }
        dev->Clear(1, &rec, D3DCLEAR_TARGET, color, 1.0f, 0);
        dev->EndScene();
}

Говорим, что из аргументов нам пришло устройство D3D, вызываем у него BeginScene(), чтобы порисовать, создаём квадрат 20х20 пикселей в верхнем левом углу экрана. Дальше смотрим, если indicator == true (VK_F1 и KeyboardHook все помнят?) - рисуем зелёным цветом, если false - то красным.

Рисуем мы, собственно, методом Clear() - просто очищаем выбранную область выбранным цветом, затем вызываем EndScene() и выходим.

Вот и результат, для нетерпеливых:

Оговорюсь, для запуска всего этого надо открыть игру в Cheat Engine, перейти в отладчик и найти там в меню "Tools - Inject DLL".


В принципе, вооружившись бумажкой и карандашом можно рисовать уже сейчас, но в следующем уроке (статье?) попробуем прикрутить сюда какой-нибудь шрифт. Доработок ещё уйма предстоит, этот способ работает не везде (например, у меня не работает в StarCraft II), да и много чего можно улучшить.

Такие вот дела. 200 строчек кода - а сколько веселья. :)










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();
      }
   }
}

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

20120702

Немного документации про PE-формат файлов.

PE - это у нас Portable Executable, как можно выяснить из [википедии]. Собственно, все эти exe, dll и прочие - имеют именно этот формат. Вот пара вырезок:

Адресация памяти: У Intel 386 существуют 3 модели адресации памяти - segmented, flat и real-address mode. Здесь для нас существенно знать лишь то, что программы под Windows обычно используют модель памяти flat ("плоская"). Это означает, что любое 32-х битное число может являться адресом в памяти. Таким образом программа виртуально получает в своё распоряжение 4 гигабайта адресуемой памяти. Конечно, только небольшая часть адресов соответствует реально существующей памяти. Доступ по нелегальному адресу приведёт к ошибке. Использование модели flat также означает, что программа не должна никоим образом пользоваться сегментными регистрами (селекторами). Их следует просто игнорировать. Память представляет из себя последовательность байт, числа в которой принято хранить в формате big-endian, т. е. наименее значимый байт числа сохраняется по младшему адресу (напр. 32-битное число 0x123456781 будет храниться в памяти как последовательность байт 0x78 0x56 0x34 0x12).

Вот немного про процесс загрузки файла в память:

.EXE файлы. Наконец, о том как выполняются программы под Windows. Типичная Windows-программа хранится в файле с расширением .EXE. Типичный .EXE-файл является Portable Executable (PE) - файлом. Portable Executable - это название формата файла. Помимо собственно исполнимого кода PE-файл содержит различную служебную информацию о том, как он будет загружен, таблицы импортируемых и экспортируемых функций и проч. При запуске PE-файла Windows загружает его в память почти в том виде, в котором он хранился на диске, и запускает как отдельный процесс. Каждый процесс получает в распоряжение своё собственное 32-битное адресное пространство (например, два различных процесса могут пользоваться одним и тем же адресом 0x12345, и при этом для каждого из них это будет "его собственная" память. Они не будут замечать друг друга). То, по какому адресу в этом пространстве будет загружен сам PE-файл, называется по английски Image Base и записано в одном из заголовков PE-файла. Обычно Image Base = 0x400000. Все прочие значения в служебных заголовках файла даны как смещения относительно этого адреса. Так, например Code Start (начало исполняемого кода), равное 0x1000 означает, что после загрузки файла в память, исполнение программы начнётся с адреса 0x400000 + 0x1000 = 0x401000.
DLL: Практически каждая Windows-программа пользуется функциями из динамически загружаемых библиотек (Dynamic-Link-Libraries, DLL). Важнейшими из них являются KERNEL32.DLL и USER32.DLL, которые предоставляют основные системные процедуры. Тогда как функции внутри программы вызываются просто инструкцией "CALL func" где func - адрес вызываемой функции, функцию из DLL (т. н. импортируемую функцию) таким путём вызвать нельзя, т. к. во время компиляции программы адрес её не известен. Использование DLL происходит следующим образом:
Во-первых, в заголовке PE-файла записано имя DLL, функции из которой используются в программе. При загрузке программы в память, Windows загружает в её адресное пространство и все используемые ей DLL. DLL представляет из себя такой же PE-файл, как и сама программа, но так как DLL в отличие от EXE загружается в "чужое" адресное пространство, то адрес, по которому она "хотела бы" быть загружена (её Image Base), может оказаться занят. Тогда Windows переносит её по своему усмотрению.
Во-вторых, в EXE файле записано имя каждой импортируемой функции и оставлено место, куда Windows после загрузки соответствующей DLL проставит действительный адрес функции. Это называется таблицей импортов PE-файла. Во время компиляции местонахождение таблицы импортов известно, и поэтому можно вызывать процедуры из DLL "косвенно", указывая место, где должен быть адрес вызываемой процедуры.
Думаю, для начала хватит, попозже напишу более подробную статью, но уже своими словами. Просто мы с вами так радостно ищем все эти адреса, меняем код, инъекции проводим, а как оно работает - и не знаем.

20120701

Взлом по заявкам - ломаем Весёлую Ферму (Farm Frenzy).

[Ссылка] на видеоурок. Заодно мы узнаем, что в Cheat Engine тоже есть сканер сигнатур.

Я - безрукий идиот, потому что я забыл очень важную штуку. Итоговый скрипт должен выглядеть вот так:


[ENABLE]
label(timer)
registersymbol(timer)
aobscan(timer_address,D9 9E 5C 43 0F 00 E8 C1 8D FF FF 8B CE 5E 83 C4 04 E9 76 D5 FF FF)


timer_address:
timer:
nop
nop
nop
nop
nop
nop


[DISABLE]
timer:
fstp dword ptr [esi+000F435C]
unregistersymbol(timer)


Жирненьким я выделил строчку, которую нужно добавить в самом конце - чтобы созданная нами метка в памяти уничтожалась при отключении скрипта. Мелочь, но довольно существенная.