代码之家  ›  专栏  ›  技术社区  ›  anonymous coward

在内存中执行机器代码

  •  26
  • anonymous coward  · 技术社区  · 15 年前

    我想知道如何执行存储在内存中的机器代码。

    #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]);
    }
    

    ./my_prog /bin/echo hello
    

    程序有故障。我发现问题出在最后一行,因为注释出来会停止故障。

    我认为我做得不太对,因为我仍然在思考函数指针。

    9 回复  |  直到 15 年前
        1
  •  12
  •   ta.speot.is    15 年前

    在我看来,你似乎正在加载一个精灵图像,然后试图直接跳入精灵头部? http://en.wikipedia.org/wiki/Executable_and_Linkable_Format

    如果您试图执行另一个二进制文件,为什么不为您使用的任何平台使用流程创建函数呢?

        2
  •  30
  •   user14554 user14554    15 年前

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

    另外,请阅读其他人所说的,您只能使用加载程序运行原始机器代码。如果你试图运行一个ELF头,它可能仍然会出错。

    关于回复和downmods的内容:

    1-OP说他试图运行机器代码,所以我回答说,而不是执行一个可执行文件。

    #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上正好显示了这种行为,其他实现中肯定会出现其他丑陋的行为。

        3
  •  13
  •   RandomNickName42    15 年前

    使用malloc很好。

    好的,这是我的最终答案,请注意我使用了原始海报的代码。 我正在从磁盘加载此代码的编译版本到堆分配区域“bin”,就像原始代码一样(名称不使用argv固定,值0x674来自;

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

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

    #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

    另外,很抱歉之前断开的链接:|

        4
  •  4
  •   Edmund    15 年前

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

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

    第二种方法是,当您找到入口点时,您不能期望将其视为一个接受参数的C函数 (int, char **) . 它也许可以作为一个不带参数的函数使用(因此在调用它之前不需要推送任何东西)。但是,您确实需要填充环境,然后输入代码将使用该环境来构造传递给main的命令行字符串。

    在一个给定的操作系统下手工操作会深入到我无法理解的深度;但我相信有一种更好的方式来做你想做的事。您是试图将外部文件作为开关操作执行,还是加载外部二进制文件并将其功能作为程序的一部分?Unix中的C库可以满足这两种需求。

        5
  •  3
  •   Clifford    15 年前

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

    还要注意的是,为了运行在所有的代码将需要 位置独立

    此外,如果您的处理器/操作系统启用了数据执行预防,那么这种尝试很可能是注定要失败的。在任何情况下,这充其量都是不明智的,加载代码是操作系统的目的。

        6
  •  2
  •   Sudhanshu    15 年前

    你试图做的事情与口译员的工作类似。除此之外,解释器读取用Python等解释语言编写的程序,动态编译代码,将可执行代码放入内存,然后执行它。

    您可能还想阅读更多有关即时编译的内容:

    Just in time compilation
    Java HotSpot JIT runtime

    有一些库可用于JIT代码生成,例如 GNU lightning libJIT

    1. 阅读用脚本语言编写的程序(可能是
    2. 将源代码解析并编译为 中间语言
    3. 使用JIT库生成代码 对于这个中间体 表示,用于目标平台的CPU。
    4. 执行JIT生成的代码。

    为了执行代码,您必须使用一些技术,例如使用mmap()将可执行代码映射到进程的地址空间,将该页标记为可执行并跳转到该内存段。这比这更复杂,但为了理解Python、Ruby等脚本语言的解释器下面发生的事情,这是一个很好的开始。

    这个 online version “本书的主题” Linkers and Loaders “将为您提供有关对象文件格式、执行程序时幕后发生的情况、链接器和加载器的角色等更多信息。这本书读得很好。

        7
  •  1
  •   emvee    15 年前

    您可以dlopen()打开一个文件,查找符号“main”,并通过转换为pointer-to-function-returning-int-taking-0,1,2或3 char使用0、1、2或3个参数(所有类型均为char*)调用它*

        8
  •  1
  •   Will    15 年前

    在unix上 exec 电话可以做到这一点。

    您的问题片段可以重写:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char* argv[])
    {
        return execv(argv[1],argv+2);
    }
    
        9
  •  0
  •   Jimbo    15 年前

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