在内存中执行机器代码

时间:2010-01-07 11:35:46

标签: c segmentation-fault function-pointers casting

我正在试图弄清楚如何执行存储在内存中的机器代码。

我有以下代码:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    FILE* f = fopen(argv[1], "rb");

    fseek(f, 0, SEEK_END);
    unsigned int len = ftell(f);
    fseek(f, 0, SEEK_SET);

    char* bin = (char*)malloc(len);
    fread(bin, 1, len, f);

    fclose(f);

    return ((int (*)(int, char *)) bin)(argc-1, argv[1]);
}

上面的代码在GCC中编译得很好,但是当我尝试从命令行执行程序时这样:

./my_prog /bin/echo hello

程序段错误。我已经发现问题出在最后一行,因为评论它会阻止段错误。

我不认为我做得很对,因为我仍然在考虑功能指针。

这个问题是错误的演员还是其他什么?

9 个答案:

答案 0 :(得分:28)

您需要一个具有写入执行权限的页面。如果您在unix下,请参阅mmap(2)和mprotect(2)。你不应该使用malloc。

另外,阅读其他人所说的内容,您只能使用加载程序运行原始机器代码。如果你试图运行一个ELF标题,它可能会发生段错误。

关于回复和下载的内容:

1- OP说他正在尝试运行机器代码,所以我回答了这个问题而不是执行可执行文件。

2-了解为什么不混合malloc和mman函数:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>

int main()
{
    char *a=malloc(10);
    char *b=malloc(10);
    char *c=malloc(10);
    memset (a,'a',4095);
    memset (b,'b',4095);
    memset (c,'c',4095);
    puts (a);
    memset (c,0xc3,10); /* return */

    /* c is not alligned to page boundary so this is NOOP.
     Many implementations include a header to malloc'ed data so it's always NOOP. */
    mprotect(c,10,PROT_READ|PROT_EXEC);
    b[0]='H'; /* oops it is still writeable. If you provided an alligned
    address it would segfault */
    char *d=mmap(0,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);
    memset (d,0xc3,4096);
    ((void(*)(void))d)();
    ((void(*)(void))c)(); /* oops it isn't executable */
    return 0;
}

它在Linux x86_64上显示了这种行为,确实会在其他实现中出现其他丑陋行为。

答案 1 :(得分:12)

在我看来,你正在加载一个ELF图像,然后试图直接跳入ELF标题? http://en.wikipedia.org/wiki/Executable_and_Linkable_Format

如果您正在尝试执行另一个二进制文件,为什么不在您使用的任何平台上使用流程创建功能?

答案 2 :(得分:12)

使用malloc可以正常工作。

好的,这是我的最终答案,请注意我使用了原始海报的代码。 我正在从磁盘加载,这个代码的编译版本到堆分配区域“bin”,正如orignal代码所做的那样(名称修复不使用argv,值0x674来自;

objdump -F -D foo|grep -i hoho
08048674 <hohoho> (File Offset: 0x674):

这可以通过BFD(二进制文件描述符库)或其他东西在运行时查找,只要它们静态链接到同一组lib,就可以调用其他二进制文件(不仅仅是你自己)。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

unsigned char *charp;
unsigned char *bin;

void hohoho()
{
   printf("merry mas\n");
   fflush(stdout);
}

int main(int argc, char **argv)
{
   int what;

   charp = malloc(10101);
   memset(charp, 0xc3, 10101);
   mprotect(charp, 10101, PROT_EXEC | PROT_READ | PROT_WRITE);

   __asm__("leal charp, %eax");
   __asm__("call (%eax)" );

   printf("am I alive?\n");

   char *more = strdup("more heap operations");
   printf("%s\n", more);

   FILE* f = fopen("foo", "rb");

   fseek(f, 0, SEEK_END);
   unsigned int len = ftell(f);
   fseek(f, 0, SEEK_SET);

   bin = (char*)malloc(len);
   printf("read in %d\n", fread(bin, 1, len, f));
   printf("%p\n", bin);

   fclose(f);
   mprotect(&bin, 10101, PROT_EXEC | PROT_READ | PROT_WRITE);

   asm volatile ("movl %0, %%eax"::"g"(bin));
   __asm__("addl $0x674, %eax");
   __asm__("call %eax" );
   fflush(stdout);

   return 0;
}

...运行

co tmp # ./foo
am I alive?
more heap operations
read in 30180
0x804d910
merry mas

您可以使用UPX来管理文件的加载/修改/执行。

P.S。对于之前的断开链接感到抱歉:|

答案 3 :(得分:3)

典型的可执行文件包含:

  • 标题
  • main(int, char **)
  • 之前调用的条目代码

第一个意味着你通常不能指望文件的字节0是可执行的; intead,标题中的信息描述了如何在内存中加载文件的其余部分以及从何处开始执行它。

第二个意味着当你找到入口点时,你不能指望它像参数(int, char **)的C函数一样对待它。它也许可以用作不带参数的函数(因此在调用它之前不需要任何东西被推动)。但是您确实需要填充环境,而环境将由条目代码用于构造传递给main的命令行字符串。

在给定的操作系统下手动执行此操作会进入一些超出我的深度;但我确信有一种更好的方法可以做你想做的事情。您是尝试将外部文件作为开关操作执行,还是加载外部二进制文件并将其功能视为程序的一部分?两者都是由Unix中的C库来满足的。

答案 4 :(得分:3)

更有可能的是,通过函数指针调用跳转到的代码导致了段错误,而不是调用本身。您发布的代码无法确定加载到bin中的代码是否有效。最好的办法是使用调试器,切换到汇编程序视图,中断return语句并单步进入函数调用,以确定您希望运行的代码确实在运行,并且它是有效的

另请注意,为了在所有代码中运行,需要位置无关并完全解析。

此外,如果您的处理器/操作系统能够防止数据执行,那么尝试可能注定失败。在任何情况下,最好是不明智的,加载代码就是操作系统的用途。

答案 5 :(得分:3)

你要做的是类似于口译员所做的事情。除了解释器读取用Python等解释语言编写的程序之外,动态编译该代码,将可执行代码放入内存然后执行它。

您可能还想了解更多有关即时编译的信息:

Just in time compilation
Java HotSpot JIT runtime

如果您感兴趣,可以使用JIT代码生成库,例如GNU lightninglibJIT。但是,您必须做的不仅仅是从文件中读取并尝试执行代码。示例使用场景将是:

  1. 阅读用脚本语言编写的程序(也许吧 你自己的。)
  2. 解析并编译源代码 中间语言理解 JIT图书馆。
  3. 使用JIT库生成代码 对于这个中级 表示,用于目标平台的CPU。
  4. 执行JIT生成的代码。
  5. 为了执行代码,你必须使用诸如使用mmap()将可执行代码映射到进程的地址空间之类的技术,标记该页面可执行文件并跳转到那段内存。它比这更复杂,但它是一个很好的开始,以便了解所有脚本语言解释器(如Python,Ruby等)下发生的事情。

    本书“online version”的Linkers and Loaders将为您提供有关目标文件格式的更多信息,执行程序时幕后的内容,链接器和加载器的角色等等。上。这是一个非常好的阅读。

答案 6 :(得分:2)

使用操作系统加载和执行程序。

在unix上,exec调用可以执行此操作。

问题中的代码段可以改写:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    return execv(argv[1],argv+2);
}

答案 7 :(得分:1)

你可以dlopen()一个文件,查找符号“main”并用0,1,2或3个参数(所有类型为char *)通过强制转换为指向函数return-int来调用它-taking-0,1,2,或3-字符*​​

答案 8 :(得分:0)

可执行文件包含的不仅仅是代码。头文件,代码,数据,更多数据,这些东西被操作系统及其库分离并加载到不同的内存区域。您无法将程序文件加载到单个内存块中,并且希望跳转到它的第一个字节。

如果您尝试执行自己的任意代码,则需要查看动态库,因为这正是它们的目的。