如何让 bash 从 stdin 执行 ELF 二进制文件?
How can I make bash execute an ELF binary from stdin?
出于某些不明确的原因,我编写了一个 bash 脚本,它生成一些源代码,然后使用
编译它
... whatever ... | gcc -x c -o /dev/stdout
现在,我想在编译上执行结果。我怎样才能做到这一点?请不要使用文件。
正如 Charles Duffy 所说,要执行二进制文件,您必须告诉您的操作系统(这似乎是 Unix 变体)加载并执行某些内容 – 而 Unix 系统仅获取文件来直接执行它们。
你可以做的是有一个准备包含 ELF 二进制文件的内存区域的进程,分叉并跳转到该区域 - 但即使这样也是有问题的,考虑到 CPU 支持抑制 正是那个操作(R^X)。基本上,您需要的是一个运行时链接器,而 shell 不(也不应该)包含类似的东西。
让我们放弃 Bash 要求(这听起来真的就像你试图在一个比我脾气暴躁的旧应用程序中找到一个明显的漏洞):
通常,需要 ELF(这是一种 file 格式)并同时避免 files 有点复杂。 GCC 生成机器代码。如果您只想执行已知的机器代码,请将其放入某个缓冲区,构建指向该缓冲区的函数指针并调用它。就那么简单。但是,您显然没有执行 ELF 二进制文件或加载共享对象 (dlopen
) 的过程所具有的所有良好的重定位和动态链接。
如果您愿意,我会看向像 LLVM 这样的东西的方向——我知道,事实上,有人在构建 "I compile C++ at runtime and execute it" 时使用 LLVM 作为执行实例,并使用 clang 作为编译器。最后,你的 gcc|something
实际上只是 JIT——一种古老的技术 :)
如果您的目标是完全不写入文件系统,那么 bash
和任何其他 UNIX 程序都无法帮助您从管道执行 ELF - execve
只需要一个常规文件的路径作为它的 filename
并且如果你传递一个特殊文件(设备或命名管道)或目录将失败(设置 errno
到 EACCES
)。
但是,如果您的目标是将可执行文件完全保存在 RAM 中而不接触硬盘(可能是因为磁盘是只读的),您可以通过使用 tmpfs
,它与许多类 UNIX 系统一起提供(并在 Linux 中用于实现信号量)并允许您创建一个完全驻留在 RAM 中的完全权限文件系统:
$ sudo mount -t tmpfs -o size=10M tmpfs /mnt/mytmpfs
然后您可以将二进制文件写入:
... whatever ... | gcc -x c -o /mnt/mytmpfs/program.out
/mnt/mytmpfs/program.out
和 bash
会像在磁盘上一样为您加载它。
但是请注意,您仍然需要设备上有足够的 RAM 来存储和执行程序 - 尽管由于大多数可执行二进制文件的性质,无论如何您都需要它。
如果你不想把程序留在你的虚拟磁盘(或普通磁盘,如果可以的话)上让别人找到,你也可以在开始执行后立即删除文件:
/mnt/mytmpfs/program.out &
rm /mnt/mytmpfs/program.out
名称将立即消失,但进程将在内部持有该文件的硬 link,然后在终止时释放该硬 link,从而使该文件立即从磁盘中删除. (但是,在程序退出之前,存储实际上不会被释放,程序也无法 exec
自身)。
出于某些不明确的原因,我编写了一个 bash 脚本,它生成一些源代码,然后使用
编译它... whatever ... | gcc -x c -o /dev/stdout
现在,我想在编译上执行结果。我怎样才能做到这一点?请不要使用文件。
正如 Charles Duffy 所说,要执行二进制文件,您必须告诉您的操作系统(这似乎是 Unix 变体)加载并执行某些内容 – 而 Unix 系统仅获取文件来直接执行它们。
你可以做的是有一个准备包含 ELF 二进制文件的内存区域的进程,分叉并跳转到该区域 - 但即使这样也是有问题的,考虑到 CPU 支持抑制 正是那个操作(R^X)。基本上,您需要的是一个运行时链接器,而 shell 不(也不应该)包含类似的东西。
让我们放弃 Bash 要求(这听起来真的就像你试图在一个比我脾气暴躁的旧应用程序中找到一个明显的漏洞):
通常,需要 ELF(这是一种 file 格式)并同时避免 files 有点复杂。 GCC 生成机器代码。如果您只想执行已知的机器代码,请将其放入某个缓冲区,构建指向该缓冲区的函数指针并调用它。就那么简单。但是,您显然没有执行 ELF 二进制文件或加载共享对象 (dlopen
) 的过程所具有的所有良好的重定位和动态链接。
如果您愿意,我会看向像 LLVM 这样的东西的方向——我知道,事实上,有人在构建 "I compile C++ at runtime and execute it" 时使用 LLVM 作为执行实例,并使用 clang 作为编译器。最后,你的 gcc|something
实际上只是 JIT——一种古老的技术 :)
如果您的目标是完全不写入文件系统,那么 bash
和任何其他 UNIX 程序都无法帮助您从管道执行 ELF - execve
只需要一个常规文件的路径作为它的 filename
并且如果你传递一个特殊文件(设备或命名管道)或目录将失败(设置 errno
到 EACCES
)。
但是,如果您的目标是将可执行文件完全保存在 RAM 中而不接触硬盘(可能是因为磁盘是只读的),您可以通过使用 tmpfs
,它与许多类 UNIX 系统一起提供(并在 Linux 中用于实现信号量)并允许您创建一个完全驻留在 RAM 中的完全权限文件系统:
$ sudo mount -t tmpfs -o size=10M tmpfs /mnt/mytmpfs
然后您可以将二进制文件写入:
... whatever ... | gcc -x c -o /mnt/mytmpfs/program.out
/mnt/mytmpfs/program.out
和 bash
会像在磁盘上一样为您加载它。
但是请注意,您仍然需要设备上有足够的 RAM 来存储和执行程序 - 尽管由于大多数可执行二进制文件的性质,无论如何您都需要它。
如果你不想把程序留在你的虚拟磁盘(或普通磁盘,如果可以的话)上让别人找到,你也可以在开始执行后立即删除文件:
/mnt/mytmpfs/program.out &
rm /mnt/mytmpfs/program.out
名称将立即消失,但进程将在内部持有该文件的硬 link,然后在终止时释放该硬 link,从而使该文件立即从磁盘中删除. (但是,在程序退出之前,存储实际上不会被释放,程序也无法 exec
自身)。