Есть ли ELF-эквивалент PE-перемещений основания? - PullRequest
2 голосов
/ 10 июня 2019

Я смотрел на некоторые разборки некоторых двоичных файлов ELF и заметил это:

   0000000000401020 <_start>:
      401020:   31 ed                   xor    ebp,ebp
      401022:   49 89 d1                mov    r9,rdx
      401025:   5e                      pop    rsi
      401026:   48 89 e2                mov    rdx,rsp
      401029:   48 83 e4 f0             and    rsp,0xfffffffffffffff0
      40102d:   50                      push   rax
      40102e:   54                      push   rsp
      40102f:   49 c7 c0 30 13 40 00    mov    r8,0x401330
      401036:   48 c7 c1 d0 12 40 00    mov    rcx,0x4012d0
      40103d:   48 c7 c7 72 12 40 00    mov    rdi,0x401272
      401044:   ff 15 a6 2f 00 00       call   QWORD PTR [rip+0x2fa6]        # 403ff0 <__libc_start_main@GLIBC_2.2.5>
      40104a:   f4                      hlt    
      40104b:   0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]

Когда вызывается __libc_start_main, эти три непосредственных значения передаются через регистры в качестве параметров. Это, очевидно, указатели на функции, которые вызываются в __libc_start_main (включая main). Но это виртуальные адреса, и я понимаю, что фактический сопоставленный адрес двоичного файла, когда он загружен в память и работает, не обязательно будет таким же. Таким образом, эти указатели функций могут не отражать их фактическое расположение в памяти.

Будучи более знакомым с PE-файлами, раздел IMAGE_DIRECTORY_BASERELOC предоставляет нам структуры IMAGE_BASE_RELOCATION, которые помогают нам корректировать эти постоянные значения для отражения новой базы изображений. Но я не вижу аналога для файлов ELF. Я что-то здесь упускаю? Как исправить эти адреса при загрузке файла ELF?

1 Ответ

2 голосов
/ 10 июня 2019

и я понимаю, что фактический сопоставленный адрес двоичного файла, когда он загружен в память и работает, не обязательно будет таким же.

Нет, по этим адресам мы можем видеть, что это исполняемый файл не PIE ELF, связанный по умолчанию с базовым адресом ld. Это зависимый от позиции исполняемый файл.

Сам исполняемый файл всегда будет загружаться по фиксированному виртуальному адресу, поэтому статические адреса могут быть помещены в регистры с использованием 32-разрядных модулей немедленного действия вместо REA-относительного LEA. ASLR для самого исполняемого файла не разрешен / невозможен.

libc - это «общий объект» ELF, который может быть ALSRed, следовательно, вызов __libc_start_main через указатель в GOT. В исходном коде gcc для этого стартового кода CRT это, вероятно, выглядит как call *__libc_start_main@GOTPCREL(%rip) (синтаксис AT & T).

И, кстати, мы можем сказать, что это был рукописный asm, из-за пропущенной оптимизации использования 7-байтового mov rdi, sign_extended_imm32 (такого же размера, как у RIP-относительного LEA) вместо 5-байтового mov edi, imm32. Модель кода по умолчанию, отличная от PIE, в x86-64 System V ABI помещает весь статический код / ​​данные в низкое 2 ГБ виртуального адресного пространства, поэтому статические адреса могут использоваться с нулевым или знаковым расширением до 64-разрядного.


"Исполняемые файлы ELF", которые могут быть загружены по случайному базовому адресу, называются PIE (Position Independent Executable) . С точки зрения деталей ELF, они используют тот же «тип» ELF, что и разделяемые библиотеки, поэтому они на самом деле являются общими объектами ELF, которые имеют «точку входа» и помечены как исполняемые.

В современных дистрибутивах Linux gcc по умолчанию использует PIE. См. 32-разрядные абсолютные адреса, более не разрешенные в Linux x86-64? (перемещаемые совместно используемые объекты ELF можно перемещать в любое место адресного пространства, не ограничиваясь низким 2 ГБ, поэтому для среды выполнения нет типа перемещения исправления 32-битных абсолютных адресов.)

Существует тип перемещения для 64-битных абсолютных адресов, поэтому таблицы переходов (указателей функций / кодов) все еще возможны, как и 10-байтовые mov rdi, imm64, но это менее эффективно, чем LEA-относительный LEA, даже если бы не загрузчик программ ELF или динамический компоновщик не должны были изменять текст программы для этих перемещений.

например. readelf -a /bin/ls

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x5ae0
...

Обратите внимание на поле Тип: DYN, такое же, как в реальной библиотеке, такой как readelf -a /lib/libc.so.6. А точка входа - это относительный адрес относительно базового адреса, в котором она отображается.

Не-PIE исполняемый файл (например, со статической связью или сборка с -fno-pie -no-pie) выглядит следующим образом:

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x401000

Обратите внимание на Type: EXEC и абсолютную точку входа (выбирается во время соединения с помощью ld).

...