可能吗?不是你似乎希望的方式。任务分为两部分:
1) 如何将二进制文件放入内存
当我们在 Linux 中指定 /dev/stdout 作为输出文件时,我们可以通过管道进入我们的程序 x0,它读取
来自标准输入的可执行文件并执行它:
gcc -pipe YourFiles1.cpp YourFile2.cpp -o/dev/stdout -Wall | ./x0
在x0 中,我们可以从标准输入读取直到到达文件末尾:
int main(int argc, const char ** argv)
{
const int stdin = 0;
size_t ntotal = 0;
char * buf = 0;
while(true)
{
/* increasing buffer size dynamically since we do not know how many bytes to read */
buf = (char*)realloc(buf, ntotal+4096*sizeof(char));
int nread = read(stdin, buf+ntotal, 4096);
if (nread<0) break;
ntotal += nread;
}
memexec(buf, ntotal, argv);
}
x0 也可以直接执行编译器并读取输出。这个问题已经在这里回答了:Redirecting exec output to a buffer or file
警告:我刚刚发现,由于某种奇怪的原因,这在我使用管道 | 时不起作用,但在我使用 x0 < foo 时起作用。
注意:如果你愿意修改你的编译器或者你做 JIT,比如 LLVM、clang 和其他框架,你可以直接生成可执行代码。但是对于本次讨论的其余部分,我假设您想使用现有的编译器。
注意:通过临时文件执行
其他程序(例如 UPX)通过执行临时文件来实现类似的行为,这比下面概述的方法更容易且更便携。在/tmp 映射到RAM 磁盘的系统上,例如典型的服务器,临时文件无论如何都是基于内存的。
#include<cstring> // size_t
#include <fcntl.h>
#include <stdio.h> // perror
#include <stdlib.h> // mkostemp
#include <sys/stat.h> // O_WRONLY
#include <unistd.h> // read
int memexec(void * exe, size_t exe_size, const char * argv)
{
/* random temporary file name in /tmp */
char name[15] = "/tmp/fooXXXXXX";
/* creates temporary file, returns writeable file descriptor */
int fd_wr = mkostemp(name, O_WRONLY);
/* makes file executable and readonly */
chmod(name, S_IRUSR | S_IXUSR);
/* creates read-only file descriptor before deleting the file */
int fd_ro = open(name, O_RDONLY);
/* removes file from file system, kernel buffers content in memory until all fd closed */
unlink(name);
/* writes executable to file */
write(fd_wr, exe, exe_size);
/* fexecve will not work as long as there in a open writeable file descriptor */
close(fd_wr);
char *const newenviron[] = { NULL };
/* -fpermissive */
fexecve(fd_ro, argv, newenviron);
perror("failed");
}
警告:为了清楚起见,省略了错误处理。为简洁起见包括在内。
注意:通过将步骤main() 和memexec() 合并到一个函数中并使用splice(2) 在stdin 和fd_wr 之间直接复制,可以显着优化程序。
2) 直接从内存执行
人们不会简单地从内存中加载和执行ELF 二进制文件。必须进行一些准备,主要与动态链接有关。有很多材料解释了 ELF 链接过程的各个步骤,研究它让我相信理论上是可能的。例如,请参阅此密切相关的 question on SO,但似乎不存在可行的解决方案。
更新 UserModeExec 似乎非常接近。
编写一个有效的实现会非常耗时,而且肯定会提出一些有趣的问题。我喜欢相信这是设计使然:对于大多数应用程序,强烈不希望(意外地)执行其输入数据,因为它允许code injection。
执行 ELF 时究竟会发生什么?通常,内核接收一个文件名,然后创建一个进程,加载可执行文件的不同部分并将其映射到内存中,执行大量健全性检查并将其标记为可执行文件,然后将控制权和文件名传回运行时链接器ld-linux.so(libc 的一部分)。负责重定位函数、处理附加库、设置全局对象和跳转到可执行文件入口点。 AIU 这个繁重的工作由 dl_main() 完成(在 libc/elf/rtld.c 中实现)。
甚至fexecve 也是使用/proc 中的文件实现的,正是这种对文件名的需求导致我们重新实现了此链接过程的某些部分。
图书馆
阅读
SO 的相关问题
所以看起来可能,你自己决定是否也实用。