【问题标题】:Execute binary file inside C code (No system())在 C 代码中执行二进制文件(无 system())
【发布时间】:2020-05-31 08:47:31
【问题描述】:

我正在尝试在 C 代码中执行二进制可执行文件而不使用 system,因为它存在安全和资源管理问题。

这里使用的系统是 Debian Buster,内核 5.4.0-2-amd64 和 gcc 9.2.1。

我在这个问题中使用了方法: execute binary machine code from 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

embedded.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

embedded.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 代码中并运行它吗?

【问题讨论】:

  • 实际上,内核支持执行二进制程序:它是exec function-family。通常与fork一起使用。

标签: c linux gcc binary segmentation-fault


【解决方案1】:

如果您想从正在运行的程序中直接执行可执行文件(system(3) 库函数会生成一个 shell 来运行它),您可以使用与运行二进制文件相同的系统调用(您正在执行并希望执行二进制文件)

在 unix 中,您首先需要创建第二个进程,它通常是带有fork(2) 系统调用的双胞胎(处于完全相同的执行状态)。 fork(2) 通过向每个进程返回不同的值来区分父进程和子进程(它将子进程的 pid 返回给父进程,向子进程返回 0)因此,从那时起,您可以遵循不同的路径在您的执行中,基于返回值。

通常情况下,父子进程再安排输入/输出重定向,也就是替换012打开文件描述符做重定向,然后子进程的内存为覆盖了一个新的可执行文件,该可执行文件由内核加载,带有execve(2) 系列系统调用。没有比这更安全的方法了。我不完全明白你所说的安全是什么意思,但是如果你不能用这种方法执行程序,那么就没有别的办法了。

您在内存中加载自己的二进制文件并尝试自己运行它的方法不仅容易出错,而且不可移植。正如其他答案中已经评论的那样,编译源文件不是解决方案,因为这需要您首先了解在一般 unix 系统中加载可执行文件的整个过程。可执行文件有一些文本段(通常它是只读的,以使可执行代码在运行同一程序的不同进程之间可共享)、一个读写数据段(通过对其发出系统调用来根据请求增长)和一个堆栈当 cpu 对它进行引用时,通常会自动增长的段(每个线程)。

所以你必须处理所有这些,这不像在一个简单的处理器上那么容易,所有内存都可以使用。您必须向操作系统询问您将用于运行程序的内存。例如,英特尔处理器中的可执行代码甚至不需要是可读内存(只有当作为指令加载到 cpu 时,读取内存操作才会成功,但是如果您尝试将数据作为数据读取,您将生成陷阱)

结论

阅读您的 linux 手册中的 fork(2)exec(2) 系统调用,以及基本的 unix 编程手册,了解如何在 unix 中执行程序。

【讨论】:

  • 我之所以说system不安全是因为system执行的命令与程序具有相同的权限运行,存在代码注入的风险。
  • 同样的事情也会发生在您作为数据加载到虚拟用户空间并赋予程序控制权的任何代码上。您只需要在加载代码之前注入代码...恐怕您不了解可以进行代码注入的可能性。您可以接收已经注入的文件,以便由您(或内核)加载
【解决方案2】:

您提供的可执行内容是 ELF 可执行文件,而不是原始机器代码。在 Linux 上,ELF 是用于包含可执行文件和共享库的容器格式,它将实际的可执行代码和相关数据(如字符串和数组)包装成一种易于加载的格式。

因此,您尝试执行的代码实际上并不是机器代码,而是 ELF 标头数据。即使从ELF中提取出可执行代码,一般还是需要动态链接器重定位,所以不容易直接执行。

相反,请考虑将附加代码放入共享库并在那里执行。如果需要动态加载代码,可以使用dlopen(3)函数加载共享库,使用dlsym(3)查找要执行的函数,只要链接-ldl即可。

【讨论】:

  • 那么,除了system 之外,以任何方式在 C 中执行 ELF 可执行文件都是不可行的,对吧?因为我有一个没有源代码的可执行文件,所以我无法用任何选项重新编译它。
  • 如果是这样,我正在考虑反汇编可执行文件并重复相同的过程。
  • 那么你需要使用forkexec系统调用。您会发现反汇编和重建二进制文件将非常困难且容易出错。
猜你喜欢
  • 1970-01-01
  • 2013-02-20
  • 2016-08-28
  • 2013-08-30
  • 1970-01-01
  • 1970-01-01
  • 2013-08-04
  • 2014-04-22
  • 2021-05-09
相关资源
最近更新 更多