Выполнить двоичный файл внутри C кода (без системы ()) - PullRequest
0 голосов
/ 16 февраля 2020

Я пытаюсь выполнить двоичный исполняемый файл внутри C кода без использования system, поскольку у него есть проблемы с безопасностью и управлением ресурсами.

Используемая здесь система - Debian Buster с ядром 5.4.0- 2-amd64 и g cc 9.2.1.

Я использовал метод в этом вопросе: выполнить двоичный машинный код из C

, который должен быть преобразован исполняется в шестнадцатеричном коде с xxd -i, но постоянно получает Segmentation fault.

Я использовал следующие процедуры:

Первая попытка

исполняемый файл. c:

#include <stdio.h>
int main(void)
{
    printf("Hello, World!\n");
    return 0;
}

после компиляции с помощью gcc -o executable executable.c

xxd -i executable отобразит двоичный файл в шестнадцатеричном формате

Затем скопируйте и вставьте вывод в embedded.c

встроенный. c:

#include <string.h>
#include <unistd.h>
#include <sys/mman.h>

const unsigned char[] executable = {
    0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01,
    ...
};

int main(void)
{
    void *buf = mmap(
        NULL, sizeof(executable), PROT_READ | PROT_WRITE | PROT_EXEC,
        MAP_PRIVATE | MAP_ANON, -1, 0);
    memcpy(buf, sizeof(executable);
    __builtin___clear_cache(buf, buf + sizeof(executable) - 1);

    int i = ((int (*) (void)buf)();
    return 0;
}

при компиляции и запуске терминал отображает Segmentation fault.

Вторая попытка

Еще один метод, который я попробовал, - использовать ld, который также отображал Segmentation fault:

встроенный. c:

extern const char _binary_executable_start[];
extern const char _binary_executable_end[];

// And same as the previous code.

Код был скомпилирован с использованием:

gcc -c -o embedded.o embedded.c

ld -r -b binary -o executable.o executable

gcc -o embedded embedded.o executable.o

И не удалось.

Есть что-то, что я пропустил или невозможно вставить двоичный файл в C код и запустить его?

Ответы [ 2 ]

2 голосов
/ 16 февраля 2020

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

В unix сначала необходимо создать второй процесс, который обычно является двойником (в том же состоянии выполнения) с системным вызовом fork(2). fork(2) делает различие между родительским и дочерним процессами, возвращая разные значения каждому (он возвращает pid дочернего элемента к родительскому и 0 к дочернему). Таким образом, с этого момента вы можете следовать различным путям в вашем исполнении на основе возвращаемого значения.

Обычно родительский и дочерний элементы затем организуют перенаправление ввода / вывода, что означает замену дескрипторов открытого файла 0, 1 и 2 на выполните перенаправление, и затем память дочернего процесса будет заполнена новым исполняемым файлом, который загружается на место ядром, с семейством системных вызовов execve(2). Нет более безопасного метода, чем этот. Я не совсем понимаю, что вы имеете в виду с безопасным, но если вы не можете выполнить программу с помощью этого метода, то больше ничего не поделаешь.

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

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

Conclussion

Прочтите о системных вызовах fork(2) и exec(2) в руководстве linux и в руководстве по основам c unix, чтобы получить хорошее введение о том, как выполнять программы в unix.

0 голосов
/ 16 февраля 2020

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

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

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

...