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 (Hinweis: mein Plugin wird als Shared Library geladen, kompiliert mit -share & amp; -fPIC), welches die Funktionen von Interesse findet (was einfach ist, da ich auf Linux bin). Um dies zu erklären, nehme ich einen speziellen Fall: Ich habe die Adresse zu einer Funktion von Interesse gefunden, deren Funktionskopf 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, einen Umweg zu erstellen (und daran zu erinnern, dass ich die Startadresse der Funktion habe), zu meiner eigenen Funktion, die ich gerne so verhalten würde:
Jetzt ist hier das Geschäft; Ich habe keine Erfahrung was das Funktionshooking betrifft, und ich habe nur eine dünne Kenntnis der Inline-Montage (nur AT & amp; T). 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 Inline-Assemblierung lernen, oder was tun? Das Beste wäre wahrscheinlich eine bereits funktionierende Wrapper-Klasse für den Linux-Umweg. Am Ende möchte ich etwas so einfaches wie dieses:
%Vor%Ich verstehe, dass ich hier keine befriedigende Antwort bekommen werde, aber ich würde mehr als eine Hilfe bei der Wegbeschreibung zu schätzen wissen, denn ich bin hier auf dünnem Eis ... 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 finde keinen Weg, dieses Wissen zu erwerben.
HINWEIS: Ich bin auch an _thiscall interessiert, aber nach dem, was ich gelesen habe, ist das nicht so schwer mit GNU-Aufrufkonvention (?)
aufzurufenSoll dieses Projekt einen "Rahmen" entwickeln, der es anderen ermöglicht, unterschiedliche Funktionen in verschiedenen Binärdateien zu haken? Oder ist es nur so, dass Sie dieses spezielle Programm, das Sie haben, anhängen müssen?
Zuerst nehmen wir an, Sie wollen die zweite Sache, Sie haben nur eine Funktion in einer Binärdatei, die Sie wollen, programmatisch 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, dies zu tun. Was Detours tut, ist Inline-Patching . Sie haben einen schönen Überblick darüber, wie es in einem PDF-Recherche-Dokument funktioniert. Die Grundidee ist, dass Sie eine Funktion haben, z. B.
%Vor%Jetzt ersetzen Sie den Anfang der Funktion durch einen CALL oder JMP und speichern die ursprünglichen Bytes, die Sie mit dem Patch überschrieben haben:
%Vor%(Beachten Sie, dass ich 5 Bytes überschrieben habe.) Nun wird Ihre Funktion mit denselben Parametern und derselben Aufrufkonvention wie die ursprüngliche Funktion aufgerufen. Wenn Ihre Funktion das Original aufrufen möchte (aber nicht muss), erstellen Sie ein "Trampolin", das 1) die ursprünglichen Anweisungen ausführt, die 2) jmps auf den Rest der ursprünglichen Funktion überschrieben wurden:
%Vor% Und das ist es, Sie müssen nur die Trampolinfunktion in der Laufzeit aufbauen, indem Sie Prozessoranweisungen ausgeben. 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 erzeugen einen 5 Byte langen Funktionsprolog, genau zu diesem Zweck, Hooking. Siehst du 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 Disassemblierungsbeispiel nicht etwas ist, das ich erfunden habe, es ist calc.exe unter Windows Vista.
Der Rest der Hook-Implementierung ist nur technische Details, aber sie können Ihnen viele Stunden des Schmerzes bringen, weil das der schwierigste Teil ist. Auch das Verhalten, das Sie in Ihrer Frage beschrieben haben:
%Vor%scheint schlimmer zu sein als das, was ich beschrieben habe (und was Detours macht), zum Beispiel möchten Sie vielleicht "nicht das Original nennen", sondern einen anderen Wert zurückgeben. 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 genug Wissen über die Technologien haben, die Sie dafür benötigen (meistens Assembly), oder nicht wissen, wie Sie das Hooking machen, schlage ich vor, dass Sie lernen, was Detours macht. 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 dieses Tutorial könnte sich als nützlich erweisen.
Wie auch immer, wenn Ihre Aufgabe darin besteht, einige Funktionen in einem bestimmten Programm anzuhängen, ist dies machbar und wenn Sie irgendwelche Probleme haben, fragen Sie einfach hier nochmal nach. Grundsätzlich können Sie eine Menge von Annahmen treffen (wie die Funktion Prolog oder verwendete Konventionen), die Ihre Aufgabe sehr erleichtern wird.
Wenn Sie ein zuverlässiges Hooking-Framework erstellen möchten, dann ist das immer noch eine völlig andere Geschichte und Sie sollten zuerst damit beginnen, einfache Hooks für einige einfache Apps zu erstellen.
Beachten Sie auch, dass diese Technik nicht betriebssystemspezifisch ist. Sie ist auf allen x86-Plattformen identisch und funktioniert sowohl unter Linux als auch unter Windows. Was ist Betriebssystemspezifisch ist, dass Sie wahrscheinlich den Speicherschutz des Codes ändern müssen ("entriegeln", damit Sie darauf schreiben können), was mit mprotect
unter Linux und mit% geschieht co_de% unter Windows. Auch die Aufrufkonventionen sind anders, das können Sie mit der richtigen Syntax in Ihrem Compiler lösen.
Ein weiteres Problem ist die "DLL-Injektion" (unter Linux wird es wahrscheinlich "shared library injection" genannt, aber der Begriff DLL injection ist allgemein 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: Was ist der LD_PRELOAD Trick? . Wenn Sie müssen tun dies in der Laufzeit, ich fürchte, Sie müssen mit gdb oder ptrace, was meiner Meinung nach ziemlich schwer ist (zumindest die ptrace Sache) zu tun. Sie können jedoch zum Beispiel lesen diesen Artikel auf Codeprojekt oder dieses Ptrace-Lernprogramm .
Ich habe auch einige nette Quellen gefunden:
Auch ein anderer 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 sich um eine exportierte Bibliotheksfunktion handelt, können Sie die gesamte Assembly / Disassembly / JMP-Funktion überspringen und einfach den Zeiger auf diese Funktion ersetzen (entweder in der Tabelle der virtuellen Funktionen oder in der exportierte Symboltabelle).