MW2 Bot in C# – Teil 8

Da ich gut aus der Exkursion zurückgekehrt bin wird es gleich einen weiteren Artikel in der MW2 Bot Serie geben. In diesem wird es sich um die, schon in Teil 0 angesprochene, InlineASM Verwendung unter C# drehen. Dabei wird die SendCommandToConsole-Funktion verwendet, welche es erfordert den Stack zu fixxen. Zudem ist diese Funktion relativ unkomplex und klein, daher kann man sie gut als “Modell” verwenden ;-)

Aber bevor ich über die mehr oder weniger komplexen Vorgänge rede, hier erstmal die Funktion in C++:

VOID SendCommandToConsole( CHAR* szCommand )
{
	DWORD dwSend = (DWORD)0x004D3EA0;  // Die Adresse von SendCommandToConsole
 
	_asm push	szCommand // Wir pushen unseren Command, bzw die Adresse, auf den Stack
	_asm push	0 // Wir pushen eine null auf den Stack (Verwendung unbekannt)
	_asm call	dwSend // Da wir alle erforderlichen Sachen auf dem Stack haben wird die Funktion angesprungen
	_asm add	esp, 0x8 // Und es wird der Stack gefixxt
}

Sieht doch ganz überschaubar aus :P Zu erwähnen ist noch, dass auf diesen ASM-Code noch ein RET folgen muss, was aber in diesem Fall vom Compiler später übernommen wird.

Um ASM-Code von C# auszuführen braucht es mindestens folgende Schritte:

  1. Per VirtualAlloc Platz für den Code schaffen. Dabei muss der Speicher auf PAGE_EXECUTE_READWRITE gesetzt werden! Daher ist auch die Verwendung von Marshal.AllocHGlobal nicht möglich, es muss die reine WinAPI verwendet werden.
  2. Den Code zu dem reservierten Speicher kopieren.
  3. Ein Delegate mit dem Typ void auf den Anfang des Codes setzen
  4. Eine Funktion des Types des Delegates erstellen und aufrufen.

Um diese Schritte zu automatisieren habe ich mir einen ASMHelper geschrieben, welcher im Grunde genommen diese Schritte ausführt. Der Code wird in einem Byte-Array übergeben, dann wird Speicher reserviert und kopiert und die Funktion schließlich in ein Dictionary eingetragen.

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
 
const uint PAGE_EXECUTE_READWRITE = 0x40;
const uint MEM_COMMIT = 0x1000;
 
public static bool AddFunction(String sFunctionName, byte[] arFunctionCode)
        {
             // Die Klasse hat keinen Konstruktor da statisch
             // Also wird bei der ersten Verwendung das Dictionary angelegt
            if (dicFunctions == null)
                dicFunctions = new Dictionary();
 
            // Wenn diese Funktion noch nicht in der Liste ist...
            if (dicFunctions.ContainsKey(sFunctionName))
                return false;
 
            // Wir reservieren den Speicher für exakt diese Codelänge
            IntPtr ptrResult = VirtualAlloc(IntPtr.Zero, (uint)arFunctionCode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            // Und kopieren den Code an die Stelle
            Marshal.Copy(arFunctionCode, 0, ptrResult, arFunctionCode.Length);
 
            // Debugausgaben sind was schönes :D
            MessageBox.Show("Located at " + ptrResult.ToString("x"));
            // Schließlich wird der Pointer zu der Funktion dem Dictionary
            // in Abhänigkeit von dem Namen zugeordnet
            dicFunctions.Add(sFunctionName, ptrResult);
 
            return true;
        }

Damit hätten wir die ersten beiden Schritte schon geschafft. Wenn wir die Funktion nun aufrufen möchten, so verwenden wir ein Delegate welches auf den Anfang der Funktion (den reservierten Speicher) zeigt. Dazu verwende ich die Hilfsfunktion “GetFunction<T>”:

// In ASMHelper
public static T GetFunction(String sFunctionName) where T : class
        {
            return MemoryHelper.GetFunctionFromAddress(dicFunctions[sFunctionName]);
        }
 
// In Main
private delegate void VoidReturner(); // Ein Delegate vom Type VOID
 
ASMHelper.AddFunction("ConsoleSend", arFinal); // Wir adden den Code zum Dictionary
// Holen es und als eine Funktion vom Type VOID
VoidReturner SendToConsole = ASMHelper.GetFunction("ConsoleSend");
// Und so wirds dann aufgerufen!
SendToConsole();

Klingt doch logisch und einfach, nicht? :D

Doch was ist jetzt mit dem mysteriösen arFinal? Richtig geraten, es enthält unseren ASM-Code in Opcodes. Am Anfang wird erst der Command in unmanaged Speicher kopiert und schließlich die Inline-ASM Opcodes dafür gebildet!

// Command in unmanaged Speicher kopieren:
// Der Command dass sich die Bots nicht mehr bewegen
byte[] Begin = Encoding.ASCII.GetBytes("testclients_domove 0");
 
// Speicher dafür reservieren
IntPtr TempPtr = VirtualAlloc(IntPtr.Zero, (uint)Begin.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// Das Byte-Array mit dem Command an den reservierten Ort kopieren
Marshal.Copy(Begin, 0, TempPtr, Begin.Length);
 
// Und schließlich diesen Zeiger auf den Command in ein Byte[4] umwandeln.
byte[] TempAddress = BitConverter.GetBytes(TempPtr.ToInt32());
 
// Die OpCodes bilden:
byte[] arFinal = {0xb8, TempAddress[0],TempAddress[1],TempAddress[2],TempAddress[3], // mov eax, TempPtr
                                     0x50, // PUSH EAX
                                     0x6A, 0x00, // PUSH 0
                                     0xB8, 0xA0,0x3E,0x4D,0x00, // mov eax, 0x004D3EA0
                                     0xFF, 0xD0, // CALL EAX
                                     0x83, 0xC4, 0x08, // ADD ESP 0x08
                                     0xC3 // RET
                                 };

Alle Klarheiten beseitigt? Es wird erst der Mov-Opcode 0xB8 verwendet, gefolgt von dem Zeiger auf unseren Command. Dann wird dieser Command auf den Stack gepushed gefolgt von einer 0. Dann wird wieder MOV verwendet um die Adresse von SendCommandToConsole nach EAX zu schrieben und schließlich wird EAX gecalled. Dann wird über ADD ESP 8 der Stack gefixxt und über ein RET das ganze abgeschlossen.

Experten werden jetzt sagen: “Man, warum den Umweg über die Register bei dem Command und der Funktionsaddresse???!!!“. Ich weis, dass das nicht elegant ist, aber ich hatte keine Lust mich durch noch komplexere Opcodes zu wühlen, daher habe ich die mir schon “bekannten” Opcodes genutzt. Wer das allerdings tun will, hier ist eine Referenz zu finden…

Fazit: Es ist auf jeden Fall möglich solche Spielchen zu machen und somit, bis auf das CLRHosting, auf C++ zu verzichten. Aber mal ehrlich, mir ist das “Aufwand/Effizienz” Verhältnis viel zu groß. Bis ich diesen Code am laufen hatte sind gut und gerne sechs Stunden vergangen.

Greez Easy

10 people like this post.

Andere Artikel zu dieser Serie

  1. MW2 Bot in C# – Teil 9
  2. MW2 Bot in C# – Teil 8 (This post)
  3. MW2 Bot in C# – Teil 7
  4. MW2 Bot in C# - Teil 6
  5. MW2 Bot in C# - Teil 5
  6. MW2 Bot in C# - Teil 4
  7. MW2 Bot in C# - Teil 3
  8. MW2 Bot in C# - Was will er eigentlich?
  9. MW2 Bot in C# - Teil 2
  10. MW2 Bot in C# - Teil 1
  11. MW2 Bot in C# - Teil 0
    • IRET
    • 12. Jul. 2011 2:11pm

    Klar geht das so auch, aber 1. springst du da aus dem managed Bereich raus und 2. Machst du nicht mehr als bereits assemblierten Code in den Speicher zu schreiben und diesen dann ausführen zu lassen. Somit stelle ich mir hier die Frage wieso man also etwas in C# schreiben soll und dann doch “inline-Assembler” verwenden sollte. Netter Artikel, aber der Sinn von ASM in einer managed Anwendung ist führ mich nicht sichtbar ;) . Da kann man ja gleich sein Programm in C oder ASM schreiben (ja Delphi usw sind auch noch dabei) und dann einfach per RunPE in den Speicher laden ;) . Wäre so ziemlich das selbe “inline-ASM” wie du es gezeigt hast, außer dass da noch dazu kommt, dass diverse verlinkte DLLs geladen werden und die Platzhalter ersetzt werden.
    Naja ich freue mich auf den nächsten Artikel und nimm mein Kommentar nicht persönlich ;) . Bei mir sträubt sich nur alles wenn ich höre wie jemand OP-Codes in einer managed Anwendung verwendet :P .

      • Easysurfer
      • 13. Jul. 2011 6:06pm

      Mir geht es bei diesem Spass-Projekt im Grunde zu “beweisen”, dass man Gamehacks auch weitgehend ohne C++ schreiben kann… Aber natürlich darf ich Dir in allen Punkten zustimmen ;-)
      Natürlich hätte ich die native DLL laden können und dann ein Delegate drauf erzeugen, was aufs selbe hinaus gelaufen wäre. Wie schon im Post gesagt, über den Nutzen und die Effizienz lässt sich streiten, und ich bin mir sicher dass ich diese Diskussion verlieren würde :P

    • AJ20
    • 23. Jul. 2011 1:31am

    Hallo danke für diese coole Tut!

    Darf ich fragen, beim durschauen vom source haben ich keine Aimbot function gefunden bzw SetCrosshairOnEnemy o.ä.

    Habe ich mich versehen?

    Lieben Gruß

    Arthur

      • Easysurfer
      • 24. Jul. 2011 4:03pm

      Dieses Tutorial behandelt nur die SendToConsole-Funktion. In dem nächsten Teil (Teil 9) wird eine Aimbot-Funktion erstellt.

      Grüße Easy

  1. 11. Jul. 2011
    TrackBack von: MW2 Bot in C# – Teil 8


× 6 = vierzig zwei