Wie wird der Code geändert, z. B. Funktionsaufrufe?
Antworten:
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
#include <stdio.h>
int main(void) {
puts("hello");
}
main.sh
#!/usr/bin/env bash
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
for pie in no-pie pie; do
exe="${pie}.out"
gcc -O0 -std=c99 "-${pie}" "-f${pie}" -ggdb3 -o "$exe" main.c
gdb -batch -nh \
-ex 'set disable-randomization off' \
-ex 'break main' \
-ex 'run' \
-ex 'printf "pc = 0x%llx\n", (long long unsigned)$pc' \
-ex 'run' \
-ex 'printf "pc = 0x%llx\n", (long long unsigned)$pc' \
"./$exe" \
;
echo
echo
done
Für den mit -no-pieist alles langweilig:
Breakpoint 1 at 0x401126: file main.c, line 4.
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x401126
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x401126
Setzt vor Beginn der Ausführung break maineinen Haltepunkt auf 0x401126.
Stoppt dann während beider Ausführungen runan der Adresse 0x401126.
Der mit ist -piejedoch viel interessanter:
Breakpoint 1 at 0x1139: file main.c, line 4.
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x5630df2d6139
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x55763ab2e139
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_spacestellt 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 offwird 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 Analyse
Darüber hinaus können wir auch Folgendes beobachten:
readelf -s ./no-pie.out | grep main
gibt die tatsächliche Laufzeit-Ladeadresse an (PC zeigt 4 Byte später auf die folgende Anweisung):
64: 0000000000401122 21 FUNC GLOBAL DEFAULT 13 main
während:
readelf -s ./pie.out | grep main
gibt nur einen Versatz:
65: 0000000000001135 23 FUNC GLOBAL DEFAULT 14 main
Durch Deaktivieren der ASLR (mit entweder randomize_va_spaceoder set disable-randomization off) gibt GDB immer maindie Adresse: an 0x5555555547a9. Daher schließen wir, dass die -pieAdresse aus folgenden Elementen besteht:
0x555555554000 + random offset + symbol offset (79a)
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
.text
.global _start
_start:
asm_main_after_prologue:
/* write */
mov $1, %rax /* syscall number */
mov $1, %rdi /* stdout */
mov $msg, %rsi /* buffer */
mov $len, %rdx /* len */
syscall
/* exit */
mov $60, %rax /* syscall number */
mov $0, %rdi /* exit status */
syscall
msg:
.ascii "hello\n"
len = . - msg
und es baut zusammen und läuft gut mit:
as -o main.o main.S
ld -o main.out main.o
./main.out
Wenn wir jedoch versuchen, es als PIE mit zu verknüpfen ( --no-dynamic-linkerist erforderlich, wie unter: So erstellen Sie eine statisch verknüpfte positionsunabhängige ausführbare ELF unter Linux? ):
ld --no-dynamic-linker -pie -o main.out main.o
dann schlägt die Verknüpfung fehl mit:
ld: main.o: relocation R_X86_64_32S against `.text' can not be used when making a PIE object; recompile with -fPIC
ld: final link failed: nonrepresentable section on output
Weil die Linie:
mov $msg, %rsi /* buffer */
codiert die Nachrichtenadresse im movOperanden fest und ist daher nicht positionsunabhängig.
Wenn wir es stattdessen positionsunabhängig schreiben:
lea msg(%rip), %rsi
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 leadie Adresse msgrelativ zur aktuellen PC-Adresse aufgrund der ripSyntax 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:
objdump -S main.o
welche jeweils geben:
e: 48 c7 c6 00 00 00 00 mov $0x0,%rsi
e: 48 8d 35 19 00 00 00 lea 0x19(%rip),%rsi # 2e <msg>
000000000000002e <msg>:
2e: 68 65 6c 6c 6f pushq $0x6f6c6c65
So sehen wir deutlich, dass leabereits die volle korrekte Adresse von hatmsg als aktuelle Adresse + 0x19 codiert ist.
Die movVersion hat jedoch die Adresse auf festgelegt 00 00 00 00, was bedeutet, dass dort ein Umzug durchgeführt wird: Was machen Linker? Die Kryptik R_X86_64_32Sin der ldFehlermeldung 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 msgin den Datenabschnitt zu setzen, anstatt .textmit:
.data
msg:
.ascii "hello\n"
len = . - msg
Nun .oversammelt sich das zu:
e: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 15 <_start+0x15>
Der RIP-Offset ist jetzt 0und wir vermuten, dass der Assembler einen Umzug angefordert hat. Wir bestätigen dies mit:
readelf -r main.o
was gibt:
Relocation section '.rela.text' at offset 0x160 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000011 000200000002 R_X86_64_PC32 0000000000000000 .data - 4
so klar R_X86_64_PC32ist 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, -pieeine 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.