59

我正在尝试调用本机机器语言代码。到目前为止,这是我所拥有的(出现总线错误):

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

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

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

它确实正确调用了该函数并进入了 ret 指令。但是当它试图执行 ret 指令时,它有一个 SIGBUS 错误。是因为我在一个没有被清除执行的页面上执行代码或类似的东西吗?

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

4

6 回答 6

53

第一个问题可能是存储 prog 数据的位置不可执行。

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

第二个问题可能是您调用的代码在某种程度上是无效的。在 C 中调用方法有一个特定的过程,称为调用约定(例如,您可能正在使用“cdecl”)。被调用的函数仅仅“ret”可能是不够的。它可能还需要进行一些堆栈清理等,否则程序将出现意外行为。一旦你克服了第一个问题,这可能会成为一个问题。

于 2016-10-05T07:16:25.717 回答
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;
}
于 2016-10-05T08:01:55.320 回答
31

正如大家已经说过的,您必须确保prog[]它是可执行的,但是除非您正在编写 JIT 编译器,否则正确的方法是将符号放在可执行区域中,方法是使用链接描述文件或指定部分如果编译器允许,则为 C 代码,例如:

const char prog[] __attribute__((section(".text"))) = {...}
于 2016-10-05T10:57:55.203 回答
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”扩展名的情况下才会发生,这是将汇编程序嵌入到 C 中的正确方法。

于 2016-10-05T17:17:28.753 回答
9

本质上,这已被取缔,因为它是对病毒作者的公开邀请。但是您可以使用直接 C 语言中的本机机器代码分配和缓冲并设置它——这没问题。问题是调用它。虽然您可以尝试使用缓冲区的地址设置一个函数指针并调用它,但这极不可能工作,并且如果您设法以某种方式设法诱使它执行您想要的操作,则极有可能在编译器的下一个版本中中断. 所以最好的办法是简单地使用一些内联汇编,设置返回并跳转到自动生成的代码。但是,如果系统可以防止这种情况发生,您将不得不找到绕过保护的方法,正如 Rudi 在他的回答中所描述的那样(但对于一个特定的系统来说非常具体)。

于 2016-10-05T10:42:18.113 回答
6

一个明显的错误是\xc3没有返回double您声称它正在返回的。

于 2016-10-05T07:11:34.813 回答