C:将x86指令放入数组并执行它们

时间:2016-05-09 17:45:52

标签: c memory x86 execute

有没有办法将处理器指令放入数组,使其内存段可执行并将其作为一个简单的函数运行:

int main()
{
    char myarr[13] = {0x90, 0xc3};
    (void (*)()) myfunc = (void (*)()) myarr;
    myfunc();
    return 0;
}

3 个答案:

答案 0 :(得分:7)

在Unix上(这些日子,这意味着“除了Windows之外的一切以及你可能从未听说过的一些嵌入式和大型机的东西”)你通过用mmap分配一定数量的页面来做到这一点,编写代码进入它们,然后用mprotect使它们可执行。

void execute_generated_machine_code(const uint8_t *code, size_t codelen)
{
    // in order to manipulate memory protection, we must work with
    // whole pages allocated directly from the operating system.
    static size_t pagesize;
    if (!pagesize) {
        pagesize = sysconf(_SC_PAGESIZE);
        if (pagesize == (size_t)-1) fatal_perror("getpagesize");
    }

    // allocate at least enough space for the code + 1 byte
    // (so that there will be at least one INT3 - see below),
    // rounded up to a multiple of the system page size.
    size_t rounded_codesize = ((codelen + 1 + pagesize - 1)
                               / pagesize) * pagesize;

    void *executable_area = mmap(0, rounded_codesize,
                                 PROT_READ|PROT_WRITE,
                                 MAP_PRIVATE|MAP_ANONYMOUS,
                                 -1, 0);
    if (!executable_area) fatal_perror("mmap");

    // at this point, executable_area points to memory that is writable but
    // *not* executable.  load the code into it.
    memcpy(executable_area, code, codelen);

    // fill the space at the end with INT3 instructions, to guarantee
    // a prompt crash if the generated code runs off the end.
    // must change this if generating code for non-x86.
    memset(executable_area + codelen, 0xCC, rounded_codesize - codelen);

    // make executable_area actually executable (and unwritable)
    if (mprotect(executable_area, rounded_codesize, PROT_READ|PROT_EXEC))
        fatal_perror("mprotect");

    // now we can call it. passing arguments / receiving return values
    // is left as an exercise (consult libffi source code for clues).
    ((void (*)(void)) executable_area)();

    munmap(executable_area, rounded_codesize);
}

您可能会发现此代码与cherrydt's answer中显示的Windows代码几乎相同。只有系统调用的名称和参数不同。

使用这样的代码时,重要的是要知道许多现代操作系统不允许您拥有同时可写和可执行的RAM页面。如果我在调用PROT_READ|PROT_WRITE|PROT_EXECmmap时写了mprotect,那么它就会失败。这称为W^X policy;首字母缩写词代表Write XOR eXecute。它originates with OpenBSD,其目的是让缓冲区溢出漏洞更难以将代码写入RAM然后执行它。 (它仍然是可能的,利用只需要find a way to make an appropriate call to mprotect first。)

答案 1 :(得分:5)

取决于平台。

对于Windows,您可以使用以下代码:

// Allocate some memory as readable+writable
// TODO: Check return value for error
LPVOID memPtr = VirtualAlloc(NULL, sizeof(myarr), MEM_COMMIT, PAGE_READWRITE);

// Copy data
memcpy(memPtr, myarr, sizeof(myarr);

// Change memory protection to readable+executable
// Again, TODO: Error checking
DWORD oldProtection; // Not used but required for the function
VirtualProtect(memPtr, sizeof(myarr), PAGE_EXECUTE_READ, &oldProtection);    

// Assign and call the function
(void (*)()) myfunc = (void (*)()) memPtr;
myfunc();

// Free the memory
VirtualFree(memPtr, 0, MEM_RELEASE);

此代码假设myarr数组与问题代码一样,它假定sizeof将对其起作用,即它具有直接定义的大小,而不仅仅是从其他地方传递的指针。如果是后者,则必须以另一种方式指定大小。

请注意,这里有两个“简化”可能,如果你想知道,但我会建议反对他们:

1)您可以使用VirtualAlloc调用PAGE_EXECUTE_READWRITE,但这通常是不好的做法,因为它会打开一个攻击向量,用于执行不需要的代码。

2)您可以直接在VirtualProtect上调用&myarr,但这只会在您的内存中生成一个随机页面,其中包含您的数组可执行文件,这比#1更糟糕,因为可能会此页面中的其他数据也可以突然执行。

对于Linux,我在Google上找到this,但我对此并不了解。

答案 2 :(得分:1)

非常依赖操作系统:并非所有操作系统都会故意(读取:没有错误)允许您在数据段中执行代码。 DOS会因为它在Real Mode下运行,所以Linux也可以拥有相应的权限。我不了解Windows。

投射通常是未定义的,并且有自己的注意事项,所以在这里对该主题进行一些阐述。从C11标准草案N1570,§J.5.7/ 1:

  

指向对象或void的指针可能   被强制转换为指向函数的指针,允许将数据作为一个调用   功能(6.5.4)。

(已添加格式。)

所以,它非常好,应该按预期工作。当然,你需要坚持ABI的召唤惯例。