
Hi% username%! So, let's continue writing our bot. Today we will inject our code into the gameplay (not without the help of assembler), and later we will take care that it would not be so easy to find it, because it’s punished not for cheating, but for getting caught. And to be honest to the end, even if we don’t completely embrace the process of the game, we will implement it, and only once during the entire life cycle.
But about everything in order, so I am waiting for you under the cut!
Disclaimer: The author is not responsible for your use of the knowledge gained in this article or damage as a result of their use. All information here is for educational purposes only. Especially for companies developing MMORPG, that would help them to fight with the bot. And, of course, the author of the article is not a botmaster, not a cheater, and never was.
As you remember from the last article, we found the address of our DirectX function EndScene and counted its first 5 bytes. If you forgot, then here's the content, if not, then read what we will do with them:
Content
- Part 0 - Search for a code injection point
- Part 1 - Implementing and executing third-party code
- Part 2 - Hide the code from prying eyes
- Part 3 - Under the gun World of Warcraft 5.4.x (Structures)
- Part 4 - Under the gun World of Warcraft 5.4.x (Moving)
- Part 5 - Under the gun World of Warcraft 5.4.x (Casting Fireball)
1. We plan implementation plan
Today we will inject our code into this function without harming itself. Below I will show how this will happen:

- HookAddress is an address for allocated memory in the game using the VirtualAllocEx function from kernel32.dll using WinApi
- Address is the address in memory of the DirectX function EndScene or ChainSwap
- OpCodes are original function opcodes and we need to keep them, since in the original they will be changed.
2. Implementation operation
To open a process, we call WinApi OpenProcess and enable debugging, and then we need to open the main thread of our process
')
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);
where 0x1F03FF is the access rights to the stream. Next, we allocate memory for our code and get a
HookAddress pointer to it, also reserve memory for the two arguments, argumentAddress1 and argumentAddress2, for the result resultAddress and fill all with zeros. Now, as promised a little hardcore:
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);
The assembler code above is written to
HookAddress and will transfer control to our code and according to the table, after working it out we return control to the main thread. Now I will show how to take advantage of this, let’s have:
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; }
As a result, we have the values at the addresses argumentAddress1 and argumentAddress2 should become zeros when our injection works. If you have many threads that call InjectAndExecute, then you need to provide a queue, for this I used 80 byte size, how to implement it, think for yourself. And in the next article, I will show my implementation and how to hide our code.