📜 ⬆️ ⬇️

We write a bot for MMORPG with assembler and draenei. Part 2

Hi% username%! And so, let's continue writing our bot. From past articles, we learned how to find the address of the intercepted function for DirectX 9 and 11, as well as execute arbitrary assembly code in the main stream of the game. Naturally, all these operations can be seen defending the game and you will be punished. But today, I will show you how to hide this code from protection, including from such a monster that everyone fears, like Warden. As I said, I am not a bot driver because I was not caught. 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.




For those who missed past articles, here is the content, and who read everything, we go further:
Content

  1. Part 0 - Search for a code injection point
  2. Part 1 - Implementing and executing third-party code
  3. Part 2 - Hide the code from prying eyes
  4. Part 3 - Under the gun World of Warcraft 5.4.x (Structures)
  5. Part 4 - Under the gun World of Warcraft 5.4.x (Moving)
  6. Part 5 - Under the gun World of Warcraft 5.4.x (Casting Fireball)

')
1. Reasoning on the protection of games

I would like to disperse the mystery clouds over anti-cheats, protectors and other protection accompanying the game. Because there are not so many protection options and I will list the main ones:
1. Checking game files for originality
Everything is simple here, the game requests from the server the checksums of the game files and checks with those obtained as a result of the check. In this case, you can simply modify the verification algorithm in the executable file. But everything is much more complicated if the verification algorithm is also loaded from the server, as is the case with Warden.
2. Analysis of connected DLLs
In this case, you risk only using something popular, because information about this, most likely already have the developers and as in the case of Valve Anti Cheat, unknown libraries are sent straight to the server for analysis, again, if you suspect. So your ban can only be delayed indefinitely.
3. VMT analysis of imported libraries on traps
Everything is difficult here, you can get a ban even for TeamViwer or Fraps, if the game developers want it and you can’t prove anything. And as Alexey2005 said in Part 0 - Search for the code injection point
The more paranoid the defense, the more false positives it has. Because the process constantly breaks and introduces a huge pile of everything - all sorts of keystroke interceptors, antivirus scanners, various “control centers” for video and sound, input device settings like a mouse, etc.

4. Protect game memory from read / write
This functionality has gaining popularity in Steam protection Easy Anti Cheat. In Windows, the EasyAntiCheat service starts, which protects the memory of the game from reading and writing. If the service is not running, then the game refuses to connect to the server and I would like to hear reflections of the community about this .
5. Checking certain parts of the game’s memory
This, again, deals with the well-known Warden. Although to be honest, what she just does not do. Warden loads memory check modules and a hash table from the server and compares values ​​with a table. In this way, it is easy to identify cheaters that modify the memory of a game to get advantages, such as increased speed, walking through the air and walls, and so on.
Total
From all of the above, it becomes clear that using popular programs and methods we run the risk of falling into a ban and to avoid this, we need to make our implementation difficult to determine. What we now do. First, let's write the GetFakeCommand and ObfuscateAsm methods:
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; } 

As you can see, GetFakeCommand returns an assembler command that does not change the state of registers and flags, and this list can be expanded if necessary several times, while ObfuscateAsm finds these commands in random places of our subroutine. And so we modify the address substitution code from the previous article at the place where the HookAddress is received , I will not duplicate the entire code, but only show the changed part:
 //     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); } 

The same trick can also be done with memory for argumentAddress1 and argumentAddress2. Be sure to remember the value of RandomOffset for the case of freeing the memory HookAddress .
 Memory.FreeMemory(HookAddress - RandomOffset); Memory.FreeMemory(argumentAddress1 - RandomOffsetArgs1); Memory.FreeMemory(argumentAddress2 - RandomOffsetArgs2); 

And we will change the InjectAndExecute method and as promised, we will implement a queue for a multi-threaded method call:
 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; } 

In the first while cycle, we move in our queue and look for free space by the offset offset at the same time in two reserved addresses for the arguments, if not, we start from the beginning. For masking, fake commands were added, and random offset and size for the allocated memory. While this is all, I am waiting for your comments, tips and interesting decisions.

Source: https://habr.com/ru/post/251199/


All Articles