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 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.
weiterlesen : http://easysurfer.me/wordpress/?p=198
Quelle : http://easysurfer.me
[spoiler]
Um ASM-Code von C# auszuführen braucht es mindestens folgende Schritte:
- 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.
- Den Code zu dem reservierten Speicher kopieren.
- Ein Delegate mit dem Typ void auf den Anfang des Codes setzen
- 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?
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
[/spoiler]