Absolute Adressierung für den Laufzeitcodeaustausch in x86_64

8

Ich verwende derzeit ein Code-Ersetzungsschema in 32-Bit, wo der Code, der an eine andere Position verschoben wird, Variablen und einen Klassenzeiger liest. Da x86_64 keine absolute Adressierung unterstützt, habe ich Probleme, die korrekten Adressen für die Variablen an der neuen Position des Codes zu erhalten. Das Problem im Detail ist, dass wegen der rip-relativen Adressierung die Adresse des Befehlszeigers anders als zur Kompilierungszeit ist.

Gibt es also eine Möglichkeit, die absolute Adressierung in x86_64 oder eine andere Weise zu verwenden, um Adressen von Variablen zu erhalten, die nicht in Anweisungszeiger relativ sind?

Etwas wie: leaq variable(%%rax), %%rbx würde auch helfen. Ich möchte nur keine Abhängigkeit vom Befehlszeiger haben.

    
nux 02.11.2011, 10:39
quelle

2 Antworten

6

Verwenden Sie das große Codemodell für x86_64. In gcc kann dies mit -mcmodel = large ausgewählt werden. Der Compiler verwendet 64 Bit absolute Adressierung für Code und Daten.

Sie können auch -fno-pic hinzufügen, um die Generierung von positionsunabhängigem Code zu verhindern.

Bearbeiten: Ich baue eine kleine Test-App mit -mcmodel = large und die resultierende Binärdatei enthält Sequenzen wie

%Vor%

was eine Last von einem absoluten 64-Bit-Sofortwert (in diesem Fall eine Adresse) ist, gefolgt von einem indirekten Aufruf oder einer indirekten Last. Die Befehlsfolge

%Vor%

ist das Äquivalent zu einem "leaq offset64bit (% rax),% rbx" (welches nicht existiert), mit einigen Nebeneffekten wie Flag-Wechsel etc.

    
hirschhornsalz 02.11.2011, 12:24
quelle
2

Was Sie fragen, ist machbar, aber nicht sehr einfach.

Eine Möglichkeit, dies zu tun, besteht darin, den Code in seinen Anweisungen zu kompensieren. Sie müssen alle Anweisungen finden, die die RIP-relative Adressierung verwenden (sie haben das ModRM byte von 05h, 0dh, 15h, 1dh, 25h, 2dh, 35h oder 3dh) und passen ihr disp32 -Feld um den Betrag an move (die Verschiebung ist daher auf +/- 2 GB im virtuellen Adressraum beschränkt, was möglicherweise nicht garantiert ist, wenn der 64-Bit-Adressraum größer als 4 GB ist).

Sie können diese Anweisungen auch durch ihre Entsprechungen ersetzen. Dabei wird wahrscheinlich jeder ursprüngliche Befehl durch mehr als einen ersetzt, zum Beispiel:

%Vor%

Beide Methoden erfordern mindestens einige rudimentäre x86-Disassemblierungsroutinen.

Bei ersterer kann die Verwendung von VirtualAlloc() unter Windows (oder einem ähnlichen Betriebssystem unter Linux) erforderlich sein, um sicherzustellen, dass der Speicher, der die korrigierte Kopie des ursprünglichen Codes enthält, innerhalb von +/- 2 GB des ursprünglichen Codes liegt. Und die Zuweisung an bestimmten Adressen kann immer noch fehlschlagen.

Letzteres erfordert mehr als nur primitives Dismembling, aber auch vollständige Dekodierung und Generierung von Befehlen.

Es kann andere Macken geben, um zu umgehen.

Befehlsgrenzen können auch gefunden werden, indem das TF -Flag im RFLAGS -Register gesetzt wird, damit die CPU am Ende der Ausführung jedes Befehls den Debug-Interrupt single-step erzeugt. Ein Debug-Exception-Handler muss diese abfangen und den Wert von RIP des nächsten Befehls aufzeichnen. Ich glaube, das kann mit Structured Exception Handling (SEH) in Windows (nie mit den Debug-Interrupts versucht) getan werden, nicht sicher über Linux. Damit dies funktioniert, müssen Sie den gesamten Code ausführen, jede Anweisung.

Übrigens gibt es eine absolute Adressierung im 64-Bit-Modus, siehe zum Beispiel die Befehle MOV zu / von Akkumulator mit Opcodes von 0A0h bis 0A3h.

    
Alexey Frunze 02.11.2011 12:24
quelle