用c语言在内存中执行二进制代码

用c语言在内存中执行二进制代码,c,linux,x86-64,dynamic-memory-allocation,C,Linux,X86 64,Dynamic Memory Allocation,我目前正忙于我的c项目,我正试图用c语言在内存中运行一个程序,但一直都有分段错误 首先,我有一个简单的hello\u world.c,我编译它,然后输出hello\u world 第二,我将hello\u world转换为C头,如下所示: #include <stdio.h> #include <string.h> #include <sys/mman.h> #include "hello.h" int main () { int

我目前正忙于我的c项目,我正试图用c语言在内存中运行一个程序,但一直都有分段错误

首先,我有一个简单的
hello\u world.c
,我编译它,然后输出
hello\u world

第二,我将
hello\u world
转换为C头,如下所示:

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include "hello.h"

int main ()
{
    int (*my_hello) () = NULL;

    // allocate executable buffer
    my_hello = mmap (0, sizeof(hello), PROT_READ|PROT_WRITE|PROT_EXEC,
                MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

    // copy code to buffer
    memcpy (my_hello, hello, sizeof(hello));
    __builtin___clear_cache (my_hello, my_hello + sizeof(my_hello));  // GNU C

    // run code
 my_hello ();


}
xxd-i hello\u world>hello.h

(hello.h的内容)

最后,我创建了程序
main.c
,如下所示:

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include "hello.h"

int main ()
{
    int (*my_hello) () = NULL;

    // allocate executable buffer
    my_hello = mmap (0, sizeof(hello), PROT_READ|PROT_WRITE|PROT_EXEC,
                MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

    // copy code to buffer
    memcpy (my_hello, hello, sizeof(hello));
    __builtin___clear_cache (my_hello, my_hello + sizeof(my_hello));  // GNU C

    // run code
 my_hello ();


}
#包括
#包括
#包括
#包括“hello.h”
int main()
{
int(*my_hello)()=NULL;
//分配可执行缓冲区
my_hello=mmap(0,sizeof(hello),PROT_READ | PROT_WRITE | PROT_EXEC,
MAP|u PRIVATE | MAP|u ANONYMOUS,-1,0);
//将代码复制到缓冲区
memcpy(我的你好,你好,sizeof(你好));
__内置清除缓存(my_hello,my_hello+sizeof(my_hello));//GNU C
//运行代码
你好;
}
我用以下两个主题来编写此代码:

在本例中,我尝试使用hello_world,但正如您可以使用gess一样,我希望使用其他程序(如linux实用程序)来实现


我不太擅长C,所以欢迎任何帮助

这是一个比你想象的要复杂得多的问题:/

唯一的“简单”选项是
write()
将嵌入数组写入
/tmp
execve
中的文件,以利用内核的ELF程序加载器和系统的动态链接器。这将有效地创建一个自解压的可执行包装器。(通常您希望使用
zstd
或gzip/deflate压缩文件,至少这是使用自解压包装器的正常原因。)


Linux可执行文件以元数据开始;您不能将整个内容加载到内存中,然后跳转到第一个字节。(除了DOS
.com
,其他所有内容都是如此)

ELF程序头说明可执行文件的哪些部分需要映射到内存中的哪个相对位置(通常由内核的ELF程序加载器设置类似mmap的映射)

e、 g..data可能位于磁盘上的.text旁边,但标头会将它们映射到相隔一定距离的位置,因此数组越界可能是错误的,而不是读取代码。另外,
.bss
部分需要单独分配,要么超过.data结尾,要么作为单独的映射\u匿名映射,因此它最初用零填充

您的包装程序已经有一个堆栈,但可执行文件的_开始入口点将预期argc、argv[]和envp[]将位于堆栈上,如系统V ABI doc中指定的那样。(例如x86-64系统V)

此外,Linux上使用的x86-64和i386 System V ABI期望堆栈指针在进入
\u start
时与16字节对齐。与函数调用不同,函数调用应该在
调用之前对齐,从而导致函数入口点处出现
RSP%16==8
。如果程序依赖于传入的堆栈对齐,则某些x86-64库函数可能会崩溃


它也可能是动态链接的,因此您需要处理重定位元数据,以便将其GOT(和PLT)条目连接到libc.so.6及其使用的任何其他库中的符号。i、 e.您需要实现ELF程序加载器和
ld.so
的动态链接功能

如果静态链接库,则可以避免该部分,例如使用
gcc-static pie-fPIE
编译
hello\u world
,链接静态但p位置-I独立的e可执行,因此,它有望在mmap随机选择的任何加载地址上工作

PIE代码可能仍然包含绝对地址作为静态初始值设定项,如
static char*ptr=foo。CRT
\u start
代码负责在静态饼图中应用这些重新定位修复()。跳转到ELF入口点将运行该代码。它应该通过按照ELF头读取映射到内存中的元数据来工作,所以如果你做对了,它可能就可以工作了。但如果不是这样,您可能需要仔细编写代码,以避免将地址作为静态初始值设定项

IDK如果您要链接的静态
libc.a
将使用
-fPIE
编译,那么它本身可能包含绝对地址(在机器代码中可能与32位绝对地址一样直接,这使得在x86-64:)上无法链接成饼图)


我猜另一种选择是将嵌入的可执行文件设置为非饼状(
gcc-fno-PIE-no-PIE-static
),并使用
mmap(MAP\u FIXED)
将其映射到虚拟地址空间中的正确位置。(或使用“提示”没有固定映射的mmap地址,如果没有将其放在您要求的位置,则返回一个错误:这意味着其他东西已经在使用虚拟内存空间中的该空间。当然,如果您要跳转到它的
\u start
,它将有效地接管您的进程,并且不会返回。它最终将
\u exit
,或者xecve是另外一回事。但是如果你把复制和跳转的代码吹走了,你仍然会有问题。)

因此,为了避免地址空间冲突,请将包装程序设置为饼图(现代Linux发行版上GCC的默认设置),以便将其映射到
0x555…
,而不是
0x400000
,或者对其中一个或另一个使用自定义链接器脚本,以便它们都可以是非饼图,但使用不同的基地址

有关PIE(位置独立可执行文件)的详细信息:

  • 无LIB的饼图与静态饼图

所有这些都无法避免以某种方式解析ELF头以找到要跳转到的入口点,而不是缓冲区的头部。