Wie wird der Code geändert, z. B. Funktionsaufrufe?
PIE soll die Adressraum-Layout-Randomisierung (ASLR) in ausführbaren Dateien unterstützen.
Bevor der PIE-Modus erstellt wurde, konnte die ausführbare Datei des Programms nicht an einer zufälligen Adresse im Speicher abgelegt werden, sondern nur dynamische Bibliotheken mit positionsunabhängigem Code (PIC) konnten auf einen zufälligen Versatz verschoben werden. Es funktioniert sehr ähnlich wie PIC für dynamische Bibliotheken. Der Unterschied besteht darin, dass keine Prozedurverknüpfungstabelle (PLT) erstellt wird, sondern eine PC-relative Verlagerung verwendet wird.
Nach Aktivierung der PIE-Unterstützung in gcc / linkers wird der Programmkörper kompiliert und als positionsunabhängiger Code verknüpft. Ein dynamischer Linker führt die vollständige Verschiebungsverarbeitung auf dem Programmmodul aus, genau wie dynamische Bibliotheken. Jede Verwendung globaler Daten wird über die Global Offsets Table (GOT) in den Zugriff konvertiert, und GOT-Verschiebungen werden hinzugefügt.
PIE wird in dieser OpenBSD PIE-Präsentation gut beschrieben .
Änderungen an Funktionen werden auf dieser Folie angezeigt (PIE vs PIC).
x86 Bild gegen Kuchen
Lokale globale Variablen und Funktionen werden in pie optimiert
Externe globale Variablen und Funktionen sind identisch mit pic
und in dieser Folie (PIE vs Old-Style-Linking)
x86 pie vs no-flags (behoben)
Lokale globale Variablen und Funktionen ähneln festen
Externe globale Variablen und Funktionen sind identisch mit pic
Beachten Sie, dass PIE möglicherweise nicht mit kompatibel ist -static
Minimal ausführbares Beispiel: GDB die ausführbare Datei zweimal
Für diejenigen, die eine Aktion sehen möchten, sehen wir, wie ASLR an der ausführbaren PIE-Datei arbeitet und Adressen über Läufe hinweg ändert:
Haupt c
main.sh
Für den mit
-no-pie
ist alles langweilig:Setzt vor Beginn der Ausführung
break main
einen Haltepunkt auf0x401126
.Stoppt dann während beider Ausführungen
run
an der Adresse0x401126
.Der mit ist
-pie
jedoch viel interessanter:Vor dem Start der Ausführung nimmt GDB nur eine "Dummy" -Adresse an, die in der ausführbaren Datei vorhanden ist :
0x1139
.Nach dem Start bemerkt GDB jedoch auf intelligente Weise, dass der dynamische Lader das Programm an einem anderen Ort platziert hat und die erste Pause bei angehalten hat
0x5630df2d6139
.Dann bemerkte der zweite Lauf auch auf intelligente Weise, dass sich die ausführbare Datei erneut bewegte und bei brach
0x55763ab2e139
.echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
stellt sicher, dass ASLR aktiviert ist (Standardeinstellung in Ubuntu 17.10): Wie kann ich ASLR (Address Space Layout Randomization) vorübergehend deaktivieren? | Fragen Sie Ubuntu .set disable-randomization off
wird benötigt, andernfalls deaktiviert GDB, wie der Name schon sagt, ASLR für den Prozess standardmäßig, um feste Adressen über Läufe hinweg anzugeben, um das Debugging-Erlebnis zu verbessern: Unterschied zwischen GDB-Adressen und "echten" Adressen? | Stapelüberlauf .readelf
AnalyseDarüber hinaus können wir auch Folgendes beobachten:
gibt die tatsächliche Laufzeit-Ladeadresse an (PC zeigt 4 Byte später auf die folgende Anweisung):
während:
gibt nur einen Versatz:
Durch Deaktivieren der ASLR (mit entweder
randomize_va_space
oderset disable-randomization off
) gibt GDB immermain
die Adresse: an0x5555555547a9
. Daher schließen wir, dass die-pie
Adresse aus folgenden Elementen besteht:TODO wo ist 0x555555554000 im Linux-Kernel / glibc loader / wo fest codiert? Wie wird die Adresse des Textabschnitts einer ausführbaren PIE-Datei unter Linux bestimmt?
Minimales Montagebeispiel
Eine andere coole Sache, die wir tun können, ist, mit einem Assembler-Code herumzuspielen, um konkreter zu verstehen, was PIE bedeutet.
Wir können das mit einer freistehenden Linux x86_64-Baugruppe tun. Hallo Welt:
Netz
GitHub stromaufwärts
und es baut zusammen und läuft gut mit:
Wenn wir jedoch versuchen, es als PIE mit zu verknüpfen (
--no-dynamic-linker
ist erforderlich, wie unter: So erstellen Sie eine statisch verknüpfte positionsunabhängige ausführbare ELF unter Linux? ):dann schlägt die Verknüpfung fehl mit:
Weil die Linie:
codiert die Nachrichtenadresse im
mov
Operanden fest und ist daher nicht positionsunabhängig.Wenn wir es stattdessen positionsunabhängig schreiben:
dann funktioniert der PIE-Link einwandfrei und GDB zeigt uns, dass die ausführbare Datei jedes Mal an einer anderen Stelle im Speicher geladen wird.
Der Unterschied besteht darin, dass
lea
die Adressemsg
relativ zur aktuellen PC-Adresse aufgrund derrip
Syntax codiert wurde. Siehe auch: Wie wird die relative RIP-Adressierung in einem 64-Bit-Assemblyprogramm verwendet?Wir können das auch herausfinden, indem wir beide Versionen zerlegen mit:
welche jeweils geben:
So sehen wir deutlich, dass
lea
bereits die volle korrekte Adresse von hatmsg
als aktuelle Adresse + 0x19 codiert ist.Die
mov
Version hat jedoch die Adresse auf festgelegt00 00 00 00
, was bedeutet, dass dort ein Umzug durchgeführt wird: Was machen Linker? Die KryptikR_X86_64_32S
in derld
Fehlermeldung ist die tatsächliche Art der Verlagerung, die erforderlich war und in ausführbaren PIE-Dateien nicht vorkommen kann.Eine andere lustige Sache, die wir tun können, ist, das
msg
in den Datenabschnitt zu setzen, anstatt.text
mit:Nun
.o
versammelt sich das zu:Der RIP-Offset ist jetzt
0
und wir vermuten, dass der Assembler einen Umzug angefordert hat. Wir bestätigen dies mit:was gibt:
so klar
R_X86_64_PC32
ist ein PC relativer Umzug, dassld
die für ausführbare PIE-Dateien geeignet ist.Dieses Experiment hat uns gelehrt, dass der Linker selbst prüft, ob das Programm PIE sein kann, und es als solches markiert.
Weist GCC dann beim Kompilieren mit GCC an,
-pie
eine positionsunabhängige Baugruppe zu generieren.Wenn wir jedoch selbst eine Versammlung schreiben, müssen wir manuell sicherstellen, dass wir die Positionsunabhängigkeit erreicht haben.
In ARMv8 aarch64 kann die positionsunabhängige Hallo-Welt mit dem ADR-Befehl erreicht werden .
Wie kann festgestellt werden, ob ein ELF positionsunabhängig ist?
Neben dem einfachen Ausführen über GDB werden einige statische Methoden erwähnt:
Getestet in Ubuntu 18.10.
quelle