К сожалению, без изменения ваших двоичных объектов, динамического компоновщика или динамического загрузчика вы не сможете этого сделать, и в любом случае это очень сложная задача.
Вариант 1 - манипуляции с ELF
Каждый исполняемый файл ELF состоит из разделов, которые содержат фактический код / данные / строки символов / ... и сегменты, которые помогают загрузчику решать такие вещи, как, например, куда загрузить ваш код в память, какие символы предоставляет этот ELF, какие символы он требует от другие места, где загрузить определенный код / данные и т. д.
Вы можете наблюдать за сегментами в вашем двоичном файле, набрав
readelf -l [ваш двоичный файл]
Результат будет похож на следующий (я выбрал ls как двоичный):
[ishaypeled @ ishay-dev bin] $ readelf -l --wide ./ls
Elf file type is EXEC (Executable file)
Entry point 0x4048bf
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000040 0x0000000000400040 0x0000000000400040 0x0001f8 0x0001f8 R E 0x8
INTERP 0x000238 0x0000000000400238 0x0000000000400238 0x00001c 0x00001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x01b694 0x01b694 R E 0x200000
LOAD 0x01bdf0 0x000000000061bdf0 0x000000000061bdf0 0x000864 0x0016d0 RW 0x200000
DYNAMIC 0x01be08 0x000000000061be08 0x000000000061be08 0x0001f0 0x0001f0 RW 0x8
NOTE 0x000254 0x0000000000400254 0x0000000000400254 0x000044 0x000044 R 0x4
GNU_EH_FRAME 0x01895c 0x000000000041895c 0x000000000041895c 0x00071c 0x00071c R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x01bdf0 0x000000000061bdf0 0x000000000061bdf0 0x000210 0x000210 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
Теперь давайте рассмотрим этот вывод:
В первой таблице (Заголовки программ):
[Тип] - Тип сегмента, для чего предназначен этот раздел
[Смещение] - Смещение в файле, где начинается этот сегмент
[VirtAddr] - Где мы хотим для загрузки этого раздела в адресное пространство процесса (если этот сегмент должен быть загружен вообще, загружаются не все из них)
[PhysAddr] - То же, что и VirtAddr для всех современных ОС, с которыми я сталкивался
[FileSiz] - Насколько велик есть ли этот раздел в файле. Это ссылка на ваши разделы - текущий сегмент состоит из всех разделов в диапазоне от Offset to Offset + FileSiz
[MemSiz] - Насколько велик этот раздел в виртуальной памяти (это НЕ обязательно должно быть таким же, как размер в файле! если он превышает размер файла, избыток устанавливается в 0)
[Flg] - Флаги разрешений, R-чтение E-выполнение W-запись. [Align] - Требуется выравнивание памяти в памяти.
Ваше внимание сосредоточено на сегментах типа LOAD (PT_LOAD). Эти сегменты группируют данные из разделов, указывают загрузчику, куда их поместить в адресное пространство процесса, и определяют их разрешения.
Вы можете увидеть удобный раздел для сопоставления сегментов в таблице сопоставления раздела с сегментами.
Давайте рассмотрим два сегмента LOAD 2 и 3:
Мы видим, что сегмент 2 имеет разрешения на чтение и выполнение и что он охватывает (среди прочего) разделы .text и .rodata.
Итак, для достижения вашей цели с помощью манипуляции с ELF:
- Найдите двоичные данные, которые делают ваши функции в файле (утилита readelf - ваш друг)
- Изменив заголовок ELF (я не знаю ни одного инструмента, который делает это автоматически, вам, вероятно, придется написать свой собственный), разделите сегмент, содержащий раздел .text, на два последовательных сегмента LOAD, оставив свой код функции
- Изменив заголовок ELF, создайте новый сегмент LOAD, содержащий только две ваши функции.
- Обновите все ссылки (если есть) со старого местоположения функции на новое.
Если вы дочитали до этого места и все поняли, вы должны знать, что это чрезвычайно утомительная и практически невыполнимая задача для реальных случаев.
Вариант 2 - Динамическое изменение компоновщика. Обратите внимание на тип сегмента INTERP в приведенном выше примере. Это строка ASCII, которая указывает, какой динамический компоновщик вам следует использовать.
Роль динамического компоновщика заключается в анализе сегментов и выполнении всех динамических операций, таких как разрешение символов во время выполнения, загрузка сегментов из файла .so и т. Д.
Возможной манипуляцией здесь может быть изменение кода динамического компоновщика (ПРИМЕЧАНИЕ: это изменение всей системы!) Для загрузки двоичных данных функций в определенный адрес памяти в адресном пространстве процесса. Обратите внимание, что у этого подхода есть несколько недостатков:
- Требуется модификация динамического компоновщика
- Вам необходимо обновить все ссылки на ваши функции в файле ELF.
Вариант 3 - манипуляции с динамическим загрузчиком. Очень похоже на вариант 2, но изменяет возможности библиотеки ld вместо динамического компоновщика.
Заключение. То, что вы хотите делать, - очень сложная и утомительная задача. В настоящий момент я работаю над инструментом, который позволяет внедрять произвольные функции в существующие общие объектные файлы, и я гарантирую, что это займет как минимум несколько хороших недель работы.
Вы уверены, что нет другого способа добиться того, что ты хочешь? Зачем вам эти две функции по отдельному адресу? Возможно, есть решение попроще ...
person
Ishay Peled
schedule
24.03.2015