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