Что такое опция -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?

Другие вопросы по тегам