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

70853fa39f3f4d4f82bc6525a2ebc41f.pngПривет, %username%! И так, продолжим написание нашего бота. Сегодня мы внедрим наш код в игровой процесс (не без помощи ассемблера), а позже позаботимся и о том, что бы его было не так просто найти, ведь наказывают не за то что жульничаешь, а за то что попался. И если быть до конца честным то даже не совсем в сам процесс игры будем его внедрять, да и 1 раз только за весь жизненный цикл. Но обо всем по порядку, так что жду Вас под катом! Disclaimer: Автор не несет ответственности за применение вами знаний полученных в данной статье или ущерб в результате их использования. Вся информация здесь изложена только в познавательных целях. Особенно для компаний разрабатывающих MMORPG, что бы помочь им бороться с ботоводами. И, естественно, автор статьи не ботовод, не читер и никогда ими не был. Как вы помните из прошлой статьи, мы нашли адрес нашей DirectX функции EndScene и считали 5 первых ее байт. Если подзабыли, то вот содержание, если нет, то читайте что будем с ними делать: Содержание Часть 0 — Поиск точки внедрения кода Часть 1 — Внедрение и исполнение стороннего кода Часть 2 — Прячем код от посторонних глаз Часть 3 — Под прицелом World of Warcraft 5.4.x (Структуры) Часть 4 — Под прицелом World of Warcraft 5.4.x (Перемещение) Часть 5 — Под прицелом World of Warcraft 5.4.x (Кастуем фаерболл) 1. Намечаем план внедрения Сегодня мы внедрим наш код в эту функцию без ущерба для нее самой. Ниже я покажу как это будет происходить: 64c7c148bfdf40b4a06925bd8171e032.pngHookAddress — это адрес на выделенную память в процессе игры с помощью 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 (0×1F03FF, 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(_resultAddress, 0); где 0×1F03FF — права доступа к потоку. Далее мы выделяем память под наш код и получаем на нее указатель HookAddress, так же резервируем память для двух аргументов argumentAddress1 и argumentAddress2, для результата resultAddress и заполняем все нулями. Теперь как и обещал немножко хардкора: var asmLine = new List { «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 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(argumentAddress1, dwAddress); while (Memory.Read(argumentAddress1) > 0) { Thread.Sleep (1); } byte[] result = new byte[0]; if (returnValue) { result = Memory.ReadBytes (Memory.Read(argumentAddress2), returnLength); } Memory.Write(argumentAddress2, 0); Memory.FreeMemory (dwAddress);

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

© Habrahabr.ru