Показаны сообщения с ярлыком Cheat Engine. Показать все сообщения
Показаны сообщения с ярлыком Cheat Engine. Показать все сообщения

20150324

Перезагружаемся.

Перезагрузка идет полным ходом. Кто-нибудь смотрит? Как ощущения? (:
[Вот] ссылка на плейлист, если кто-то еще не видел.

20150309

Можно ли при помощи Cheat Engine взломать сетевую игру?

...задал мне вопрос один из зрителей на youtube. Попробую ответить.

Во-первых, давайте немного разберемся, как работают сетевые игры. Есть клиент (К), в который играет игрок, где рисуется вся красивая графика, леса, монстры бегают и рейды на боссах вайпаются. Есть сервер (С), к которому подключаются игроки, который управляет авторизацией, чатом, игровым миром и всем таким прочим. На нем графики обычно нет вообще, часто - даже консольного окна. Представим, что у нас два клиента и один сервер:

(К0)
  ^
  |---------(С)
  v
(К1)


К0 бежит себе по полю, видит монстра. Потому что сервер ему об этом сказал. К0 берет и убивает монстра. Говорит серверу - "Я убил монстра!". Сервер думает и отвечает - вот тебе 3 золотых. К0 видит на экране появившиеся 3 золотых и подбирает их. Говорит серверу - "Я подобрал золото! 3 штуки!!". Сервер открывает у себя блокнотик и записывает - у К0 золота стало на 3 больше. Далее К0 открывает инвентарь (спрашивая у сервера, "Что у меня в инвентаре?"), а сервер ему и отвечает - столько-то золотых, половинка дарницкого и литр кефира.

Бывают такие невообразимо глупые игры, где можно заморозить здоровье на стороне клиента и он просто будет сообщать серверу, что здоровье полное. То есть на стороне сервера не будет никаких проверок на это. Бывает, например, World of Tanks, к которому толком нет читов, потому что весь клиент игры - это большой слайд-проектор, которому сервер выдает картинки, а всю информацию считает у себя. Чуть что не сошлось - клиента выкидывает из игры. Бывает так, что на одни показатели в игре косвенно влияют другие. Скажем, как это было у моих друзей в LineAge - если сильно нагрузить персонажа багажом, у него не будет со временем восстанавливаться здоровье. Если после этого умереть и в момент возрождения разорвать соединение клиента с сервером, то игрок возрождается с 0 здоровья и становится бессмертным. Баг был быстро исправлен, но было весело. (:

Вторая сторона монеты, помимо сканирования адресов и ковыряния в отладчике - разбор сетевых данных, которыми обменивается сервер и клиент. Те самые цитаты от клиента серверу и обратно, что я приводил выше. Можно, скажем, взять утилиту, которая позволяет увидеть и прочитать сетевые пакеты, передаваемые между клиентом и сервером (такие утилиты называются снифферами, от английского to sniff - нюхать), и увидеть там (например):

(К0): Я нашел 3 золота!
(С): Положил тебе в инвентарь 3 золота.

Берем пакет, отправляемый клиентом, исправляем в нем цифру 3 на 10000, а затем отправляем опять. Смотрим, что ответит сервер:

(С): Положил тебе в инвентарь 10000 золота.

Вуаля. Взломали? Вполне. Но это - самый простой случай, обычно все куда сложнее.

20150303

ESP. Часть 1. Ищем нужные нам данные.

Сначала - видео:


А теперь, для начала, я приведу наш небольшой план еще раз:
0. Получение данных из игры
1. Чтение данных из игры программно
2. Рисование в окне игры
3. Вывод данных из игры в окно


Итого, руководствуясь видео выше и своей смекалкой, мы получаем следующие данные:

0. Указатель на коллекцию структур игроков
1. Смещения до здоровья и координат в структуре игрока
2. Адрес общего количества игроков
3. Смещения между структурами игроков в коллекции из п. 0

Конечная наша цель - вывести над каждым игроком в игре его текущее здоровье, как вы уже, думаю, догадались.

В следующей части мы напишем небольшую программу, которая будет уметь читать все нужные нам данные и выводить. Пока что - в консоль, а потом - уже на экран игры.

20140803

D3D: Chameleon Wallhack

Привет, ребята! Для начала, вот видео:


А [вот] ссылка на репозиторий с исходным кодом.

Значит, скачали вы исходники, взяли любимый компилятор и любимую IDE, скомпилировали это дело и ничего не поняли. В видео я, отсылась к предыдущим статьям и урокам, рассказываю о том, что мы написали логгер, позволяющий нам узнать некоторую необходимую для нашей бурной деятельности информацию. А именно - stride, или номер куска экрана, который мы сейчас будем рисовать, и NumVertiles, аргумент функции [DrawIndexedPrimitive], который показывает нам количество вершин того куска, который мы будем рисовать. Мир игры состоит из моделек, модельки - из полигонов. Полигон - это треугольник, то есть у него три вершины. На модельки, собственно, сверху натянуты текстуры, чтобы было видно, что металл - это металл, оружие - это оружие, а враги - это враги. Нашли мы, значит, нужную нам текстуру. Дальше видим такой кусок кода:

pDev->SetRenderState(D3DRS_ZENABLE, false);
pDev->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
pDev->SetTexture(0, red);
oDrawIndexedPrimitive(pDev, PrimType, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
pDev->SetRenderState(D3DRS_ZENABLE, true);
pDev->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
pDev->SetTexture(0, green);

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

0            1             2
             |                   
X -----------|-------------O
             | 

X - это мы. Палочки - это стена перед нами, а O - это нечто за этой стеной. Циферки обозначают позицию одного относительно другого в этом самом буфере глубины. У кого цифра 1 - того мы и видим, у кого больше - значит он чем-то от нас закрыт. Если товарищ О выйдет из-за стены, то у него Z-order (позиция в буфере глубины) станет равным 1, так что игра должна будет его перед нами отрисовать и в результате мы его увидим.

Если отключить буфер глубины, то игра решит, что все игровые объекты в поле нашего зрения находятся перед нами - то есть нету никаких препятствий и никто никого не перекрывает. Получится каша, потому что одновременно будут рисоваться стены, мебель, пол, потолок, оружие, гранаты, люди, враги, союзники, словом - всё. Для того, чтобы вычленить из этой каши то, что нам нужно, мы и использовали наш логгер. В общем-то, только у нужных нам объектов мы буфер глубины и отключили. Давайте вернёмся к коду, а то я заболтался. Отключив буфер глубины, мы говорим, что текущий объект мы будем рисовать сплошным цветом. Сразу после этого мы вызываем функцию [SetTexture], которая указывает, что мы будем использовать текстуру по имени red - красненькую. Напомню, что пока мы ещё ничего не рисовали и не пытались даже. Что мы делаем следующей строчкой? Верно, рисуем, вызвав оригинальную функцию рисования вместо нашей перехваченной.

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

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

Тэк-с. Я упоминал текстуры. Собственно, чуть ниже, в перехваченной функции [EndScene], мы их и создаём. Тут я в подробности вдаваться не буду, так как этот кусок кода я нашёл и честно скопировал себе, скажу только, что цвет мы задаём вот в этой строчке:

((PDWORD)d3dlr.pBits)[xy] = 0xFF00FF00;

Тут нас интересуют последние три пары чисел шестнадцатиричного значения. Догадливые уже поняли, что это RGB, он же - Red Green Blue, то есть три цветовых канала. Первые две цифры в том числе выше - это прозрачность, которая не используется в данном случае, как я понял. Собственно, FF0000 - это красный, 00FF00 - это зелёный, а 0000FF - это синий. Значения лежат в пределах от 00 до FF. Подходящий цвет можно выбрать в любом графическом редакторе или же в интернете найти.

В той же перехваченной EndScene мы первой строчкой узнаём ViewPort - это, собственно, то, куда мы смотрим, когда играем. Узнаём мы его затем, чтобы выводить туда текст, так что после создания текстур мы создаём шрифт и пишем им на экран, в данном случае - включён ли наш wallhack.

На этом, в принципе, можно бы было и остановиться, но есть ещё одна важная деталь. В комментариях некоторые люди жаловались, что игра вылетает с ошибкой при попытке сделать alt+tab или поменять разрешение экрана. Это чинится перехватом функции [Reset], в которой мы уничтожаем наши текстуры и шрифт, чтобы они не засоряли видеокарту и пересоздались, дабы игра снова могла их рисовать.

Вот теперь - всё. До новых встреч! (:

20140119

D3D: Трагически наступившее "скоро".

Всем привет!

У меня нашлось время и я решил ещё немного поковыряться с D3D и прочими неприличными словами. (:

Остановились мы на том, что написали перехватчик методов D3D, ответственных за рисование, и смогли нарисовать на экране чужого приложения квадратик. Попутно мы написали свой собственный инжектор DLL-ок.

Это всё, конечно, круто, но не очень сильно впечатляет и далековато от наших амбиций. Итак, встречаем:


Вот эта самая функция нам и понадобится. Я, честно сказать, фигово разбираюсь в 3д-графике и рисовании в целом, так что гуру этой области прошу меня сильно не пинать - объяснять буду, как понял сам.

Как мы помним, читали или где-нибудь слышали, нельзя просто так взять и нарисовать оружие, машину или нечто такое. Видеокарточки туповаты и рисовать умеют только примитивы - точки, линии, треугольники. Захотели квадрат - сложили два треугольника вместе. Эти штуки ещё иногда зовут полигонами, ага. Дык вот эта самая функция используется для отрисовки примитивов, если очень кратко. Нас там будет интересовать параметр BaseVertexIndex, в суть которого я тоже вник крайне смутно и пытаться объяснить это кому-то пока что не стану - давайте просто звать эту штуку индексом. Ещё пара крутых штук ждёт нас чуть дальше, когда я код приведу. Так как у меня возникли некоторые трудности с перехватом этой функции, то перехватывать мы её будем не вручную, а при помощи библиотечки detours от microsoft (все необходимые файлы идут в комплекте с исходниками), так что код слегка видоизменился. Вот, например:



Тут сразу бросаются в глаза последние три строчки - это, собственно, и есть перехват. Делает библиотека всё то же самое - мы скармливаем ей адрес найденной фунции, которую нужно перехватить, в её начало дописывается переход (JMP 00112233) на перехваченную функцию, а перехваченная выставляет всё, как было, отрабатывает и возвращает хук на место. Едем дальше:



Это - перехваченная DrawIndexedPrimitive, при помощи которой игра будет пытаться рисовать всё то, что мы с вами видим на экране. Я не буду вдаваться глубоко в подробности, нам это нужно один раз, так что приведу примерную суть:

1. Каждый вызов у нас есть некие данные (Stride и BaseVertexIndex) , которые нам надо узнать и запомнить для использования уже в чите. Они характеризуют тот или иной объект на экране.
2. Каждый раз, когда игра пытается что-нибудь нарисовать, мы заносим структуру этих данных в массив.
3. В логгере есть кнопки, позволяющие управлять процессом - менять значения Stride и BaseVertexIndex, выбранные на данный момент будут подсвечиваться зелёным цветом.
4. Задача - натыкать такое значение, чтобы нужный нам объект(-ты) подсветился(-лись) зелёным. Запомнить цифры.

Скомпилируется всё это дело в DLL, которую надо будет заинжектить в процесс игры (я не парюсь и продолжаю делать это через Cheat Engine). Поддерживаются любые игры, которые рисуют при помощи D3D9.

ВНИМАНИЕ тем, кто запускает игры из Steam (как я, например): В стиме в свойствах игры надо отрубить steam overlay ui, так как он работает точно так же - иначе получим вылет.

ВНИМАНИЕ тем, кто до сих пор не использует MS Visual Studio 2013 (как я, например): Если вам лень ставить 13-ю студию, то просто слейте все исходники и создайте новый проект (DLL). Ему понадобится в свойствах указать путь до include и lib для MS DirectX SDK (полгига которого можно спокойно скачать в гугле), если заругается на отсутствие detours.lib - она лежт в папке проекта.

Результат:


А вот и [ссылка] на все исходники. На днях будем учить этих инопланетных парней просвечивать сквозь стены. (:

20130803

IT'S ALIVE! ALIVE!!

Привет всем, кто это читает! Ещё не забыли моё имя и тему этого блога? (:

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

Проблема тут только в одном - я сейчас пересмотрел последние видео и мне кажется, что это уже ни черта не обучающий блог, а какая-то фигня. Поэтому я прошу мнений:

1. Я смотрю последние видео и пробую продолжить от них (d3d, хуки, воллхаки и прочая прелесть на C++). Плюсы я знаю средне, d3d - ещё более средне, так что это будет медленно и, скорее всего, корявенько, но я учусь и исправляюсь.
2. Мы садимся и с нуля пишем движок для трейнеров на C++. Или на ассемблере. Или на C#. С фичами, которые предложите вы, или до которых не догадаюсь я. Весь код я буду заливать на GitHub, конечно же.
3. С нуля пишем какую-нибудь простенькую игру на d3d, потом - компаемся отладчиком в её потрохах и понимаем, как и что работает. Ещё более медленный вариант, но сулит много интересного.
3. Что-нибудь повеселее - то, что вы предложите в комментариях.

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. Не переживайте, в видео всё разжёвано.

20121031

Я тут начитался всяких крутых форумов и вот, что получилось в процессе экспериментирования:


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

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

20121028

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 строчек кода - а сколько веселья. :)










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 - вдруг кому-то кроме меня интересен этот замечательный отладчик.

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

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)


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

Рассуждаем о D3D вслух.

Попутно ковыряясь в нашем D3D-приложении при помощи острого, аки скальпель, OllyDbg.

[Ссылка].

20120613

[Видеоурок] Знакомимся с отладчиком OllyDbg.

[Ссылочка] на урок, тыкаем и смотрим\слушаем\хихикаем\комментим.

Пытаюсь на своей глючной и сырой сборке восьмого виндовса продемонстрировать отладку и поиск сигнатуры на примере многострадальной Crimsonland, сломав в ней патроны. Адрес ищем при помощи Cheat Engine, всё остальное делаем в Olly. Скачать отладчик и необходимый для создания сигнатур плагин можно у меня на [сайте].

20120528

[Видеоурок] И что же, всё-таки, делать с указателями?

[Ссылочка].

Смотрим ещё раз, как ищутся указатели, подробно разбираем код из [статьи].

PS: Совсем забыл! Чтобы узнать именно адрес значения (например, чтобы затем записать туда что-нибудь) - просто остановитесь на предпоследнем шаге - не читайте значение адреса, прочитайте последний уровень указателя - результатом будет адрес значения, куда можно писать, используя уже готовые процедуры.

20120524

[Видеоурок] Ломаем Split Second!

[Ссылка] на урок. Заранее извиняюсь за небольшое отставание звука (не критичное) - какие-то проблемы с видеоредактором.

В уроке демонстрируется поиск и взлом неизвестного значения (экранной полоски). С нуля, совсем. Из инструментов - голый [Cheat Engine]. Начинаем с поиска значения, заканчиваем уже готовым скриптом (или трейнером, если вы рукастые), попутно стараюсь комментировать свои действия, т.к. уроки на эту тему уже были.

Следующий урок - не за горами!