habrahabr

Пишем бот для MMORPG с ассемблером и дренейками. Часть 0

  • воскресенье, 22 февраля 2015 г. в 02:11:10
http://habrahabr.ru/post/251137/

Привет, %username%! Покопавшись в статьях хабра, я нашел несколько оных про написание ботов для MMORPG. Несомненно это очень интересные и познавательные статьи, но возможности в них весьма скудны. Что если, например нужно пофармить мобов или руду по заданному маршруту убивая агрессивных мобов, игроков и всех кто будет на Вас нападать по пути, выкрикивая им вслед непристойности, да что б еще и определить не смогли. В общем полная эмуляция среднестатистического MMORPG игрока. Написание макросов для AutoIt, симуляция кликов в окне, анализ пикселей под курсором — это совсем не наш вариант. Заинтриговал? Добро пожаловать под кат!
Disclaimer: Автор не несет ответственности за применение вами знаний полученных в данной статье или ущерб в результате их использования. Вся информация здесь изложена только в познавательных целях. Особенно для компаний разрабатывающих MMORPG, что бы помочь им бороться с ботоводами. И, естественно, автор статьи не ботовод, не читер и никогда ими не был.


Содержание

  1. Часть 0 — Поиск точки внедрения кода
  2. Часть 1 — Внедрение и исполнение стороннего кода
  3. Часть 2 — Прячем код от посторонних глаз
  4. Часть 3 — Под прицелом World of Warcraft 5.4.x (Структуры)
  5. Часть 4 — Под прицелом World of Warcraft 5.4.x (Перемещение)
  6. Часть 5 — Под прицелом World of Warcraft 5.4.x (Кастуем фаерболл)

И так с места в карьер.
1. Выбор способа реализации внедрения (немного теории)

Определенно нам необходимо внедрить код в процесс игры, который и будет ей управлять. Для это можно модифицировать сам исполняемый файл (это очень легко сделать, но и легко определить и получить бан) или внедрить DLL (это тоже определяется очень просто), но это все не для нас. Наш подход — это внедрение кода, в главный поток процесса, получающего управление и возвращающего его обратно.
Для этого нужно найти/придумать точку внедрения, которая будет не столь очевидна для анти-читов и полезна для нас. Таких точек может быть очень много, но по многим причинам, лучшим решением будет внедрение в отрисовку игры, т.е. создание хука для Direct3D. Опять же по многим причинам лучше всего перехватывать EndScene функцию, потому как до ее вызова, все изменения игрового мира и иные расчеты уже произойдут. Вот происходящий процесс внутри для наглядности:
  1. ...
  2. Отрисовка объектов текущей сцены игры
  3. Вызов подмененной D3D EndScene
  4. Наш код
  5. Вызов оригинальной D3D EndScene
  6. Следующая сцена
  7. ...

Сцена в данном ключе это так называемый frame. Другими словами — наш код будет работать с частотой вашего fps.
Замечание: fps может быть достаточно высоким значением, по-этому не стоит обрабатывать каждый вызов кода. Думаю достаточно будет 10-15 вызовов в секунду


2. Инструментарий

И так план мы наметили, теперь нужны инструменты. Я (как и большинство надеюсь) люблю использовать все готовое. По сему предлагаю обзавестись следующими вещами:
  1. Любая IDE где будем писать код на C#
  2. OllyDbg — на мой взгляд лучший дебагер после IDA
  3. HackCalc — калькулятор для пересчета VA (виртуального адреса) в Offset и обратно
  4. SlimDX — DirectX фреймворк для .NET
  5. FlatAsm Managed — библиотека преобразует мнемокоды ассемблера в байткод



3. Поиск точки внедрения

И так со способом разобрались, инструментами обзавелись, теперь нам необходимо понять, куда внедрять наш код.
При отрисовке с помощью D3D создается виртуальный объект Direct3D device, который по сути представляет собой VMT(виртуальная таблица методов, которая является указателем на указатель на таблицу методов D3D). Это таблица хранит, опять же, указатели на методы Direct3D, например BeginScene, EndScene, DrawText и т.д. Нас в данном случае интересует только EndScene, т.к. Direct3D device создается в единственном экземпляре, то нам нужно получить указатель на него, а затем получить указатели на таблицу. Опять же нам необходимо определить какой D3D используется в клиенте игры и т.к. вариантов у нас 2 (DX9 и DX11), то решить эту проблему можно простым перебором. Для этого мы и будем использовать SlimDX.
В коде processMemory.Read и processMemory.ReadBytes обертки стандартной ReadProcessMemory из kernel32.dll
Проверка на DX9:
//Создаем устройство D3D9
var device = new SlimDX.Direct3D9.Device(
    new SlimDX.Direct3D9.Direct3D(), 
    0, 
    DeviceType.Hardware, 
    Process.GetCurrentProcess().MainWindowHandle, 
    CreateFlags.HardwareVertexProcessing, 
    new[] { new PresentParameters() });
    using (device)
    {
//Открываем текущий процесс
        var processMemory = new ProcessMemory((uint)Process.GetCurrentProcess().Id);
//Считываем необходимый нам адрес расположения в памяти D3D9 функции по смещению 0xA8 от указателя на Com объект
        _D3D9Adress = processMemory.Read<uint>(processMemory.Read<uint>((uint)(int)device.ComPointer) + 0xa8);
//Считываем опкоды функции
        _D3D9OpCode = (int)processMemory.Read<byte>(_D3D9Adress) != 0x6a ? processMemory.ReadBytes(_D3D9Adress, 5) : processMemory.ReadBytes(_D3D9Adress, 7);
    }

Откуда 0xA8?.. Если открыть d3d9.h и найти EndScene метод, то вы найдете
STDMETHOD(EndScene)(THIS) PURE;
42 ой по счету в интерфейсе IDirect3DDevice9, а одна функция в архитектуре х86 адресуется 4мя байтами, получаем 42*4=168, а это и есть 0xA8.
Все это необходимо обернуть в try/catch и если вылетела ошибка, значит нам нужно пробовать D3D11 и тут все немного сложнее, нам нужен не EndScene, а SwapChain, он находится по индексу 8, т.е. 8 * 4 = 32 = 0х20:
//Создаем форму отрисовки для получения устройства D3D11
using (var renderForm = new RenderForm())
{
    var description = new SwapChainDescription()
    {
        BufferCount = 1,
        Flags = SwapChainFlags.None,
        IsWindowed = true,
        ModeDescription = new ModeDescription(100, 100, new Rational(60, 1), SlimDX.DXGI.Format.R8G8B8A8_UNorm),
        OutputHandle = renderForm.Handle,
        SampleDescription = new SampleDescription(1, 0),
        SwapEffect = SlimDX.DXGI.SwapEffect.Discard,
        Usage = SlimDX.DXGI.Usage.RenderTargetOutput
    };
    SlimDX.Direct3D11.Device device;
    SlimDX.DXGI.SwapChain swapChain;
    var result = SlimDX.Direct3D11.Device.CreateWithSwapChain(
        DriverType.Hardware, 
        DeviceCreationFlags.None, 
        description, 
//Здесь мы получаем устройство
        out device, 
        out swapChain);
    if (result.IsSuccess) using (device) using (swapChain)
    {
//И открыв текущий процесс - считаем адрес функции и опкоды
        var processMemory = new ProcessMemory((uint)Process.GetCurrentProcess().Id);
//Считываем наш SwapChain
        _D3D11Adress = processMemory.Read<uint>(processMemory.Read<uint>((uint)(int)swapChain.ComPointer) + 0x20);
        _D3D11OpCode = processMemory.ReadBytes(_D3D11Adress, 5);
    }
}

Все это опять необходимо опять же обернуть в try/catch. В случае неудач обоих попыток получить адрес функции D3D возможно изменились смещения либо у вас не D3D9 или D3D11.
Итог: У нас имеется адрес функции EndScene D3D и опкоды этой функции.
Что с ними делать и как внедрить свой код я возможно расскажу в следующих частях, а пока голосуйте, осмысливайте код выше, гуглите, яндексуйте, бингуйте, яху..., спрашивайте в коментариях и читайте документацию по ассемблеру, будет полный хардкор и дренейки!
Писать продолжение?

Проголосовало 1063 человека. Воздержалось 112 человек.

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