ELF бинарный анализ статический против динамического. Как работает ассемблерный код | изменения в памяти команд - PullRequest
0 голосов
/ 21 января 2019

. / Hello - простая эхо-программа на языке c.
в соответствии с заголовками файлов objdump,

$ objdump -f ./hello

./hello:     file format elf32-i386
architecture: i386, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x00000430

. / Hello имеет начальный адрес 0x430

Теперь загружается этот двоичный файл в GDB.

(gdb) file ./hello
Reading symbols from ./hello...(no debugging symbols found)...done.
(gdb) x/x _start
0x430 <_start>: 0x895eed31
(gdb) break _start
Breakpoint 1 at 0x430
(gdb) run
Starting program: /1/vooks/cdac/ditiss/proj/binaries/temp/hello 

Breakpoint 1, 0x00400430 in _start ()
(gdb) x/x _start
0x400430 <_start>:  0x895eed31
(gdb) 

вышевыводится до установки точки останова или запуска двоичного файла, _start имеет адрес 0x430 , но после запуска этот адрес меняется на 0x400430 .

$ readelf -l ./hello | grep LOAD

 LOAD           0x000000 0x00000000 0x00000000 0x007b4 0x007b4 R E 0x1000
 LOAD           0x000eec 0x00001eec 0x00001eec 0x00130 0x00134 RW  0x1000

Как происходит это отображение?

Пожалуйста, помогите.

Ответы [ 2 ]

0 голосов
/ 22 января 2019

У вас есть исполняемый файл PIE (Position Independent Executable), поэтому файл содержит только смещение относительно адреса загрузки, которое ОС выбирает (и может рандомизировать).

0x400000 по умолчанию для Linuxбазовый адрес для загрузки исполняемых файлов PIE с отключенным ASLR (как GDB делает по умолчанию).

Если вы компилируете с -m32 -fno-pie -no-pie hello.c, чтобы сделать нормальное положение зависимым динамически связанным исполняемым файлом, который может загружаться из статическогоместоположения с mov eax, [symname] вместо того, чтобы получать EIP в регистре и использовать это для выполнения относительной к ПК адресации без x86-64 режимов относительной RIP, objdump -f скажет:

./hello-32-nopie:     file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x08048380                   # hard-coded load address, can't be ASLRed

вместо

architecture: i386, flags 0x00000150:   # some different flags set
HAS_SYMS, DYNAMIC, D_PAGED              # different ELF type
start address 0x000003e0

В "обычном" позиционно-зависимом исполняемом файле компоновщик выбирает этот базовый адрес по умолчанию и встраивает его в исполняемый файл. ОСзагрузчик программы не может выбирать исполняемые файлы ELF, только для общих объектов ELF.Неисполняемый PIE исполняемый файл не может быть загружен по любому другому адресу, поэтому ASLRed может быть только их библиотека, но не сам исполняемый файл.Вот почему были изобретены исполняемые файлы PIE.

Не-PIE разрешено встраивать абсолютные адреса без метаданных, которые позволили бы ОС попытаться переместить его.Или разрешено содержать рукописный asm, который использует все, что ему нужно, о числовых значениях адресов.


PIE - это общий объект ELF с точкой входа .До изобретения PIE общие объекты ELF обычно использовались только для общих библиотек.См. 32-битные абсолютные адреса, больше не разрешенные в x86-64 Linux? для получения дополнительной информации о PIE.

Они довольно неэффективны для 32-битного кода, я бы рекомендовал не делать 32-бит PIEs.


Статический исполняемый файл не может быть PIE, поэтому gcc -static создаст исполняемый файл не-PIE elf;это подразумевает -no-pie.(То же самое относится и к ld напрямую, потому что только gcc по умолчанию изменил на создание PIE, для этого gcc должен передать -pie в ld.)

Так что это легко понятьпочему вы написали «static vs. dynamic» в своем заголовке, если единственными динамическими исполняемыми файлами, на которые вы когда-либо обращали внимание, были PIEs .Но динамически связанный исполняемый файл без PIE ELF вполне нормален, и что вам следует делать, если вы заботитесь о производительности, но по какой-то причине хотите / должны создавать 32-битные исполняемые файлы.

До последнегопару лет или около того нормальные двоичные файлы, такие как /bin/ls в обычных дистрибутивах Linux, были динамическими исполняемыми файлами, отличными от PIE. Для кода x86-64 PIE только замедляет их, возможно, на 1%, я думаю, что я прочитал.Чуть больший код для помещения статического адреса в регистр или для индексации статического массива.Нехватка объема служебной информации, которую имеет 32-битный код для PIC / PIE.

0 голосов
/ 21 января 2019

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

Каждый фрагмент кода и данных помещается в пределах смещения внутри раздела, такого как раздел данных, текстовый раздел и т. Д., И доступ к определенной функции или глобальной переменной осуществляется путем добавления правильного смещения к начальному адресу раздела.

Теперь формат файла ELF также включает таблицу заголовков программ:

Таблица заголовка программы исполняемого файла или файла общего объекта представляет собой массив структур, каждая из которых описывает сегмент или другую информацию, Система должна подготовить программу к исполнению. Объектный файл сегмент содержит один или несколько разделов , как описано в разделе «Сегмент» Содержание».

Эти структуры затем используются загрузчиком ОС для загрузки изображения в память. Структура:

typedef struct {
        Elf32_Word      p_type;
        Elf32_Off       p_offset;
        Elf32_Addr      p_vaddr;
        Elf32_Addr      p_paddr;
        Elf32_Word      p_filesz;
        Elf32_Word      p_memsz;
        Elf32_Word      p_flags;
        Elf32_Word      p_align;
} Elf32_Phdr;

Обратите внимание на следующие поля:

p_vaddr

Виртуальный адрес, по которому первый байт сегмента находится в памяти

p_offset

Смещение от начала файла, в котором первый байт сегмент находится.

А p_type

Вид сегмента, который описывает этот элемент массива, или как интерпретировать информацию элемента массива. Типовые значения и их значения указаны в таблице 7-35.

Из таблицы 7-35, примечание PT_LOAD:

Определяет загружаемый сегмент , описываемый p_filesz и p_memsz. байты из файла отображаются в начало сегмента памяти. Если размер памяти сегмента (p_memsz) больше, чем размер файла (p_filesz), дополнительные байты определены для хранения значения 0 и следуйте инициализированной области сегмента. Размер файла не может быть больше чем объем памяти. Загружаемые записи сегмента в заголовке программы таблицы отображаются в порядке возрастания, отсортированы по элементу p_vaddr.

Таким образом, просматривая эти поля (и более), загрузчик может найти сегменты (которые могут содержать несколько разделов) в файле ELF и загрузить их (PT_LOAD) в память по заданному виртуальному адресу.

Теперь можно ли изменить виртуальный адрес сегмента файла ELF во время выполнения (время загрузки)? да:

Виртуальные адреса в заголовках программы могут не представлять фактические виртуальные адреса памяти программы . Смотрите "Программа" Загрузка (зависит от процессора) ".

Таким образом, заголовок программы содержит сегменты, которые загрузчик ОС будет загружать в память (загружаемые сегменты, которые содержат загружаемые разделы), но виртуальные адреса, которые загружает их загрузчик, могут отличаться от адресов в файле ELF.

Как?

Чтобы понять это, давайте сначала прочитаем о Base Address

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

Вычисляется базовый адрес исполняемого файла или файла общего объекта при выполнении из трех значений: адрес загрузки памяти, максимальный размер страницы и минимальный виртуальный адрес программы загружаемый сегмент. Виртуальные адреса в заголовках программы могут не представляют действительные виртуальные адреса памяти программы изображение . См. «Загрузка программы (зависит от процессора)».

Итак, практика следующая:

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

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

Таким образом, используя относительную адресацию, (PIE- позиционно-независимый исполняемый файл) фактическое размещение может отличаться от адреса в файле ELF.

От ответа PeterCordes:

0x400000 - база Linux по умолчаниюадрес для загрузки исполняемых файлов PIE с отключенным ASLR (как GDB делает по умолчанию).

Так что для вашего конкретного случая (исполняемый файл PIE в Linux) загрузчик выбирает этот base address.

OfНезависимо от позиции курса это просто вариант.Программа может быть скомпилирована без нее, и тогда имеет место режим абсолютной адресации, в котором не должно быть разницы между адресом сегмента в ELF и реальным адресом памяти, сегмент которого загружен в:

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

Я бы порекомендовал вам взглянуть на реализацию linux загрузки изображений elf здесь ,и эти два потока SO здесь и здесь .

Абзацы взяты из документов Oracle ELF ( здесь и здесь )

...