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

  • Tutorial
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 assembler code in the main stream of the game. Naturally, all these operations can be noticed by the defense of the game and you will be punished. But today, I will show how to hide this code from protection, including from such a monster that everyone is afraid of, like Warden. As I said, I'm not a bot driver because I have not been 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 resulting from their use. All information presented here is for educational purposes only. Especially for companies developing MMORPG to help them deal with bots. And, of course, the author of the article is not a bot driver, not a cheater, and he never was.




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

  1. Part 0 - Finding a code injection point
  2. Part 1 - Implementation and execution of third-party code
  3. Part 2 - Hide the code from prying eyes
  4. Part 3 - Under the Sight of World of Warcraft 5.4.x (Structures)
  5. Part 4 - Under the Sight of World of Warcraft 5.4.x (Moving)
  6. Part 5 - Under the Sight of World of Warcraft 5.4.x (Custom Fireball)


1. Reasoning on the protection of games

I would like to disperse the clouds of mystery over anti-cheats, protectors and other protection associated with the game. Because there are not so many protection options and I will list the main ones:
1. Checking the game files for originality
Everything is simple here, the game requests the checksums of the game files from the server and checks against those that result from 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 Most likely, the developers already have information about this, and as is the case with Valve Anti Cheat, unknown libraries are sent directly to the server for analysis, again, if it causes suspicion. So your ban can only be delayed indefinitely.
3. VMT analysis of imported libraries for intercepts
Everything is complicated here, you can get a ban even for TeamViwer or Fraps if the game developers want it and you won’t prove anything. And as Alexey2005 said in Part 0 - Finding a code injection point
The more paranoid the defense, the more false positives it has. Because a huge pile of everything is constantly breaking in and being introduced into the process - all kinds of keyboard click interceptors, anti-virus scanners, all kinds of “control centers” for video and sound, input device settings components like a mouse, etc.

4. Protection of game memory from reading / writing
This functionality has gained popularity in Steam protection Easy Anti Cheat. Windows runs the EasyAntiCheat service, which protects the game’s memory 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 the thoughts of the habrasociety in this regard .
5. Checking certain sections of the game’s memory
This, again, is not without the well-known Warden. Although to be honest, what is she just not doing. Warden loads memory check modules and a hash table from the server and compares the values ​​with the table. In this way, it is easy to identify cheaters that modify the game’s memory to gain benefits, such as increased speed, walking through air and walls, and more.
Summary
From all the above, it becomes clear that using popular programs and methods we are very at risk of getting into a ban and to avoid this, we need to make our implementation difficult to determine. What will we do now. First, write the GetFakeCommand and ObfuscateAsm methods:
internal static string GetFakeCommand()
{
    var list = new List { "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 ObfuscateAsm(IList 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 several times if necessary, and ObfuscateAsm will poke these commands in random places in our routine. And so we modify the address substitution code from the last article at the place of receipt of HookAddress , 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 feint can be done with memory for arguments argumentAddress1 and argumentAddress2. Be sure to remember the value of RandomOffset for the case of freeing HookAddress memory .
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 the queue for the multi-threaded method call:
public byte[] InjectAndExecute(IEnumerable 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(argumentAddress1 + offset) != 0 || Memory.Read(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(argumentAddress1, dwAddress + offset);
    }
    while (Memory.Read(argumentAddress1 + offset) > 0)
    {
        Thread.Sleep(1);
    }
    byte[] result = new byte[0];
    if (returnValue)
    {
         result = Memory.ReadBytes(Memory.Read(argumentAddress2 + offset), returnLength);
    }
    Memory.Write(argumentAddress2 + offset, 0);
    Memory.FreeMemory(dwAddress - randomValue);
    return result;
}

In the first while loop, we move in our turn and look for free space at offset at the same time in two reserved addresses for arguments, if not found, then start from the beginning. To mask, fake commands and random offset and size for the allocated memory were added. That's all for now, waiting for your comments, tips and interesting solutions.

Also popular now: