Есть ли возможность в GNU ld полностью опустить -dynamic-linker (PT_INTERP)?

Я экспериментирую с концепцией чисто статических исполняемых файлов PIE в Linux, но сталкиваюсь с проблемой, заключающейся в том, что компоновщик GNU binutils настаивает на добавлении заголовка PT_INTERP в двоичный файл вывода, когда -pie используется, даже если также задано -static. Есть ли способ подавить это поведение? То есть есть ли способ указать GNU ld специально не записывать определенные заголовки в выходной файл? Возможно, со скриптом компоновщика?

(Пожалуйста, не отвечайте с заявлениями, что это не будет работать; я хорошо знаю, что программе все еще требуется обработка перемещения - перемещение, относящееся к адресу загрузки, только из-за моего использования -Bsymbolic - и вместо стандартного Scrt1.o у меня есть специальный код запуска справиться с этим. Но я не могу заставить его запускаться без динамического компоновщика, который уже включился и выполнил работу, если только не отредактировал заголовок PT_INTERP из двоичного файла.)

19 голосов | спросил R.. 6 Mayam12 2012, 00:48:38

4 ответа


0

Я думаю, что мог бы найти решение: просто используя -shared вместо -pie чтобы сделать двоичные файлы. Вам нужно несколько дополнительных опций компоновщика, чтобы исправить поведение, но кажется, что вам не нужен специальный скрипт компоновщика. Или, другими словами, скрипт компоновщика -shared уже по существу подходит для связывания статических круговых двоичных файлов.

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

Обновление: это работает! Вот командная строка:

gcc -shared -static-libgcc -Wl,-static -Wl,-Bsymbolic \
    -nostartfiles -fPIE Zcrt1.s Zcrt2.c /usr/lib/crti.o hello.c /usr/lib/crtn.o

где Zcrt1.s - это модифицированная версия Scrt1.s, которая вызывает функцию в Zcrt2.c перед выполнением своей нормальной работы, а код в Zcrt2.c обрабатывает вспомогательный вектор сразу после массивов argv и environment, чтобы найти ДИНАМИЧНЫЙ раздел, затем перебирает таблицы перемещений и применяет все перемещения относительного типа (единственные, которые должны существовать).

Теперь все это можно (с небольшой работой) обернуть в сценарий или файл спецификации gcc ...

ответил R.. 28 Mayam12 2012, 03:25:12
0

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

Например, на моем компьютере сценарии находятся в /usr/lib/ldscripts, а рассматриваемая строка - interp : { *(.interp) } в разделе SECTIONS.

Вы можете использовать скрипт по умолчанию, используя следующую команду:

$ ld --verbose ${YOUR_LD_FLAGS} | \
    gawk 'BEGIN { s = 0 } { if ($0 ~ /^=/) s = !s; else if (s == 1) print; }'

Вы можете немного изменить скрипт gawk, чтобы удалить interp (или просто используйте grep -v и используйте этот скрипт для связи вашей программы.

ответил rodrigo 11 Mayam12 2012, 07:46:22
0

Расширение моей предыдущей заметки, поскольку она не вписывается в эту маленькую коробку (и это просто идея или обсуждение, пожалуйста, не считайте себя обязанным принимать или поощрять щедрость), возможно, самый простой и чистый способ сделать это это просто добавить шаг после сборки, чтобы удалить заголовок PT_INTERP из полученного двоичного файла?

Даже проще, чем редактирование заголовков вручную и, возможно, необходимость все изменить, просто заменить PT_INTERP на PT_NULL. Я не знаю, можете ли вы найти способ простого исправления файла с помощью существующих инструментов (что-то вроде поиска и замены с помощью сценариев в шестнадцатеричном формате) или вам придется написать небольшую программу для этого. Я знаю, что libbfd (библиотека дескрипторов двоичных файлов GNU) может быть вашим другом в последнем случае, так как это облегчит весь этот бизнес.

Полагаю, я просто не понимаю, почему это важно сделать с помощью параметра ld. Если доступно, я могу понять, почему это было бы предпочтительнее; но, как некоторые (по общему признанию) гуглят, указывают на то, что такой функции нет, может быть, это будет меньше хлопот, если просто сделать это отдельно и по факту. (Возможно, добавить флаг в ld проще, чем написать скрипт для замены PT_INTERP с PT_NULL, но убедить разработчиков поднять его вверх по течению - это другое дело.)


Очевидно (и, пожалуйста, исправьте меня, если вы уже видели это), вы можете изменить поведение ld в отношении любой из заголовков ELF в вашем скрипте компоновщика с PHDRS команда и с использованием :none, чтобы указать, что определенный тип заголовка не должен быть включен ни в один сегмент. Я не уверен в синтаксисе, но я предполагаю, что он будет выглядеть примерно так:

PHDRS
{
  headers PT_PHDR PHDRS ;
  interp PT_INTERP ;
  text PT_LOAD FILEHDR PHDRS ;
  data PT_LOAD ;
  dynamic PT_DYNAMIC ;
}

SECTIONS
{
  . = SIZEOF_HEADERS;
  .interp : { } :none
  ...
}

Из документов ld Вы можете переопределить скрипт компоновщика с помощью --library-path:

--library-path=searchdir
  

Добавьте путь searchdir в список путей, которые будет искать ld   архивные библиотеки и скрипты управления ld. Вы можете использовать эту опцию любой   количество раз. Каталоги ищутся в порядке, в котором   они указаны в командной строке. Каталоги, указанные на   поиск в командной строке перед каталогами по умолчанию. Все -Л   параметры применяются ко всем параметрам -l независимо от порядка, в котором   варианты появляются. Набор путей по умолчанию для поиска (без   указывается в `-L ') зависит от того, какой режим эмуляции использует ld, и   в некоторых случаях также о том, как это было настроено. Смотрите раздел Окружающая среда   Переменные. Пути также могут быть указаны в сценарии ссылки с   Команда SEARCH_DIR. Каталоги, указанные таким образом, ищутся на   точка, в которой скрипт компоновщика появляется в командной строке.

Кроме того, из раздела, посвященного Неявные сценарии компоновщика :

  

Если вы укажете входной файл компоновщика, который компоновщик не сможет распознать   как объектный файл или архивный файл, он будет пытаться прочитать файл как   скрипт компоновщика. Если файл не может быть проанализирован как скрипт компоновщика,   компоновщик сообщит об ошибке.

Что может означать значения в пользовательских сценариях компоновщика, в отличие от неявно определенных сценариев компоновщика, заменит значения в сценариях по умолчанию.

ответил Mahmoud Al-Qudsi 11 Mayam12 2012, 07:31:05
0

Я не являюсь экспертом в GNU ld, но я нашел следующую информацию в документация :

  

Специальное secname `/DISCARD /'может использоваться для удаления входных разделов.   Любые разделы, которые назначены выходному разделу с именем `/DISCARD /'   не включены в окончательный вывод ссылки.

Надеюсь, это поможет вам.

UPDATE:

(Это первая версия решения, которая не работает, поскольку раздел INTERP удаляется вместе с заголовком PT_INTERP.)

main.c:

int main(int argc, char **argv)                                                                                                                               
{                                                                                                                                                             
    return 0;                                                                                                                                                 
}

main.x:

SECTIONS {                                                                                                                                                    
    /DISCARD/ : { *(.interp) }                                                                                                                                
}

команда сборки:

$ gcc -nostdlib -pie -static -Wl,-T,main.x main.c
$ readelf -S a.out | grep .interp

команда сборки без опции -Wl, -T, main.x:

$ gcc -nostdlib -pie -static main.c 
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000000218
$ readelf -S a.out | grep .interp
  [ 1] .interp           PROGBITS        00000134 000134 000013 00   A  0   0  1

ОБНОВЛЕНИЕ 2:

Идея этого решения заключается в том, что исходный раздел «INTERP» (. interp в файле сценария компоновщика) переименован в .interp1. Другими словами, все содержимое раздела помещается в раздел .interp1. Поэтому мы можем безопасно удалить раздел INTERP (теперь пустой), не опасаясь потери настроек скрипта компоновщика по умолчанию, и, следовательно, заголовок INTERP_PT также будет удален.

SECTIONS {
    .interp1 : { *(.interp); } : NONE
    /DISCARD/ : { *(.interp) }
}

Чтобы показать, что содержимое раздела INTERP присутствует в файле (как .interp1), но заголовок INTERP_PT удален, я использую комбинацию readelf + grep.

$ gcc -nostdlib -pie -Wl,-T,main.x main.c
$ readelf -l a.out | grep interp
   00     .note.gnu.build-id .text .interp1 .dynstr .hash .gnu.hash .dynamic .got.plt 
$ readelf -S a.out | grep interp
  [ 3] .interp1          PROGBITS        0000002e 00102e 000013 00   A  0   0  1
ответил alexander 18 Mayam12 2012, 01:31:14

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132