如何调用存储在字符数组中的机器码?

59

我正在尝试调用本地机器语言代码。以下是我目前的代码(它会导致总线错误):

char prog[] = {'\xc3'}; // x86 ret instruction

int main()
{
    typedef double (*dfunc)();

    dfunc d = (dfunc)(&prog[0]);
    (*d)();
    return 0;
}

它确实正确地调用了函数,并且执行到了 ret 指令。但是当它尝试执行 ret 指令时,出现了 SIGBUS 错误。这是因为我在未清除执行权限的页面上执行代码导致的吗?

那么我在这里做错了什么?


1
如果有帮助的话:我经常发现SIGBUS表明了错误的对齐。 - Doddy
7
@user请将您的解决方案作为答案发布,而不是将其编辑到问题中。我会将其翻译为:“请以答案的形式发布您的解决方案,而不是将其编辑到问题中。” - dandan78
3
我已经撤销了对代码示例所做的更改,这样问题又有意义了。请按照@dandan78已经建议的去接受一个答案,而不是在修改过程中改变问题的含义。 - You
3
使用asm()函数是否更实用? - Stavr00
9
请,请,请,请,请,请,请使用 asm()(根据 @Stavr00 的评论和 Graham 的回答)而不是其他任何方法,特别是如果您的代码有 任何 可能在与互联网连接或可能与除自己以外的任何人进行交互的硅片上看到日光的可能性。 - Kyle Strand
显示剩余4条评论
6个回答

53

首先可能存在的一个问题是,存储程序数据的位置不可执行。

至少在Linux上,生成的二进制文件将全局变量的内容放置在"数据段"此处,这在大多数情况下是不可执行的。

第二个问题可能是您调用的代码存在某些无效之处。在C语言中,有一定的过程来调用方法,称为调用约定(例如,您可能正在使用“cdecl”)。仅仅“ret”可能不足以让被调用的函数正常工作,它还可能需要进行一些堆栈清理等操作。否则,程序将表现出意外行为。一旦解决了第一个问题,这可能会成为一个问题。


5
这篇文章非常详细地介绍了如何在C语言中嵌入和调用机器代码,它首先从将main()函数转换为char数组的前提条件开始。请注意,我的翻译不包括解释和额外内容。 - event44

52

你需要调用memprotect函数来使存储prog代码的页面变得可执行。以下代码可以调用该函数,并执行prog中的文本。

#include <unistd.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>

char prog[] = {
   0x55,             // push   %rbp
   0x48, 0x89, 0xe5, // mov    %rsp,%rbp
   0xf2, 0x0f, 0x10, 0x05, 0x00, 0x00, 0x00,
       //movsd  0x0(%rip),%xmm0        # c <x+0xc>
   0x00,
   0x5d,             // pop    %rbp
   0xc3,             // retq
};

int main()
{
    long pagesize = sysconf(_SC_PAGE_SIZE);
    long page_no = (long)prog/pagesize;
    int res = mprotect((void*)(page_no*pagesize), (long)page_no+sizeof(prog), PROT_EXEC|PROT_READ|PROT_WRITE);
    if(res)
    {
        fprintf(stderr, "mprotect error:%d\n", res);
        return 1;
    }
    typedef double (*dfunc)(void);

    dfunc d = (dfunc)(&prog[0]);
    double x = (*d)();
    printf("x=%f\n", x);
    fflush(stdout);
    return 0;
}

在这种情况下,您还可以声明数组为const,以便将其存储在进程内存的可执行部分中:http://stackoverflow.com/q/12446965/1025391 - moooeeeep
3
通常情况下不能假定只读数据的内存段可执行,即使某些糟糕的链接器会这样做。 - CodesInChaos
3
通常情况下,您不能假定可以执行存储在数组中的机器代码,但是这位提问者要求这样做。 - moooeeeep
1
@moooeeeep:在过去的一年左右时间里,GNU ld 开始将 .rodata 链接到它自己的 ELF 段中,以便可以只读而不需要执行权限。不再像以前那样是文本段的一部分了。因此,这个简单的技巧不再起作用。但是,在 GNU C 中,您可以在 const 数组上使用 __attribute__((section(".text"))) - Peter Cordes
你的shellcode使用movsd 0x0(%rip),%xmm0读取了数组末尾之外的内容。这是从第0x5d个字节开始的8字节加载(因为RIP+0,所以是在movsd指令之后)。x86是小端序,所以double的指数字段将来自于接下来的任何垃圾数据。看起来你天真地复制了编译器生成的调试模式代码的objdump输出,该函数返回一个double。它当然会从.rodata中加载该常量,因为x86没有FP立即操作数。但你没有将引用的double放入shellcode中。 - Peter Cordes
一些函数式编程的常数可以在几个指令中生成,例如在如何按需生成向量常数的最佳指令序列?中介绍的那样。或者,您可以将示例转换为返回一个整数,因为mov $123, eax / ret是自包含的。 - Peter Cordes

31

就像其他人已经说的那样,你必须确保prog[]是可执行的,然而正确的方法(除非你正在编写JIT编译器)是将符号放在可执行区域中,可以通过使用链接脚本或者在C代码中指定部分来实现,例如:

const char prog[] __attribute__((section(".text"))) = {...}

30

几乎所有的C编译器都允许您通过在代码中嵌入常规汇编语言来实现此目的。 当然,这是C的非标准扩展,但编译器作者认识到这通常是必要的。 作为非标准扩展,您需要阅读编译器手册并检查如何执行此操作,但GCC "asm" 扩展是一个相当标准的方法。

 void DoCheck(uint32_t dwSomeValue)
 {
    uint32_t dwRes;

    // Assumes dwSomeValue is not zero.
    asm ("bsfl %1,%0"
      : "=r" (dwRes)
      : "r" (dwSomeValue)
      : "cc");

    assert(dwRes > 3);
 }

由于在汇编语言中很容易破坏堆栈,因此编译器通常也允许您标识将用作汇编程序一部分的寄存器。然后,编译器可以确保该函数的其余部分避开这些寄存器。

如果您自己编写汇编代码,则没有设置该汇编程序作为字节数组的好理由。这不仅是一种代码味道 - 我会说这是一个真正的错误,只有当您不知道正确嵌入汇编代码的 "asm" 扩展方式时才会发生。


7
天啊,怎么有五个回答者连asm都没提到这个问题?呕呕呕呕呕。 - Kyle Strand
6
@KyleStrand 或许其他人会区分“机器语言”(用户想要的)和“汇编语言”。例如,如果您想要动态生成代码,“asm”就不是那么有用了。 - pipe
1
同时,并非每个 C 编译器都能使用真正的汇编语言。例如,MSVC 将把 __asm 代码视为另一种高级语言的代码处理:它会尝试对其进行优化,并且不允许您发出原始字节(例如在 MASM 中使用的 db 指令)。 - Ruslan
2
除了他正在使用指令字节码来设置常量数组之外。如果他知道自己想要哪些指令,他所做的只是一种嵌入“asm”块的复杂版本。 - Graham
3
@KyleStrand,我从未提到“const”。我相信那些试图从C语言执行机器语言的人都知道asm()构造函数,并且我很高兴他将代码片段缩减到最小来演示问题。 - pipe
显示剩余4条评论

9
基本上,这是因为它是病毒作者的一个公开邀请而被压制了。但是,您可以使用纯C语言中的本机机器代码分配和缓冲区,并将其设置,这没有问题。问题在于如何调用它。虽然您可以尝试使用缓冲区地址设置函数指针并调用它,但这极不可能起作用,并且如果您设法诱导它做您想要的事情,它很可能会在下一个编译器版本上出现故障。因此,最好的方法是简单地采用一些内联汇编,以设置返回并跳转到自动生成的代码。但是,如果系统防止这样做,您将不得不找到规避保护的方法,就像Rudi在他的答案中所描述的那样(但非常特定于一个特定的系统)。

6

一个明显的错误是,\xc3 没有返回你所声明的 double


真的!(这只是填充) - Adriano Repetti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接