Что такое опция -fPIE для не зависящих от позиции исполняемых файлов в gcc и ld?
Как это изменит код, например, вызовы функций?
2 ответа
PIE должен поддерживать рандомизацию размещения адресного пространства (ASLR) в исполняемых файлах.
До того, как был создан режим PIE, исполняемый файл программы не мог быть размещен по случайному адресу в памяти, только динамические библиотеки с позиционным кодом (PIC) могли быть перемещены на случайное смещение. Это работает очень похоже на то, что делает PIC для динамических библиотек, разница в том, что таблица процедурных связей (PLT) не создается, вместо этого используется относительное перемещение с ПК.
После включения поддержки PIE в gcc/linkers тело программы компилируется и связывается как независимый от позиции код. Динамический компоновщик выполняет полную обработку перемещения на программном модуле, как динамические библиотеки. Любое использование глобальных данных преобразуется в доступ через Глобальную таблицу смещений (GOT) и добавляются перемещения GOT.
Пирог хорошо описан в этой презентации PBS OpenBSD.
Изменения функций показаны на этом слайде (PIE vs PIC).
x86 рис против пирога
Локальные глобальные переменные и функции оптимизированы в виде пирога
Внешние глобальные переменные и функции такие же, как рис.
и на этом слайде (PIE против ссылок в старом стиле)
x86 pie против no-flags (исправлено)
Локальные глобальные переменные и функции похожи на фиксированные
Внешние глобальные переменные и функции такие же, как рис.
Обратите внимание, что пирог может быть несовместим с -static
Минимальный исполняемый пример: GDB исполняемый файл дважды
Для тех, кто хочет увидеть некоторые действия:
printf '
#include <stdio.h>
int main() {
puts("hello world");
}
' > main.c
gcc -std=c99 -pie -fpie -ggdb3 -o pie main.c
gcc -std=c99 -no-pie -fno-pie -ggdb3 -o nopie main.c
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
gdb -batch -nh -ex 'set disable-randomization off' \
-ex 'start' -ex 'info line' \
-ex 'start' -ex 'info line' \
./pie
gdb -batch -nh -ex 'set disable-randomization off' \
-ex 'start' -ex 'info line' \
-ex 'start' -ex 'info line' \
./nopie
Для одного с -pie
мы видим, что адрес main
изменения между прогонами:
Temporary breakpoint 1 at 0x7a9: file memory_layout.c, line 31.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Temporary breakpoint 1, main (argc=1, argv=0x7ffe57d75318) at memory_layout.c:31
31 int main(int argc, char **argv) {
Line 31 of "memory_layout.c" starts at address 0x55db0066b79a <main> and ends at 0x55db0066b7a9 <main+15>.
Temporary breakpoint 2 at 0x55db0066b7a9: file memory_layout.c, line 31.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Temporary breakpoint 2, main (argc=1, argv=0x7ffd03b67d68) at memory_layout.c:31
31 int main(int argc, char **argv) {
Line 31 of "memory_layout.c" starts at address 0x563910ccd79a <main> and ends at 0x563910ccd7a9 <main+15>.
поэтому в этом примере адрес для первого запуска был 0x55db0066b79a
и для второго 0x563910ccd79a
,
Но для одного с -no-pie
Адрес main
остается такой же 0x400627
для обоих прогонов:
Temporary breakpoint 1 at 0x400636: file ./memory_layout.c, line 28.
Temporary breakpoint 1, main (argc=1, argv=0x7ffd5f69c8b8) at ./memory_layout.c:28
warning: Source file is more recent than executable.
28 int bss = 0;
Line 28 of "./memory_layout.c" starts at address 0x400627 <main> and ends at 0x400636 <main+15>.
Temporary breakpoint 2 at 0x400636: file ./memory_layout.c, line 28.
Temporary breakpoint 2, main (argc=1, argv=0x7ffdd9f74bd8) at ./memory_layout.c:28
28 int bss = 0;
Line 28 of "./memory_layout.c" starts at address 0x400627 <main> and ends at 0x400636 <main+15>.
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
гарантирует, что ASLR включен (по умолчанию в Ubuntu 17.10): Как я могу временно отключить ASLR (рандомизация расположения адресного пространства)? | Спросите Ubuntu.
set disable-randomization off
в противном случае GDB, как следует из названия, по умолчанию отключает ASLR для процесса, чтобы дать фиксированные адреса при каждом прогоне для улучшения процесса отладки. Разница между адресами GDB и "реальными" адресами? | Переполнение стека
readelf
веселье
Кроме того, мы также можем наблюдать, что:
readelf -s ./nopie | grep main
дает фактический адрес загрузки во время выполнения:
69: 0000000000400627 370 FUNC GLOBAL DEFAULT 13 main
в то время как:
readelf -s ./pie | grep main
дает только смещение:
70: 000000000000079a 401 FUNC GLOBAL DEFAULT 14 main
Выключив ASLR (либо с randomize_va_space
или же set disable-randomization off
), ГБД всегда дает main
адрес: 0x5555555547a9
поэтому мы делаем вывод, что -pie
адрес состоит из:
0x555555554000 + random offset + symbol offset (79a)
TODO где 0x555555554000 жестко запрограммирован в ядре Linux / загрузчик glibc / где угодно? Как определяется адрес текстового раздела исполняемого файла PIE в Linux?
Проверено в Ubuntu 18.04.
Смежный вопрос: Как я могу определить, что-то вроде objdump, был ли объектный файл собран с -fPIC?