В чем разница между «статически связанными» и «не динамическими c исполняемыми файлами»? - PullRequest
0 голосов
/ 02 мая 2020

Рассмотрим программу сборки AMD64:

.globl _start
_start:
    xorl %edi, %edi
    movl $60, %eax
    syscall

Если я скомпилирую это с помощью gcc -nostdlib и запусту ldd a.out, я получу это:

        statically linked

Если я скомпилирую это с gcc -static -nostdlib и запуском ldd a.out, я получаю это:

        not a dynamic executable

В чем разница между statically linked и not a dynamic executable? И если мой двоичный файл уже был статически связан, почему добавление -static влияет на что-либо?

1 Ответ

3 голосов
/ 02 мая 2020

Здесь есть две разные вещи:

  • Запрос интерпретатора ELF (ld.so) или нет.
    Как и #!/bin/sh, но для двоичных файлов выполняется до вашего _start.
    В этом разница между stati c и dynamici c исполняемым .
  • Список динамически связанных библиотек для загрузки ld.so оказывается пустым.
    Это, очевидно, то, что ldd называет "статически связанными", т. Е. Что любые библиотеки, которые вы могли связать во время сборки, были stati c библиотеками.

Другими инструментами, такими как file и readelf дает больше информации и использует терминологию, которая соответствует ожидаемой.


Ваш G CC настроен на , поэтому -pie является значением по умолчанию и g cc не делает ставку c -p ie для особого случая отсутствия динамических c библиотек.

  • gcc -nostdlib просто делает P IE, который не ссылка на любые библиотеки, но в остальном идентична обычной P IE, с указанием интерпретатора ELF.
    ldd вводит в заблуждение это «статически связано».
    file: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2 ...
  • gcc -nostdlib -static переопределяет значение по умолчанию -pie и делает истинный исполняемый файл c.
    file: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked ...
  • gcc -nostdlib -no-pie также решает сделать исполняемый файл stati c в качестве оптимизации для случая, когда вообще нет динамических библиотек c. Так как не-P IE исполняемый файл в любом случае не мог быть ASLRed, это имеет смысл. Байт за байтом, идентичный регистру -static.
  • gcc -nostdlib -static-pie делает исполняемый файл ASLRable без интерпретатора ELF. G CC не делает этого по умолчанию для gcc -pie -nostdlib, в отличие от случая no-p ie, когда он выбирает обход в сторону ld.so, когда не задействованы динамически связанные библиотеки.
    file: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked ...

    -static-pie неясен, редко используется, и более старый file не идентифицирует его как статически связанный.

-nostdlib не подразумевает -no-pie или -static, и -static-pie должен быть явно указан для получения этого.

gcc -static-pie вызывает ld -static -pie, поэтому ld должен знать, что это значит. В отличие от случая, отличного от P IE, когда вам не нужно явно запрашивать динамический c исполняемый файл, вы просто получите его, если передадите ld любую .so библиотеку. Я думаю, именно поэтому вы случайно получаете исполняемый файл c от gcc -nostdlib -no-pie - G CC не должен делать ничего особенного, он просто ld делает эту оптимизацию.

Но ld не включает -static неявным образом, когда указано -pie, даже когда нет общих библиотек для ссылки.


Подробности

Примеры, сгенерированные с помощью gcc --version g cc (Arch Linux 9.3.0-1) 9.3.0
ld --version GNU ld (GNU Binutils) 2,34 (также readelf is binutils)
ldd --version ldd (GNU lib c) 2,31
file --version file-5.38 - обратите внимание, что обнаружение stati c -p ie изменилось в последних патчах, когда Ubuntu cherry выбирает невыпущенный патч. (Спасибо @Joseph за детективную работу) - это в 2019 обнаружено Dynami c = наличие PT_INTERP для обработки stati c -p ie, но оно было возвращено определять на основе PT_DYNAMI C, поэтому общие библиотеки считаются dynamic. ошибка Debian # 948269 . static-pie является малопонятной редко используемой функцией.

G CC заканчивается выполнением ld -pie exit.o с указанным динамическим c компоновщиком пути и без библиотек. (И множество других опций для поддержки возможной оптимизации времени соединения LTO, но ключи здесь -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie. collect2 - это просто оболочка вокруг ld.)

$ gcc -nostdlib exit.s -v      # output manually line wrapped with \ for readability
...
COLLECT_GCC_OPTIONS='-nostdlib' '-v' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/collect2  \
-plugin /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/liblto_plugin.so \
-plugin-opt=/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/lto-wrapper \
-plugin-opt=-fresolution=/tmp/ccoNx1IR.res \
--build-id --eh-frame-hdr --hash-style=gnu \
-m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie \
-L/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0 \
-L/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/../../../../lib -L/lib/../lib \
-L/usr/lib/../lib \
-L/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/../../.. \
/tmp/cctm2fSS.o

Вы получаете Dynami c P IE без каких-либо зависимостей от других библиотек. Запуск его по-прежнему вызывает «ELF-интерпретатор» /lib64/ld-linux-x86-64.so.2, который запускается перед тем, как перейти к вашему _start. (Хотя ядро ​​уже сопоставило сегменты ELF исполняемого файла с виртуальными адресами ASLR вместе с текстом / data / bss ld.so).

file и readelf являются более информативными.

P IE non-stati c, исполняемый с gcc -nostdlib

$ gcc -nostdlib exit.s -o exit-default
$ ls -l exit-default 
-rwxr-xr-x 1 peter peter 13536 May  2 02:15 exit-default 
$ ldd exit-default 
        statically linked
$ file exit-default
exit-default: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=05a4d1bdbc94d6f91cca1c9c26314e1aa227a3a5, not stripped

$ readelf -a exit-default
...
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1000
...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000001f8 0x00000000000001f8  R      0x8
  INTERP         0x0000000000000238 0x0000000000000238 0x0000000000000238
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000002b1 0x00000000000002b1  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x0000000000000009 0x0000000000000009  R E    0x1000
  ...   (the Read+Exec segment to be mapped at virt addr 0x1000 is where your text section was linked.)

Если вы выпустите его, вы также увидите различия:

$ gcc -nostdlib exit.s -o exit-default
$ strace ./exit-default
execve("./exit-default", ["./exit-default"], 0x7ffe1f526040 /* 51 vars */) = 0
brk(NULL)                               = 0x5617eb1e4000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffcea703380) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9ff5b3e000
arch_prctl(ARCH_SET_FS, 0x7f9ff5b3ea80) = 0
mprotect(0x5617eabac000, 4096, PROT_READ) = 0
exit(0)                                 = ?
+++ exited with 0 +++

против. -static и -static-pie первая инструкция, выполняемая в пространстве пользователя, - это ваша _start (которую вы также можете проверить с помощью GDB, используя starti).

$ strace ./exit-static-pie 
execve("./exit-static-pie", ["./exit-static-pie"], 0x7ffcdac96dd0 /* 51 vars */) = 0
exit(0)                                 = ?
+++ exited with 0 +++

gcc -nostdlib -static-pie

$ gcc -nostdlib -static-pie exit.s -o exit-static-pie
$ ls -l exit-static-pie
-rwxr-xr-x 1 peter peter 13440 May  2 02:18 exit-static-pie
peter@volta:/tmp$ ldd exit-static-pie
        statically linked
peter@volta:/tmp$ file exit-static-pie
exit-static-pie: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=daeb4a8f11bec1bb1aaa13cd48d24b5795af638e, not stripped

$ readelf -a exit-static-pie 
...
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1000
...

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000229 0x0000000000000229  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x0000000000000009 0x0000000000000009  R E    0x1000
  ... (no Interp header, but still a read+exec text segment)

Обратите внимание, что адреса по-прежнему относительно базы изображений, оставляя ASLR вплоть до ядра.

Удивительно, но ldd не говорит, что это не динамически c исполняемый файл. Это может быть ошибка или побочный эффект некоторых деталей реализации.


gcc -nostdlib -static традиционный не P IE old-school stati c исполняемый файл

$ gcc -nostdlib -static exit.s -o exit-static
$ ls -l exit-static
-rwxr-xr-x 1 peter peter 4744 May  2 02:26 exit-static
peter@volta:/tmp$ ldd exit-static
        not a dynamic executable
peter@volta:/tmp$ file exit-static
exit-static: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=1b03e3d05709b7288fe3006b4696fd0c11fb1cb2, not stripped
peter@volta:/tmp$ readelf -a exit-static
ELF Header:
...
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x401000
...   (Note the absolute entry-point address nailed down at link time)
      (And that the ELF type is EXEC, not DYN)

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000010c 0x000000000000010c  R      0x1000
  LOAD           0x0000000000001000 0x0000000000401000 0x0000000000401000
                 0x0000000000000009 0x0000000000000009  R E    0x1000
  NOTE           0x00000000000000e8 0x00000000004000e8 0x00000000004000e8
                 0x0000000000000024 0x0000000000000024  R      0x4

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id 
   01     .text 
   02     .note.gnu.build-id 
   ...

Это все заголовки программы; в отличие от p ie / stati c -p ie Я не пропускаю ничего, только другие целые части вывода readelf -a.

Также обратите внимание на абсолютные виртуальные адреса в программе Заголовки, которые не дают ядру выбора, где в виртуальном адресном пространстве отображать файл. В этом разница между типами ELF типов EXE C и DYN. Исполняемые файлы P IE являются общими объектами с точкой входа, что позволяет нам получить ASLR для основного исполняемого файла. Фактические исполняемые файлы EXE C имеют макет памяти, выбранный во время компоновки.


ldd, по-видимому, только сообщает "не исполняемый файл Dynami c", когда оба:

  • нет интерпретатора ELF (динамический c компоновщик) путь
  • тип ELF = EXE C
...