Bagaimana cara mengubah kode, misalnya panggilan fungsi?
Bagaimana cara mengubah kode, misalnya panggilan fungsi?
PIE mendukung pengacakan tata letak ruang alamat (ASLR) dalam file yang dapat dieksekusi.
Sebelum mode PIE dibuat, program yang dapat dieksekusi tidak dapat ditempatkan di alamat acak dalam memori, hanya pustaka dinamis kode independen posisi (PIC) yang dapat dipindahkan ke offset acak. Ia bekerja sangat mirip dengan apa yang dilakukan PIC untuk perpustakaan dinamis, perbedaannya adalah bahwa Tabel Tautan Prosedur (PLT) tidak dibuat, melainkan relokasi relatif-PC digunakan.
Setelah mengaktifkan dukungan PIE di gcc / linker, badan program dikompilasi dan ditautkan sebagai kode yang tidak bergantung pada posisi. Linker dinamis melakukan pemrosesan relokasi penuh pada modul program, seperti pustaka dinamis. Setiap penggunaan data global diubah menjadi akses melalui Tabel Offset Global (GOT) dan relokasi GOT ditambahkan.
PIE dijelaskan dengan baik dalam presentasi OpenBSD PIE ini .
Perubahan fungsi ditampilkan dalam slide ini (PIE vs PIC).
gambar x86 vs pai
Variabel global dan fungsi lokal dioptimalkan secara bersamaan
Variabel dan fungsi global eksternal sama seperti pic
dan di slide ini (PIE vs penautan gaya lama)
x86 pie vs no-flags (tetap)
Variabel dan fungsi global lokal mirip dengan tetap
Variabel dan fungsi global eksternal sama seperti pic
Perhatikan, bahwa PIE mungkin tidak kompatibel dengan -static
Contoh minimal runnable: GDB dapat dieksekusi dua kali
Bagi mereka yang ingin melihat beberapa tindakan, mari kita lihat ASLR bekerja pada PIE yang dapat dieksekusi dan mengubah alamat di seluruh proses:
main.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
Untuk yang bersama
-no-pie
, semuanya membosankan: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
Sebelum memulai eksekusi,
break main
setel breakpoint pada0x401126
.Kemudian, selama kedua eksekusi,
run
berhenti di alamat0x401126
.Namun yang satu
-pie
jauh lebih menarik: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
Sebelum memulai eksekusi, GDB hanya membutuhkan "boneka" alamat yang hadir dalam eksekusi:
0x1139
.Namun, setelah dimulai, GDB dengan cerdas memperhatikan bahwa pemuat dinamis menempatkan program di lokasi yang berbeda, dan pemutusan pertama berhenti di
0x5630df2d6139
.Kemudian, proses kedua juga dengan cerdas memperhatikan bahwa eksekusi bergerak lagi, dan akhirnya berhenti
0x55763ab2e139
.echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
memastikan bahwa ASLR aktif (default di Ubuntu 17.10): Bagaimana saya dapat menonaktifkan sementara ASLR (Address space layout randomization)? | Tanya Ubuntu .set disable-randomization off
diperlukan jika tidak GDB, seperti namanya, menonaktifkan ASLR untuk proses secara default untuk memberikan alamat tetap di seluruh proses untuk meningkatkan pengalaman debugging: Perbedaan antara alamat gdb dan alamat "asli"? | Stack Overflow .readelf
analisisSelain itu, kami juga dapat mengamati bahwa:
memberikan alamat pemuatan runtime aktual (pc menunjuk ke instruksi berikut 4 byte setelah):
64: 0000000000401122 21 FUNC GLOBAL DEFAULT 13 main
sementara:
hanya memberikan offset:
65: 0000000000001135 23 FUNC GLOBAL DEFAULT 14 main
Dengan mematikan ASLR (dengan salah satu
randomize_va_space
atauset disable-randomization off
), GDB selalu memberikanmain
alamat0x5555555547a9
:, jadi kami menyimpulkan bahwa-pie
alamat tersebut terdiri dari:0x555555554000 + random offset + symbol offset (79a)
TODO di mana 0x555555554000 hard code di kernel Linux / glibc loader / di mana saja? Bagaimana alamat bagian teks dari PIE yang dapat dieksekusi ditentukan di Linux?
Contoh perakitan minimal
Hal keren lainnya yang dapat kita lakukan adalah bermain-main dengan beberapa kode assembly untuk memahami lebih konkret apa arti PIE.
Kita dapat melakukannya dengan rakitan berdiri bebas Linux x86_64 hello world:
main.S
.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
GitHub upstream
dan itu berkumpul dan bekerja dengan baik dengan:
Namun, jika kami mencoba menautkannya sebagai PIE dengan (
--no-dynamic-linker
diperlukan seperti yang dijelaskan di: Cara membuat ELF independen yang dapat dieksekusi secara independen di Linux? ):maka tautan akan gagal dengan:
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
Karena garis:
mov $msg, %rsi /* buffer */
hardcode alamat pesan di
mov
operan, dan karena itu tidak independen posisi.Jika kita malah menulisnya dalam posisi independen:
lea msg(%rip), %rsi
lalu tautan PIE berfungsi dengan baik, dan GDB menunjukkan kepada kita bahwa file yang dapat dieksekusi dimuat di lokasi berbeda dalam memori setiap saat.
Perbedaannya di sini adalah bahwa
lea
menyandikan alamatmsg
relatif ke alamat PC saat ini karenarip
sintaksnya, lihat juga: Bagaimana cara menggunakan RIP Relative Addressing dalam program perakitan 64-bit?Kami juga dapat mengetahuinya dengan membongkar kedua versi dengan:
yang memberi masing-masing:
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
Jadi kami melihat dengan jelas bahwa
lea
sudah memiliki alamat lengkap yang benarmsg
dikodekan sebagai alamat saat ini + 0x19.The
mov
Versi namun telah menetapkan alamat ke00 00 00 00
, yang berarti bahwa relokasi akan dilakukan di sana: Apa yang linker lakukan? CrypticR_X86_64_32S
dalamld
pesan kesalahan adalah jenis relokasi sebenarnya yang diperlukan dan yang tidak dapat terjadi di executable PIE.Hal menyenangkan lainnya yang dapat kita lakukan adalah meletakkan
msg
di bagian data alih-alih.text
dengan:.data msg: .ascii "hello\n" len = . - msg
Sekarang
.o
berkumpul untuk:e: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 15 <_start+0x15>
jadi offset RIP sekarang
0
, dan kami menduga relokasi telah diminta oleh assembler. Kami mengonfirmasi hal itu dengan:pemberian yang mana:
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
jadi jelas
R_X86_64_PC32
adalah relokasi relatif PC yangld
dapat menangani executable PIE.Eksperimen ini mengajarkan kita bahwa linker itu sendiri memeriksa program tersebut dapat menjadi PIE dan menandainya seperti itu.
Kemudian saat mengompilasi dengan GCC,
-pie
memberi tahu GCC untuk menghasilkan perakitan independen posisi.Tetapi jika kita menulis perakitan sendiri, kita harus secara manual memastikan bahwa kita telah mencapai independensi posisi.
Di ARMv8 aarch64, posisi hello world yang independen dapat dicapai dengan instruksi ADR .
Bagaimana cara menentukan apakah ELF adalah posisi independen?
Selain hanya menjalankannya melalui GDB, beberapa metode statis disebutkan di:
Diuji di Ubuntu 18.10.
sumber