C:将x86指令放入数组并执行它们[重复]

bwitn5fc  于 2023-04-29  发布在  其他
关注(0)|答案(3)|浏览(98)

此问题已在此处有答案

How to write self-modifying code in x86 assembly(7个回答)
6年前关闭。
有没有办法把处理器指令放入数组,使其内存段可执行,并作为一个简单的函数运行:

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

eh57zj3b1#

在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,同时可写和可执行。如果我在调用mmapmprotect时写入PROT_READ|PROT_WRITE|PROT_EXEC,则会失败。这被称为W^X policy;首字母缩略词代表写入异或执行。它是originates with OpenBSD,其想法是使缓冲区溢出漏洞更难将代码写入RAM并执行。(这仍然是可能的,漏洞只需要find a way to make an appropriate call to mprotect first。)

093gszye

093gszye2#

这取决于平台。
对于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.您可以使用PAGE_EXECUTE_READWRITE调用VirtualAlloc,但这通常是不好的做法,因为它会为不必要的代码执行打开攻击向量。
1.你可以直接在&myarr上调用VirtualProtect,但这只会使内存中的一个随机页面可执行,而这个页面恰好包含你的数组可执行,这比#1更糟糕,因为这个页面中可能还有其他数据,现在突然也可执行了。
对于Linux,我在Google上找到了this,但我对它了解不多。

qkf9rpyu

qkf9rpyu3#

非常依赖操作系统:不是所有的操作系统都会故意(阅读:没有bug)允许您执行数据段中的代码。DOS会因为它运行在真实的模式下,Linux也可以拥有相应的特权.我不知道Windows。
铸造往往是未定义的,并有自己的警告,所以一些详细的主题在这里。根据C11标准草案N1570,§J.5.7/1:
指向对象或void的指针可以转换为指向函数的指针,允许数据作为函数调用(6.5.4)。
(格式已添加。)
所以,它非常好,应该像预期的那样工作。当然,你需要遵守ABI的电话会议。

相关问题