如何调用存储在char数组中的机器代码?

use*_*764 55 c c++ linux native machine-language

我正在尝试调用本机机器语言代码.这是我到目前为止(它得到一个总线错误):

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

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

    dfunc d = (dfunc)(&prog[0]);
    (*d)();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它确实正确地调用了函数并且它到达了ret指令.但是当它试图执行ret指令时,它有一个SIGBUS错误.是因为我在一个未清除执行的页面上执行代码或类似的东西?

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

Hor*_*man 51

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

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

第二个问题可能是您调用的代码在某种程度上是无效的.在C中调用方法有一定的过程,称为调用约定(例如,您可能使用"cdecl").被调用的函数可能不足以"退回".它可能还需要进行一些堆栈清理等,否则程序会出现意外行为.一旦你遇到第一个问题,这可能是一个问题.

  • [本文](http://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html)详细介绍了如何嵌入和调用C中的机器代码.首先将main()转换为char数组. (4认同)

Rud*_*udi 48

您需要调用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;
}
Run Code Online (Sandbox Code Playgroud)

  • @moooeeeep你通常不能假设readonly数据的内存部分是可执行的,即使一些糟糕的连接器就是这样做的. (3认同)
  • @CodesInChaos你通常不能假设你可以执行存储在数组中的机器代码,但OP要求它. (3认同)

Ism*_*eno 30

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

const char prog[] __attribute__((section(".text"))) = {...}
Run Code Online (Sandbox Code Playgroud)


Gra*_*ham 28

实际上,所有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);
 }
Run Code Online (Sandbox Code Playgroud)

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

如果您自己编写汇编代码,则没有充分的理由将汇编程序设置为字节数组.它不只是代码味道 - 我说这是一个真正的错误,只有通过不知道"asm"扩展才会发生,这是将汇编程序嵌入C语言的正确方法.

  • 好主,五个单独的用户怎么回答这个问题,甚至没有提到*`asm`?Bleeeeeeaaaaaaaarrrrrrrgggggghhhhh. (7认同)
  • @KyleStrand也许其他所有人都区分_machine language_(用户想要的)和_assembler_.例如,如果你想在运行中生成代码,那么`asm`就不那么有用了. (5认同)
  • @pipe除了他用指令字节代码设置常量数组.如果他知道他想要什么指令,他所做的只是嵌入"asm"块的复杂版本. (2认同)
  • @KyleStrand我从未提到过const。我很确定有人试图从C执行机器语言,并且知道“ asm()”构造,并且我很高兴他将_example代码snippet_减少到了演示问题的最低限度。 (2认同)

Mal*_*ean 9

基本上这已被限制,因为它是对病毒编写者的公开邀请.但是您可以在直接C中使用本机机器码分配和缓冲并设置它 - 这没问题.问题是呼唤它.虽然您可以尝试使用缓冲区的地址设置一个函数指针并调用它,但这种情况极不可能发挥作用,并且很可能会破坏下一版本的编译器,如果您确实设法哄它执行您想要的操作.因此,最好的办法是简单地使用一些内联汇编,设置返回并跳转到自动生成的代码.但是,如果系统能够防范这种情况,你必须找到绕过保护的方法,正如Rudi在他的回答中所描述的那样(但是对于一个特定的系统非常具体).


MSa*_*ers 5

一个明显的错误是\xc3没有返回double你声称它返回的那个.