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

Проблема

Я хочу добавить объектный файл в существующий двоичный файл. В качестве конкретного примера рассмотрим источник 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 не является объектным файлом.
  • Найдите существующий инструмент, который выполняет такую ​​вставку?

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

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

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

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

Ответ 1

Если бы это был я, я хотел бы создать 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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

С сайта выпуска:

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

Ответ 5

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

Файл исходного исходного кода, hello.c

#include <stdio.h>

int main ()
{

   return 0;
}

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

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 */
}

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

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