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

  • Tutorial
Привет, %username%! И так, продолжим написание нашего бота. Из прошлых статей, мы научились находить адрес перехватываемой функции для DirectX 9 и 11, а так же исполнять произвольный ассемблерный код в главном потоке игры. Естественно, эти все операции, могут быть замечены защитой игры и вы будете наказаны. Но сегодня, я покажу как спрятать этот код от защиты, в том числе и от такого монстра, которого все боятся, как Warden. Как я и говорил, я не ботовод потому, что меня не поймали. Жду вас под катом!

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. Рассуждения на тему защиты игр

Хотелось бы разогнать тучи таинственности над античитами, протекторами и прочей сопутствующей с игрой защитой. Потому как вариантов защиты не так уж и много и я перечислю основные:
1. Проверка файлов игры на оригинальность
Здесь все просто, игра запрашивает с сервера контрольные суммы файлов игры и сверяет с теми, что получатся в результате проверки. В данном случае можно просто модифицировать алгоритм проверки в исполняемом файле. Но все намного сложнее, если алгоритм проверки, тоже подгружается с сервера, как в случае с Warden.
2. Анализ подключенных DLL
В данном случае вы рискуете, только используя нечто популярное, т.к. информация об этом, скорее всего уже есть у разработчиков и как в случае с Valve Anti Cheat, неизвестные библиотеки отправляются прямиком на сервер для анализа, опять таки, если вызовет подозрение. Так что ваш бан может быть всего лишь отсрочен на неопределенный срок.
3. Анализ VMT импортируемых библиотек на перехваты
Здесь вообще все сложно, вы можете получить бан даже за TeamViwer или Fraps, если разработчики игры так захотят и ничего вы не докажете. И как сказал Alexey2005 в Часть 0 — Поиск точки внедрения кода
Чем параноидальнее защита, тем больше у неё ложных срабатываний. Потому что в процесс постоянно ломится и внедряется огромная куча всего — всякие перехватчики клавиатурных нажатий, антивирусные сканеры, всевозможные «управляющие центры» для видео и звука, компоненты настроек устройств ввода вроде мыши и т.д.

4. Защита игровой памяти от чтения/записи
Данным функционалом обладает набирающая популярность в Steam защита Easy Anti Cheat. В Windows запускается сервис EasyAntiCheat, который защищает память игры от чтения и записи. Если же сервис не запущен, то игра отказывается соединяться с сервером и хотелось бы услышать размышления хабрасообщества на этот счет.
5. Проверка определенных участков памяти игры
Этим, опять же, занимается не без известная Warden. Хотя если честно, чем она только не занимается. Warden подгружает с сервера модули проверки памяти и хэш таблицу и сравнивает значения с таблицей. Этим способом легко определить читеров, которые модифицируют память игры для получения преимуществ, типа повышенной скорости, хождение по воздуху и стенам и прочее.
Итог
Из всего выше описанного, становится понятно, что используя популярные программы и методы мы очень рискуем попасть в бан и что бы этого избежать, нам необходимо сделать наше внедрение тяжело определяемым. Чем мы сейчас и займемся. Для начала напишем метод GetFakeCommand и ObfuscateAsm:
internal static string GetFakeCommand()
{
    var list = new List<string> { "mov edx, edx", "mov edi, edi", "xchg ebp, ebp", "mov esp, esp", "xchg esp, esp", "xchg edx, edx", "mov edi, edi" };
    int num = Random.Int(0, list.Count - 1);
    return list[num];
}
internal static IEnumerable<string> ObfuscateAsm(IList<string> asmLines)
{
    for (var i = asmLines.Count - 1; i >= 0; i--)
    {
        for (var k = Random.Int(1, 4); k >= 1; k--)
        {
            asmLines.Insert(i, GetFakeCommand());
        }
    }
    for (var j = Random.Int(1, 4); j >= 1; j--)
    {
        asmLines.Add(GetFakeCommand());
    }
    return asmLines;
}

Как видно, GetFakeCommand возвращает ассемблерную команду, которая не меняет состояния регистров и флагов и этот список можно расширить при необходимости в несколько раз, а ObfuscateAsm — понатыкает эти команды в случайных местах нашей подпрограммы. И так модифицируем код подмены адреса из прошлой статьи в месте получения HookAddress, я не буду дублировать весь код, а только покажу измененную часть:
//Открыли процесс и главный поток
var RandomOffset = (uint)Random.Int(0, 60);
var HookAddress = Memory.AllocateMemory(6000 + Random.Int(1, 2000)) + RandomOffset;
//Зарезервировали память и сформировали подпрограмму
Memory.Asm = new ManagedFasm(Memory.ProcessHandle);
Memory.Asm.Clear();
foreach (var str in ObfuscateAsm(asmLine))
{
    Memory.Asm.AddLine(str);
}

Этот же финт можно проделать и с памятью для аргументов argumentAddress1 и argumentAddress2. Обязательно запоминаем значение RandomOffset для случая освобождения памяти HookAddress.
Memory.FreeMemory(HookAddress - RandomOffset);
Memory.FreeMemory(argumentAddress1 - RandomOffsetArgs1);
Memory.FreeMemory(argumentAddress2 - RandomOffsetArgs2);

И изменим InjectAndExecute метод и как обещал, реализуем очередь для многопоточного вызова метода:
public byte[] InjectAndExecute(IEnumerable<string> asm, bool returnValue = false, int returnLength = 0)
{
    lock (Locker)
    {
        var offset = 0;
        var randomValue = (uint)Random.Int(0, 60);
        //Наша очередь может хранить 80/4 = 20 значений
        while (Memory.Read<int>(argumentAddress1 + offset) != 0 || Memory.Read<int>(argumentAddress2 + offset) != 0)
        {
            offset += 4;
            if (offset >= 80)
            {
                offset = 0;
            }
        }
        Memory.Asm.Clear();
        foreach (var str in asm)
        {
            for (var i = Random.Int(0, 3); i >= 1; i--)
            {
                Memory.Asm.AddLine(ProtectHook());
            }
            Memory.Asm.AddLine(str);
        }
        dwAddress = Memory.AllocateMemory(Memory.Asm.Assemble().Length + Random.Int(60, 80)) + randomValue;
        Memory.Asm.Inject(dwAddress);
        Memory.Write<uint>(argumentAddress1, dwAddress + offset);
    }
    while (Memory.Read<int>(argumentAddress1 + offset) > 0)
    {
        Thread.Sleep(1);
    }
    byte[] result = new byte[0];
    if (returnValue)
    {
         result = Memory.ReadBytes(Memory.Read<uint>(argumentAddress2 + offset), returnLength);
    }
    Memory.Write<int>(argumentAddress2 + offset, 0);
    Memory.FreeMemory(dwAddress - randomValue);

    return result;
}

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

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 20

    0
    Поправочка на счет EasyAntiCheat.
    Это клиент-серверное приложение. На сервер установлено приложение, модуль которого внедрен в процесс сервера игры, и которое проверяет соединилась ли программа с этого клиента (который зашел на сервер), и, если да, то оставляет клиента. Если нет — выкидывает средствами игры (kick, ban и т.д.).
    Remark: EasyAntiCheat Client — программа, которая запускается параллельно игре и занимается проверкой процесса игры на внедрение, проверяет эвристикой запущенные процессы у пользователя, а так же с рандомным интервалом (но не реже раза в минуту) отсылает на сервер скриншот экрана пользователя.

    Читы которые работают с EasyAntiCheat (а таких, с которым EAC еще не может совладать по сути всего один) работают на компьютере в качестве и файрволла тоже. Он дает соединится с сервером, а потом блокирует его работу в интернет, поскольку античит позволяет терять коннект на срок от 30 секунд до 1 минуты, опять же рандомно (я так понимаю, что это связанно с особенностями UDP, чтобы не было проблем при нестабильном канале). Более того, он читает момент снятия скриншота с экрана и подсовывает его античиту. Подробностей, к сожалению, не знаю, поскольку я нахожусь со стороны античита, а не тех, кто создает читы.
      0
      srsly? Вот так просто скриншот экрана? А если я в это время порс банковскими данными работаю, а игрушка на фоне свёрнута?
        0
        Раньше иногда бывали всякие инсинуации. Но уже более двух лет EAC определяет развернутость fullscreen приложения (раз) и приложение в фокусе (два) и не отсылает скриншоты вне указанных ситуаций. Ну, и конечно, любой, кто скачивает и использует ПО EAC конечно же согласен с лицензионным соглашением, где явно написано на восьми (сейчас может уже больше) языках, в том числе и русском, о том, что во время работы могут быть отосланы скриншоты экрана и это является прямым функционалом программы.
          +8
          В народе это называется «анальный зонд».
        0
        Совершенно верно, клиент-серверное, я и не утверждал чего-то иного, так как знаю это
        namespace EasyAntiCheat.Game.Launcher
        {
            internal static class Program
            {
                private static void CopyOldOutputLog()
                {
                    if (!File.Exists("AwesomeGame_Data/output_log.txt"))
                    {
                        return;
                    }
                    if (File.Exists("AwesomeGame_Data/output_log.last"))
                    {
                        File.Delete("AwesomeGame_Data/output_log.last");
                    }
                    File.Copy("AwesomeGame_Data/output_log.txt", "AwesomeGame_Data/output_log.last");
                }
        
                private static void Main(string[] args)
                {
                    Program.CopyOldOutputLog();
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);
                    Loader loader = new Loader(12, "AwesomeGame.exe", null);
                    loader.Options.Installer.Allow = true;
                    loader.Options.Arguments = string.Join(" ", args);
                    loader.Progress += new EventHandler<LoadProgressEventArgs>(Program.OnLaunchProgress);
                    loader.Completed += new EventHandler<LoadCompletedEventArgs>(Program.OnLaunchCompleted);
                    loader.BeginLoad();
                    while (!loader.HasCompleted())
                    {
                        Thread.Sleep(100);
                    }
                }
        
                private static void OnLaunchCompleted(object sender, LoadCompletedEventArgs eventArgs)
                {
                    if (eventArgs.Status == LoadExitCode.Success)
                    {
                        return;
                    }
                    MessageBox.Show(string.Format("AwesomeGame Launcher Error: {0} - {1}", eventArgs.Status, eventArgs.Message));
                }
        
                private static void OnLaunchProgress(object sender, LoadProgressEventArgs eventArgs)
                {
                }
            }
        }
        


        Но ее сервис, на стороне клиента, который висит в процессах и не дает просто так читать память из Ring3 немного раздражает… Вот в чем вопрос.
        +3
        Судя по серии статей, автор уже давно реализовал рабочую версию бота для WoW, и хотелось бы немножко спойлера: в итоге то, работает или нет? )))
          0
          Могу предположить, что бот для 5.4 не будет работать в 6.*, технически много чего изменилось
          +3
          Хинт: можно захостить в процессе игры CLR и в него из памяти прогрузить свою сборку. Античиты будут видеть только системные библиотеки с подписью MS.
            +1
            И чит-аналитики из близзарда сидят, такие, смотрят снапшоты окружения, ха, подумаешь, mscoree с нами, это же MS, можно доверять. На самом деле, идея крутая, во многих случаях может дать профит.
              0
              CLR в процессе может очень многими разными путями оказаться на самом деле. Особенно если игра по каким-то причинам решила где-то использовать движок от IE.
            +1
            Странная у вас обфускация, разбавление константного кода константным же мусором ИМХО ничего не даст, как только разработчики античитов прознают о вашем приложении ничто им не помешает натаскать сканер конкретно на ваш мусор, он-то неизменен.
            Как по мне, нужен генератор мусора(и не только мусора), который при каждом запуске будет выдавать разный код, сделать хотя бы замену команд по некоторому набору правил, например:

            ADD EAX, 3

            можно превратить в
            SUB EAX,-2
            INC EAX

            или
            PUSH EAX
            XOR EAX,EAX
            MOV EAX,2
            INC EAX
            ADD EAX,DWORD PTR SS:[ESP]
            ADD ESP,4
            

            или ещё можно сделать так

            полезный код
            jmp lb
            мусорные инструкции, рандомный набор, всё равно не выполняются
            lb:
            полезный код
            

            И так далее

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

              Согласен, но вот, что делает программа по безусловному переходу? Вдруг это fraps или еще какой overlay для игры, который рисует в вашем окне?
              +1
              4. Защита игровой памяти от чтения/записи
              Данным функционалом обладает набирающая популярность в Steam защита Easy Anti Cheat. В Windows запускается сервис EasyAntiCheat, который защищает память игры от чтения и записи. Если же сервис не запущен, то игра отказывается соединяться с сервером и хотелось бы услышать размышления хабрасообщества на этот счет.

              Интересно как оно может защитать память через сервис? Тут на сколько я понимаю либо драйвер, либо это глупость (либо я чего-то не знаю)

              Вообще с точки зрения защиты, нет необходимости сканировать процессы и модули на наличие запрещённого кода, как показывает опыт антивирусных компаний эта игра в кошки мышки заранее проиграна. ИМХО для защиты игр необходимо анализировать дефекты окружения, например перехваты системных функций, модификацию кода игровых модулей и т.п., всё остальные «эвристики» которые разрабы пихают в защиты порой представляют из себя такое варворство, что не хочется ставить игру с такой защитой ибо это не безопасно.

              Что косается статьи, мне кажется писать чит на ассемблере крайне сложно, особенно если логики много. Лично я бы лучше написал его на С\С++ в качестве библиотеки и спроецировал бы чит собственным загрузчиком, без внесения модуля в список LDR_MODULE, для маскировки можно затереть PE заголовок и т.д.
                +1
                Поддерживаю, а если совсем уж не хочется вставлять в процесс свою длл то можно написать на том-же с/с++ базонезависимые функции и тупо налету копировать их из своего процесса в целевой. Таким образом получаются легко модифицируемые куски, а не readonly ассемблер.
                Единственное что в таком способе будет сложнее так это сделать мутацию кода налету, хоть и не невозможно. Хотя лично я сомневаюсь в целесообразности вообще каких либо мутаций проще мимикрировать под легальную длл, например взять и собрать свою длл-прокси d3d9.dll и положить в папку с игрой. Тогда не будет всякой магии с джампами из начала функции сразу за пределы модуля(модуль то свой), не придётся ничего патчить(за исключением разве что игровых методов).
                Серьёзно в любой программе присутствует куча посторонних модулей напрямую к программе не относящихся, вот например что обнаружилось в обычном np++
                левые модули
                RocketDo         D:\Programs\RocketDock\RocketDock.dll
                libgit23             C:\Program Files\TortoiseGit\bin\libgit232.dll
                zlib132             C:\Program Files\TortoiseGit\bin\zlib132.dll
                gitdll32             C:\Program Files\TortoiseGit\bin\gitdll32.dll
                Tortoi_1           C:\Program Files\TortoiseGit\bin\TortoiseGit32.dll
                Tortoise           C:\Program Files (x86)\Common Files\TortoiseOverlays\TortoiseOverlays.dll
                

                  0
                  Просто попробуйте сами, прочитать из памяти не убивая Windows-сервис.
                  А что касается чита на ассемблере, то во-первых — это не чит, во-вторых, ассемблер нужен лишь для вызова внутренних функций и что сложно, а что просто решает каждый сам для себя:
                  "call " + GetAnyGameObjectFunctionPointer,
                  "push " + argument3Pointer,
                  "push " + argument2Pointer,
                  "push " + argument1Pointer,
                  "push " + argument0Pointer,
                  "mov ecx, eax",
                  "call " + DoAnythingFunctionPointer,
                  "retn"
                  

                  Это разве сложно?
                    –1
                    Непереносимо. В x64 аргументы передаются иным способом. Да, я помню,
                    x64 очень легко получить из x86, пару бессонных ночей и готово
                    но этих бессонных ночей можно избежать, как говорится, in the first place. ;)
                      –1
                      у меня занудодетектор сейчас зашкаливает!
                      0
                      Просто попробуйте сами, прочитать из памяти не убивая Windows-сервис.
                      Очевидно сервис использует какие-то дополнительные трюки, вроде инжекта или драйвер.

                      Что вы имеете ввиду под «внутренними функциями»? Вот что вы писали в своей первый статье:
                      Определенно нам необходимо внедрить код в процесс игры, который и будет ей управлять. Для это можно модифицировать сам исполняемый файл (это очень легко сделать, но и легко определить и получить бан) или внедрить DLL (это тоже определяется очень просто), но это все не для нас. Наш подход — это внедрение кода, в главный поток процесса, получающего управление и возвращающего его обратно.
                      если я правильно понял, тут идёт речь о коде чита, а не только о перехватчике управления.

                      К таму же не совсем понятно почему именно используется трюк с перехватом управления из EndScene(), реализация требует регулярного перехвата управления?

                        0
                        Очевидно сервис использует какие-то дополнительные трюки, вроде инжекта или драйвер.

                        Не имеет значения, есть сервис — чтение невозможно из Ring3, нет сервиса — чтение есть, но играть нельзя. Вот и вся суть.
                        если я правильно понял, тут идёт речь о коде чита, а не только о перехватчике управления.

                        Вы совершенно не правильно поняли.
                        К таму же не совсем понятно почему именно используется трюк с перехватом управления из EndScene(), реализация требует регулярного перехвата управления?

                        FrameLock, знаете что такое?
                          0
                          К таму же не совсем понятно почему именно используется трюк с перехватом управления из EndScene()
                          Конкретно в WoW основная причина того, почему требуется перехват EndScene, связана не с пересчётом мира, а в том, что EndScene вызывается из «основного» потока игры. А все или большинство функций, представляющих интерес (передвижение, каст фаерболов, Lua) потоконебезопасны — будучи вызванными из других потоков, они либо просто не сработают, либо приведут к крэшу всей игры.

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

                    Самое читаемое