2010-11-19 14 views
4

Gibt es eine mehr oder weniger zuverlässige Möglichkeit zu sagen, ob Daten an einer Stelle im Speicher ein Anfang einer Prozessoranweisung oder andere Daten sind?x86 Möglichkeit, Anweisungen aus Daten zu geben

Zum Beispiel kann mit relativen E8 3F BD 6A 00call Befehl (E8) sein Offset von 0x6ABD3F, oder es könnte drei Bytes von Daten sein, die zu einer anderen Anweisung, gefolgt von push 0 (6A 00).

Ich weiß, die Frage klingt albern und es gibt wahrscheinlich keinen einfachen Weg, aber möglicherweise Befehlssatz wurde mit diesem Problem im Hinterkopf entwickelt und vielleicht einige einfache Code Untersuchung + -100 Bytes um den Standort kann eine Antwort geben, die sehr wahrscheinlich ist richtig.

Ich möchte dies wissen, weil ich den Code des Programms scan und alle Anrufe zu einer Funktion mit Anrufen zu meinem Ersatz ersetze. Es funktioniert so weit, aber es ist nicht unmöglich, dass irgendwann, wenn ich die Anzahl der Funktionen, die ich ersetze, anwachsen, einige Daten genau wie ein Funktionsaufruf an diese genaue Adresse aussehen und ersetzt werden, was ein Programm dazu bringt brechen in einer unerwarteten Art und Weise. Ich möchte die Wahrscheinlichkeit dafür reduzieren.

+1

Erinnert mich an einen alten Disassembler, den ich benutzt habe. Es führte 9 verschiedene Analysedurchläufe durch den Maschinencode durch, um zu versuchen, den Code von den Daten zu trennen. Und immer noch oft falsch. –

Antwort

1

Es ist unmöglich, Daten von Anweisungen im Allgemeinen zu unterscheiden, und dies liegt an von Neumann architecture. Es ist hilfreich, den Code zu analysieren, und Disassemblierungswerkzeuge tun dies. (This kann hilfreich sein. Wenn Sie IDA Pro nicht verwenden können, verwenden Sie ein anderes Demontagewerkzeug.)

1

Einfacher Code hat eine sehr spezifische Entropie, so dass es von den meisten Daten relativ einfach ist. Es ist jedoch ein probabilistischer Ansatz, aber ein groß genug Puffer von einfachem Code kann erkannt werden (insbesondere Compiler-Ausgabe, wenn Sie auch Muster wie Anfang einer Funktion erkennen können).

Auch einige Opcodes sind für die Zukunft reserviert, andere sind nur im Kernel-Modus verfügbar. In diesem Fall können Sie es tun, indem Sie sie kennen und wissen, wie man die Befehlslängen berechnet (Sie könnten eine von Z0mbie dafür geschriebene Routine ausprobieren).

5

Wenn es Ihr Code ist (oder ein anderer, der Verbindungs- und Debug-Informationen beibehält), ist es am besten, Symbol-/Verschiebungstabellen in der Objektdatei zu scannen. Sonst gibt es keinen zuverlässigen Weg, um zu bestimmen, ob ein Byte eine Kehre oder Daten ist.

Möglicherweise ist die effizienteste Methode zum Qualifizieren von Daten die rekursive Zerlegung. I. e. Disassembly-Code von Entypoint und von allen gefundenen Sprungzielen. Aber das ist nicht absolut zuverlässig, weil es Sprungtabellen nicht durchläuft (Sie können versuchen, einige Heuristiken dafür zu verwenden, aber das ist auch nicht absolut zuverlässig).

Lösung für Ihr Problem wäre Patch-Funktion ersetzt sich selbst: Überschreiben Sie seinen Anfang mit Sprung zu Ihrer Funktion.

2

Leider gibt es keine 100% zuverlässige Methode, um Code von Daten zu unterscheiden. Aus Sicht der CPU ist Code nur Code, wenn ein Sprungopcode den Prozessor dazu bringt, die Bytes so auszuführen, als ob sie Code wären. Sie könnten versuchen, eine Kontrollflussanalyse zu erstellen, indem Sie mit dem Programmeintrittspunkt beginnen und alle möglichen Ausführungspfade befolgen, aber dies kann bei Vorhandensein von Zeigern zum Funktionieren fehlschlagen.

Für Ihr spezifisches Problem: Ich nehme an, dass Sie eine vorhandene Funktion durch einen eigenen ersetzen möchten. Ich schlage vor, dass Sie die ersetzte Funktion selbst patchen. I.e., anstatt alle Anrufe zur foo() Funktion zu finden und sie durch einen Anruf zu bar() zu ersetzen, ersetzen Sie einfach die ersten Bytes von foo() mit einem Sprung zu bar() (ein jmp, kein call: Sie möchten nicht mit dem Stapel verwirren). Dies ist wegen des Doppelsprungs weniger befriedigend, aber es ist zuverlässig.

+0

+1 für den JMP-Trick. IIRC, so werden Importbibliotheken von Visual Studio implementiert. –

+0

Ich brauche einen Anruf, nicht springen, und ich möchte mit Stack Chaos. Ich möchte, dass meine Ersetzungsfunktion mit Argumenten spielt, die Originalfunktion aufruft, mit dem Ergebnis geiget und zurückkehrt. JMP würde Teile der ursprünglichen Funktion überschreiben und ich möchte alles intakt behalten. Obwohl es eine Möglichkeit ist; Ich könnte jmp zu meiner Funktion am Anfang der ursprünglichen Funktion platzieren, dann am Anfang meiner Funktion Code des Originals zurück zu dem ändern, was es war, es aufrufen, und wenn es zurückkommt, setze jmp wieder dorthin. – AUTOMATIC

+1

@AUTOMATIC - Die Verwendung eines JMP zu Ihrem Code bedeutet, dass Sie leicht mit dem Stapel geigen können. z.B. Der Code hat 'CALL foo', die ersten Bytes von foo werden auf' JMP bar' gepatcht, also ist der Stack in 'bar()' genau * wie für 'foo()' und wenn 'bar()' zurückkehrt es geht direkt zum Aufrufer zurück, ohne über 'foo()' zu gehen. – Roddy

0

Thomas schlägt die richtige Idee vor. Um es richtig zu implementieren, müssen Sie die ersten paar Anweisungen zerlegen (den Teil, den Sie mit dem JMP überschreiben würden) und eine einfache Trampolinfunktion erzeugen, die sie ausführt und dann zum Rest der ursprünglichen Funktion springt.

Es gibt Bibliotheken, die das für Sie tun. Ein bekanntes ist Detours, aber es hat etwas peinliche Lizenzbedingungen. Eine nette Implementierung derselben Idee mit einer permissiveren Lizenz ist Mhook.

Verwandte Themen