Pull to refresh

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

Reading time4 min
Views101K
Привет, %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. 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 и опкоды этой функции.
Что с ними делать и как внедрить свой код я возможно расскажу в следующих частях, а пока голосуйте, осмысливайте код выше, гуглите, яндексуйте, бингуйте, яху..., спрашивайте в коментариях и читайте документацию по ассемблеру, будет полный хардкор и дренейки!
Only registered users can participate in poll. Log in, please.
Писать продолжение?
95.41% Да1747
4.59% Нет84
1831 users voted. 208 users abstained.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+68
Comments61

Articles

Change theme settings