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

C: put x86 instructions into array and execute them

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

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

取决于平台。

对于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 将对其起作用,即它具有直接定义的大小,而不仅仅是从其他地方传递的指针.如果是后者,则必须以其他方式指定大小。

请注意,如果您想知道,这里有两个 "simplifications" 可能,但我建议不要使用它们:

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

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

对于Linux,我在Google上找到了this,但我不太了解

非常依赖 OS:并非所有 OSes 都会故意(阅读:没有错误)允许您在数据段中执行代码。 DOS会因为它在实模式下运行,Linux也可以具有相应的权限。我不知道 Windows。

转换通常是未定义的,并且有其自身的注意事项,因此请在此处详细说明该主题。来自 C11 标准草案 N1570,§J.5.7/1:

A pointer to an object or to void may be cast to a pointer to a function, allowing data to be invoked as a function (6.5.4).

(已添加格式。)

所以,它非常好并且应该按预期工作。当然,您需要遵守 ABI 的调用约定。

在 Unix 上(如今,这意味着 "everything except Windows and some embedded and mainframe stuff you've probably never heard of"),您可以通过使用 mmap, writing the code into them, and then making them executable with 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);
}

您可能会看到此代码与 中显示的 Windows 代码几乎相同。只有系统调用的名称和参数不同。

当使用这样的代码时,重要的是要知道许多现代操作系统不允许您有一页 RAM 同时 可写和可执行。如果我在 mmapmprotect 的调用中写入 PROT_READ|PROT_WRITE|PROT_EXEC,它将失败。这叫做W^X policy; the acronym stands for Write XOR eXecute. It originates with OpenBSD, and the idea is to make it harder for a buffer-overflow exploit to write code into RAM and then execute it. (It's still possible, the exploit just has to find a way to make an appropriate call to mprotect first.)