Как связать объектный файл с исполняемым / скомпилированным двоичным файлом? - PullRequest
10 голосов
/ 26 февраля 2012

Проблема

Я хочу добавить объектный файл в существующий двоичный файл.В качестве конкретного примера рассмотрим источник Hello.c:

#include <stdlib.h>

int main(void)
{
    return EXIT_SUCCESS;
}

. Его можно скомпилировать в исполняемый файл с именем от Hello до gcc -std=gnu99 -Wall Hello.c -o Hello.Кроме того, теперь рассмотрим Embed.c:

func1(void)
{
}

Объектный файл Embed.o может быть создан из этого через gcc -c Embed.c.Мой вопрос заключается в том, как в общем случае вставить Embed.o в Hello таким образом, чтобы были выполнены необходимые перемещения, и соответствующие внутренние таблицы ELF (например, таблица символов, PLT и т. Д.) Были исправлены правильно?


Допущения

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


Текущие попытки / идеи

  • Использованиеlibbfd для копирования разделов из объектного файла в двоичный файл.Достигнутый мной прогресс заключается в том, что я могу создать новый объект с разделами из исходного двоичного файла и разделами из объектного файла.Проблема заключается в том, что, поскольку объектный файл можно перемещать, его разделы не могут быть надлежащим образом скопированы в выходные данные без предварительного выполнения перемещений.
  • Преобразование двоичного файла обратно в объектный файл и связывание с ld.До сих пор я пытался использовать objcopy для преобразования objcopy --input elf64-x86-64 --output elf64-x86-64 Hello Hello.o.Очевидно, это не работает, как я намереваюсь, так как ld -o Hello2 Embed.o Hello.o приведет к ld: error: Hello.o: unsupported ELF file type 2.Я предполагаю, что этого следует ожидать, поскольку Hello не является объектным файлом.
  • Найти существующий инструмент, который выполняет вставку такого типа?

Обоснование (Необязательно для чтения)

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

  1. Вставка объектного файла (содержащего пользовательские процедуры) в двоичный файл. Это обязательный шаг, и его нельзя обойти альтернативами, такими как внедрение общего объекта.
  2. Выполнение статического анализа нового двоичного файла и использование его для статического обхода подпрограмм изисходный код недавно добавленного кода.

По большей части я уже выполнил работу, необходимую для шага 2, но у меня возникают проблемы с внедрением объектного файла.Эта проблема определенно решаема, поскольку другие инструменты используют тот же метод ввода объектов (например, EEL ).

Ответы [ 7 ]

5 голосов
/ 26 февраля 2012

Если бы это был я, я бы хотел создать Embed.c в общем объекте libembed.so, вот так:

gcc -Wall -shared -fPIC -o libembed.so Embed.c

Это должно создать перемещаемый общий объект из Embed.c. При этом вы можете заставить целевой двоичный файл загружать этот общий объект, установив переменную окружения LD_PRELOAD при его запуске (см. Дополнительную информацию здесь ):

LD_PRELOAD=/path/to/libembed.so Hello

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

void __attribute__ ((constructor)) my_init()
{
    // put code here!
}
2 голосов
/ 08 мая 2014

Если исходный код для первого исполняемого файла доступен и скомпилирован с помощью сценария компоновщика, который выделяет пространство для более поздних объектных файлов, существует относительно более простое решение.Поскольку в настоящее время я работаю над проектом ARM, приведенные ниже примеры скомпилированы с помощью кросс-компилятора GNU ARM.

Основной файл исходного кода, hello.c

#include <stdio.h>

int main ()
{

   return 0;
}

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

SECTIONS
{
    .text :
    {
        KEEP (*(embed)) ;

        *(.text .text*) ;
    }
}

Как:

arm-none-eabi-gcc -nostartfiles -Ttest.ld -o hello hello.c
readelf -s hello

Num:    Value  Size Type    Bind   Vis      Ndx Name
 0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
 1: 00000000     0 SECTION LOCAL  DEFAULT    1 
 2: 00000000     0 SECTION LOCAL  DEFAULT    2 
 3: 00000000     0 SECTION LOCAL  DEFAULT    3 
 4: 00000000     0 FILE    LOCAL  DEFAULT  ABS hello.c
 5: 00000000     0 NOTYPE  LOCAL  DEFAULT    1 $a
 6: 00000000     0 FILE    LOCAL  DEFAULT  ABS 
 7: 00000000    28 FUNC    GLOBAL DEFAULT    1 main

Теперь позволяет скомпилировать объект для встраивания, источник которого находится в embed.c

void func1()
{
   /* Something useful here */
}

Перекомпилируйте с тем же сценарием компоновщика, на этот раз вставив новые символы:

arm-none-eabi-gcc -c embed.c
arm-none-eabi-gcc -nostartfiles -Ttest.ld -o new_hello hello embed.o

См. Результаты:

readelf -s new_hello
Num:    Value  Size Type    Bind   Vis      Ndx Name
 0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
 1: 00000000     0 SECTION LOCAL  DEFAULT    1 
 2: 00000000     0 SECTION LOCAL  DEFAULT    2 
 3: 00000000     0 SECTION LOCAL  DEFAULT    3 
 4: 00000000     0 FILE    LOCAL  DEFAULT  ABS hello.c
 5: 00000000     0 NOTYPE  LOCAL  DEFAULT    1 $a
 6: 00000000     0 FILE    LOCAL  DEFAULT  ABS 
 7: 00000000     0 FILE    LOCAL  DEFAULT  ABS embed.c
 8: 0000001c     0 NOTYPE  LOCAL  DEFAULT    1 $a
 9: 00000000     0 FILE    LOCAL  DEFAULT  ABS 
10: 0000001c    20 FUNC    GLOBAL DEFAULT    1 func1
11: 00000000    28 FUNC    GLOBAL DEFAULT    1 main
1 голос
/ 10 апреля 2014

Интересная тема.У меня есть еще один конкретный пример того, почему это имеет смысл.

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

1) Зашифровать определенные секции эльфа (.text и т. Д.)

2) Связать эльфа с моими процедурами дешифрования и функцией __attribute__((constructor))это вызывает дешифрование зашифрованных разделов

. Таким образом, это будет работать с любыми программами без их ведома.

Я не нашел простого способа сделать это, поэтому мне, возможно, придется разделитьэльфа врозь и сам добавляй к нему вещи.

0 голосов
/ 16 декабря 2013

Вы должны освободить место для перемещаемого кода, чтобы он поместился в исполняемом файле, расширяя текстовый сегмент исполняемых файлов, как вирусная инфекция. Затем после записи перемещаемого кода в это пространство обновите таблицу символов, добавив символы для чего-либо в этом перемещаемом объекте, и затем примените необходимые вычисления перемещения. Я написал код, который делает это очень хорошо с 32-битными ELF.

0 голосов
/ 06 апреля 2012

Вы смотрели на DyninstAPI ? Похоже, недавно была добавлена ​​поддержка для соединения .o в статический исполняемый файл.

С сайта релиза:

Поддержка бинарных перезаписчиков для статически связанных двоичных файлов на платформах x86 и x86_64

0 голосов
/ 28 февраля 2012

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

Выполнение шага связывания с общей библиотекой не означает, что вы должны загрузить ее через динамический загрузчик библиотек. Предполагается, что собственный загрузчик для двоичной или разделяемой библиотеки может быть проще, чем для .o.

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

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

Полагаю, у вас это уже есть, потому что это ваша магистерская работа, но эта книга: http://www.iecc.com/linker/ - стандартное введение по этому поводу.

0 голосов
/ 26 февраля 2012

Вы не можете сделать это любым практическим способом.Предполагаемое решение состоит в том, чтобы превратить этот объект в общую библиотеку, а затем вызвать для нее dlopen.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...