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

от автора

Привет, %username%!

Итак, продолжим написание нашего бота. Сегодня мы внедрим наш код в игровой процесс (не без помощи ассемблера), а позже позаботимся и о том, что бы его было не так просто найти, ведь наказывают не за то что жульничаешь, а за то что попался. И если быть до конца честным то даже не совсем в сам процесс игры будем его внедрять, да и 1 раз только за весь жизненный цикл.

Но обо всем по порядку, так что жду Вас под катом!


Disclaimer: Автор не несет ответственности за применение вами знаний полученных в данной статье или ущерб в результате их использования. Вся информация здесь изложена только в познавательных целях. Особенно для компаний разрабатывающих MMORPG, что бы помочь им бороться с ботоводами. И, естественно, автор статьи не ботовод, не читер и никогда ими не был.


Как вы помните из прошлой статьи, мы нашли адрес нашей DirectX функции EndScene и считали 5 первых ее байт. Если подзабыли, то вот содержание, если нет, то читайте что будем с ними делать:

Содержание

  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. Намечаем план внедрения

Сегодня мы внедрим наш код в эту функцию без ущерба для нее самой. Ниже я покажу как это будет происходить:

  • HookAddress — это адрес на выделенную память в процессе игры с помощью WinApi функции VirtualAllocEx из kernel32.dll
  • Address — это адрес в памяти DirectX функции EndScene или ChainSwap
  • OpCodes — это оригинальные опкоды функции и их нам нужно сохранить, т.к. в оригинале они будут изменены.

2. Операция внедрения

Что бы открыть процесс мы вызовем WinApi OpenProcess и разрешим отладку, а затем нам необходимо открыть главный поток, нашего процесса

var ProcessHandle = OpenProcess(processId); Process.EnterDebugMode(); var dwThreadId = Process.GetProcessById(dwProcessId).Threads[0].Id; var ThreadHandle = OpenThread(0x1F03FF, false, (uint)dwThreadId); var HookAddress = Memory.AllocateMemory(6000); var argumentAddress1 = Memory.AllocateMemory(80); Memory.WriteBytes(argumentAddress1, new byte[80]); var argumentAddress2 = Memory.AllocateMemory(BufferSize); Memory.WriteBytes(argumentAddress2, new byte[80]); var resultAddress = Memory.AllocateMemory(4); Memory.Write<int>(_resultAddress, 0); 

где 0x1F03FF — права доступа к потоку. Далее мы выделяем память под наш код и получаем на нее указатель HookAddress, так же резервируем память для двух аргументов argumentAddress1 и argumentAddress2, для результата resultAddress и заполняем все нулями. Теперь как и обещал немножко хардкора:

var asmLine = new List<string> {     "pushfd",     "pushad",     "mov edx, 0",     "mov ecx, " + resultAddress,     "mov [ecx], edx",     "@loop:",     "mov eax, [ecx]",     "cmp eax, " + 80,     "jae @end",     "mov eax, " + argumentAddress1,     "add eax, [ecx]",     "mov eax, [eax]",     "test eax, eax",     "je @out",     "call eax",     "mov ecx, " + resultAddress,     "mov edx, " + argumentAddress2,     "add edx, [ecx]",     "mov [edx], eax",     "mov edx, " + argumentAddress1,     "add edx, [ecx]",     "mov eax, 0",     "mov [edx], eax",     "@out:",     "mov eax, [ecx]",     "add eax, 4",     "mov [ecx], eax",     "jmp @loop",     "@end:",     "popad",     "popfd" }; Memory.Asm = new ManagedFasm(ProcessHandle); Memory.Asm.Clear(); foreach (var str in asmLine) {     Memory.Asm.AddLine(str); } Memory.Asm.Inject(HookAddress); var length = (uint) Memory.Asm.Assemble().Length; Memory.WriteBytes(HookAddress + length, OpCodes); Memory.Asm.Clear(); Memory.Asm.AddLine("jmp " + (Address + OpCodes.Length)); Memory.Asm.Inject((uint)((HookAddress + length) + OpCodes.Length)); Memory.Asm.Clear(); Memory.Asm.AddLine("jmp " + HookAddress); for (var k = 0; k <= ((OpCodes.Length - 5) - 1); k++) {     Memory.Asm.AddLine("nop"); } Memory.Asm.Inject(Address); 

Ассемблерный код выше, записывается в HookAddress и будет передавать управление нашему коду и согласно таблице, после его отработки мы возвращаем управление в главный поток. Теперь я покажу как этим воспользоваться, пусть имеем:

public byte[] InjectAndExecute(IEnumerable<string> asm, bool returnValue = false, int returnLength = 0) {     Memory.Asm.Clear();     foreach (var str in asm)     {           Memory.Asm.AddLine(str);     }     dwAddress = Memory.AllocateMemory(Memory.Asm.Assemble().Length + 60);     Memory.Asm.Inject(dwAddress);     Memory.Write<uint>(argumentAddress1, dwAddress);     while (Memory.Read<int>(argumentAddress1) > 0)     {         Thread.Sleep(1);     }     byte[] result = new byte[0];     if (returnValue)     {          result = Memory.ReadBytes(Memory.Read<uint>(argumentAddress2), returnLength);     }     Memory.Write<int>(argumentAddress2, 0);     Memory.FreeMemory(dwAddress);      return result; } 

В итоге у нас значения по адресам argumentAddress1 и argumentAddress2 должны стать нулями когда наша инъекция отработает. Если у вас много потоков которые вызывают InjectAndExecute, то нужно предусмотреть очередь, для этого я и использовал 80 байт размер, как его реализовать, подумайте сами. А в следующей статье, я покажу свою реализацию и как прятать наш код.

ссылка на оригинал статьи http://habrahabr.ru/post/251149/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *