2012-05-02 19 views
8

Ich programmiere Erweiterungen für ein Spiel, das eine API für (uns) Modder bietet. Diese API bietet eine Vielzahl von Dingen, hat jedoch eine Einschränkung. Die API ist nur für die 'Engine', was bedeutet, dass alle Modifikationen (Mods), die basierend auf der Engine veröffentlicht wurden, keine (modspezifische) API anbieten. Ich habe einen 'Signature Scanner' erstellt (Anmerkung: mein Plugin wird als shared library geladen, kompiliert mit -share & -fPIC), welches die Funktionen von Interesse findet (was einfach ist, da ich auf Linux bin). Um das zu erklären, nehme ich einen speziellen Fall: Ich habe die Adresse zu einer Funktion von Interesse gefunden, die Funktion Header ist sehr einfach int * InstallRules(void);. Es nimmt ein nichts (void) und gibt einen Integer-Zeiger (auf ein Objekt von meinem Interesse) zurück. Nun, was ich tun möchte, ist es, einen Umweg zu schaffen (und denken Sie daran, dass ich die Startadresse der Funktion haben), um meine eigene Funktion, die Ich mag würde, so etwas verhalten:Umleitung und GCC Inline Assembly (Linux)

void MyInstallRules(void) 
{ 
    if(PreHook() == block) // <-- First a 'pre' hook which can block the function 
     return; 
    int * val = InstallRules(); // <-- Call original function 
    PostHook(val); // <-- Call post hook, if interest of original functions return value 
} 

jetzt Das ist der Deal; Ich habe keine Erfahrung, was auch immer mit Funktionshaken, und ich habe nur eine dünne Kenntnis der Inline-Montage (AT & T nur). Die vorgefertigten Umleitungspakete im Internet sind nur für Windows oder verwenden eine ganz andere Methode (d. H. Lädt eine DLL vor, um die ursprüngliche zu überschreiben). Also im Grunde genommen; Was sollte ich tun, um auf Kurs zu kommen? Sollte ich über Aufrufkonventionen lesen (in diesem Fall cdecl) und etwas über die Inline-Assemblierung erfahren oder was tun? Das Beste wäre wahrscheinlich eine bereits funktionierende Wrapper-Klasse für den Linux-Umweg. Am Ende würde ich etwas so einfache wie dies mag:

void * addressToFunction = SigScanner.FindBySig("Signature_ASfs&43"); // I've already done this part 
void * original = PatchFunc(addressToFunction, addressToNewFunction); // This replaces the original function with a hook to mine, but returns a pointer to the original function (relocated ofcourse) 
// I might wait for my hook to be called or whatever 
// .... 

// And then unpatch the patched function (optional) 
UnpatchFunc(addressToFunction, addressToNewFunction); 

Ich verstehe, dass ich nicht in der Lage sein werde, hier eine völlig befriedigende Antwort zu bekommen, aber ich würde mehr als Hilfe bei den Richtungen schätzen zu nehmen , weil ich hier auf dünnem Eis bin ... Ich habe über Umwege gelesen, aber es gibt kaum eine Dokumentation (speziell für Linux), und ich denke, ich möchte ein sogenanntes "Trampolin" implementieren, aber ich kann nicht scheinen einen Weg finden, wie man dieses Wissen erwerben kann.

HINWEIS: Ich bin auch in _thiscall interessiert, aber von dem, was ich gelesen habe, das ist nicht so schwer, mit GNU rief Aufrufkonvention

+0

Nur um es raus zu werfen, haben Sie die nicht-technische Lösung in Betracht gezogen, d. H. * Bitten * den Autor, eine API zur Verfügung zu stellen? –

+0

@KarlBielefeldt Das wäre schwer ... GoldSrc (für Half-Life) ist jetzt ungefähr 12 Jahre alt, denke ich? –

Antwort

12

Ist das Projekt einen „Rahmen“ zu entwickeln, die (?) wird anderen erlauben, verschiedene Funktionen in verschiedenen Binärdateien zu haken? Oder ist es nur das Sie müssen dieses spezifische Programm, das Sie haben, Haken?

Zuerst nehmen wir an, Sie wollen die zweite Sache, Sie haben nur eine Funktion in einer Binärdatei, die Sie wollen, programmgesteuert und zuverlässig. Das Hauptproblem bei dieser universellen Vorgehensweise ist, dass es ein sehr hartes Spiel ist, dies zuverlässig zu tun, aber wenn Sie bereit sind, Kompromisse einzugehen, dann ist es definitiv machbar. Nehmen wir an, das ist x86-Sache.

Wenn Sie eine Funktion haken möchten, gibt es mehrere Möglichkeiten, wie Sie es tun können. Was Detours tut, ist Inline-Patching. Sie haben einen schönen Überblick darüber, wie es in einem Research PDF document funktioniert. Die Grundidee ist, dass Sie eine Funktion haben, z.B.

00E32BCE /$ 8BFF   MOV EDI,EDI 
00E32BD0 |. 55    PUSH EBP 
00E32BD1 |. 8BEC   MOV EBP,ESP 
00E32BD3 |. 83EC 10  SUB ESP,10 
00E32BD6 |. A1 9849E300 MOV EAX,DWORD PTR DS:[E34998] 
... 
... 

Jetzt haben Sie den Beginn der Funktion mit einem CALL oder JMP an Ihre Funktion ersetzen und die ursprünglichen Bytes speichern, die Sie mit dem Patch irgendwo überschrieben:

00E32BCE /$ E9 XXXXXXXX JMP MyHook 
00E32BD3 |. 83EC 10  SUB ESP,10 
00E32BD6 |. A1 9849E300 MOV EAX,DWORD PTR DS:[E34998] 

(Beachten Sie, dass ich überschrieb 5 Bytes .) Nun wird Ihre Funktion mit denselben Parametern und derselben Aufrufkonvention wie die ursprüngliche Funktion aufgerufen.Wenn Ihre Funktion will das Original nennen (aber es muss nicht), erstellen Sie ein „Trampolin“, dass 1) die ursprünglichen Anweisungen ausgeführt wird, die 2) jmps der ursprünglichen Funktion der restlichen überschrieben wurden:

Trampoline: 
    MOV EDI,EDI 
    PUSH EBP 
    MOV EBP,ESP 
    JMP 00E32BD3 

Und das ist es, Sie müssen nur die Trampolin-Funktion in der Laufzeit durch Ausgeben von Prozessoranweisungen erstellen. Der schwierige Teil dieses Prozesses besteht darin, dass er zuverlässig funktioniert, für jede Funktion, für jede Aufrufkonvention und für verschiedene Betriebssysteme/Plattformen. Eines der Probleme besteht darin, dass die 5 Byte, die Sie überschreiben möchten, in der Mitte einer Anweisung enden. Um "Ende der Anweisungen" zu erkennen, müssten Sie grundsätzlich einen Disassembler einbauen, da zu Beginn der Funktion eine Anweisung vorhanden sein kann. Oder wenn die Funktion selbst kürzer als 5 Bytes ist (eine Funktion, die immer 0 zurückgibt, kann als XOR EAX,EAX; RETN geschrieben werden, was nur 3 Bytes ist).

Die meisten aktuellen Compiler/Assembler produzieren einen 5 Byte langen Funktionsprolog, genau zu diesem Zweck, Hooking. Siehst du das MOV EDI, EDI? Wenn du dich fragst: "Warum zum Teufel bewegen sie Edi nach Edi? Das macht nichts!" Sie sind absolut richtig, aber das ist der Zweck des Prologs, genau 5 Bytes lang zu sein (nicht in der Mitte einer Anweisung zu enden). Beachten Sie, dass das Disassembly-Beispiel nicht etwas ist, das ich erfunden habe, es ist calc.exe unter Windows Vista.

Der Rest der Haken Implementierung ist nur technische Details, aber sie können Ihnen viele Stunden Schmerzen bringen, denn das ist der schwierigste Teil ist. Auch das Verhalten beschrieben Sie in Ihrer Frage:

void MyInstallRules(void) 
{ 
    if(PreHook() == block) // <-- First a 'pre' hook which can block the function 
     return; 
    int * val = InstallRules(); // <-- Call original function 
    PostHook(val); // <-- Call post hook, if interest of original functions return value 
} 

scheint schlimmer als das, was ich beschrieben (und was Detours der Fall ist), zum Beispiel möchten Sie vielleicht „das Original nicht nennen“, sondern etwas anderen Wert zurück. Oder rufen Sie die ursprüngliche Funktion zweimal auf. Lassen Sie stattdessen Ihren Hookhandler entscheiden, ob und wo er die ursprüngliche Funktion aufrufen wird. Auch dann brauchen Sie nicht zwei Handler-Funktionen für einen Hook.

Wenn Sie nicht über genügend Wissen über die Technologien haben Sie sich für diese (meist Montage) benötigen, oder nicht wissen, wie das Einhaken zu tun, ich schlage vor, Sie untersuchen, was Detours tut. Haken Sie Ihre eigene Binärdatei und nehmen Sie einen Debugger (zum Beispiel OllyDbg), um auf Assemblerebene zu sehen, was genau gemacht wurde, welche Anweisungen platziert wurden und wo. Auch this tutorial könnte sich als nützlich erweisen.

Wie auch immer, wenn Sie Ihre Aufgabe, einige Funktionen in einem bestimmten Programm Haken ist, dann ist dies machbar und wenn Sie Probleme haben, fragen Sie einfach hier wieder. Im Grunde können Sie eine Menge Annahmen treffen (wie die Funktion Prolog oder verwendete Konventionen), die Ihre Aufgabe viel einfacher machen.

Wenn Sie einige zuverlässige Hakenrahmen schaffen wollen, dann ist noch eine ganz andere Geschichte, und Sie sollten zunächst durch die Erstellung von einfachen Haken für einige einfache Anwendungen beginnen.

Beachten Sie auch, dass diese Technik nicht OS spezifisch ist, ist es das gleiche auf allen x86-Plattformen, wird es auf Linux und Windows. Was ist Betriebssystemspezifisch ist, dass Sie wahrscheinlich Speicherschutz des Codes ändern ("entsperren" es, so dass Sie darauf schreiben können), die mit unter Linux und mit VirtualProtect unter Windows erfolgt ist. Auch die Aufrufkonventionen sind anders, das können Sie mit der richtigen Syntax in Ihrem Compiler lösen.

Ein weiteres Problem ist "DLL-Injektion" (unter Linux wird es wahrscheinlich "Shared Library-Injektion" genannt, aber der Begriff DLL-Injektion ist weithin bekannt). Sie müssen Ihren Code (der den Haken ausführt) in das Programm einfügen. Mein Vorschlag ist, dass, wenn es möglich ist, einfach die Umgebungsvariable LD_PRELOAD verwendet wird, in der Sie eine Bibliothek angeben können, die direkt vor der Ausführung in das Programm geladen wird.Dies wurde in SO viele Male beschrieben, wie hier: What is the LD_PRELOAD trick?. Wenn Sie müssen tun dies in der Laufzeit, ich fürchte, Sie müssen mit gdb oder ptrace, die meiner Meinung nach ziemlich schwer ist (zumindest die ptrace Sache) zu tun. Sie können jedoch zum Beispiel this article on codeproject oder this ptrace tutorial lesen.

Ich fand auch ein paar netten Ressourcen:

Noch ein weiterer Punkt: Dieses "Inline-Patching" ist nicht die einzige Möglichkeit, dies zu tun. Es gibt sogar einfachere Wege, z.B. Wenn die Funktion virtuell ist oder wenn es eine Bibliothek exportierte Funktion ist, können Sie die gesamte Assembly/Disassembly/JMP-Sache überspringen und einfach den Zeiger auf diese Funktion (entweder in der Tabelle der virtuellen Funktionen oder in der exportierten Symboltabelle) ersetzen.

+0

Deshalb liebe ich StackOverflow. Ich hätte keine bessere Antwort verlangen können! Ich werde definitiv einen ausführlicheren Kommentar geben, aber ich möchte zuerst alle Ihre Links lesen, aber ich musste nach der "DLL-Injektion" fragen. Ist das überhaupt relevant für mich? Da mein Plugin eine dll/so ist, welche das 'mod'/Spiel lädt (dynamisch), bedeutet das, dass ich meinen 'hooking' Code einfach in meine Bibliothek setzen kann anstatt LD_PRELOAD zu benutzen? –

+0

In Ordnung, dann haben Sie ein Problem weniger. Wie auch immer, welches Spiel ist das? – kuba

+0

Ich programmiere so genannte "Metamod-Plugins" für das bekannte FPS-Spiel Counter-Strike (das eine Engine namens GoldSrc verwendet, die ~ 12 Jahre alt ist). Der frustrierende Teil ist, dass ich fast alles habe, was ich brauche. Ich habe buchstäblich den Quellcode der Funktion, die ich haken möchte, ich kann seine Adresse dynamisch finden und ich kenne seine Aufrufkonvention, also bin ich ziemlich verzweifelt, um dieses letzte Teil des Puzzles zu lösen, aber ich bin es nicht bereit, 2 Monate damit zu verbringen, durch Montage Bücher zu graben, einer nach dem anderen ... –