如何分配可执行的内存缓冲区

How to alloc a executable memory buffer?

本文关键字:可执行 内存 缓冲区 分配 何分配      更新时间:2023-10-16

我想为我可以在Win32上执行的缓冲区提供一个缓冲区,但是我在Visual Studio中有一个例外,Malloc函数返回一个非可执行的内存区域。我读到有一个nx标志可以禁用...我的目标是将字节码转换为ASM x86,始终牢记性能。

Somemone可以帮助我吗?

您不使用malloc。无论如何,您为什么要在C 程序中?但是,您也不使用new进行可执行的内存。有特定于Windows的VirtualAlloc函数可保留内存,然后将其标记为可执行的VirtualProtect函数,例如PAGE_EXECUTE_READ标志。

完成此操作后,您可以将指针投入到适当的功能指针类型中,然后调用该函数。完成后不要忘记致电VirtualFree

这是一些没有错误处理或其他理智检查的非常基本的示例代码,只是向您展示如何在现代C 中完成(程序打印5):

#include <windows.h>
#include <vector>
#include <iostream>
#include <cstring>
int main()
{
    std::vector<unsigned char> const code =
    {
        0xb8,                   // move the following value to EAX:
        0x05, 0x00, 0x00, 0x00, // 5
        0xc3                    // return what's currently in EAX
    };    
    SYSTEM_INFO system_info;
    GetSystemInfo(&system_info);
    auto const page_size = system_info.dwPageSize;
    // prepare the memory in which the machine code will be put (it's not executable yet):
    auto const buffer = VirtualAlloc(nullptr, page_size, MEM_COMMIT, PAGE_READWRITE);
    // copy the machine code into that memory:
    std::memcpy(buffer, code.data(), code.size());
    // mark the memory as executable:
    DWORD dummy;
    VirtualProtect(buffer, code.size(), PAGE_EXECUTE_READ, &dummy);
    // interpret the beginning of the (now) executable memory as the entry
    // point of a function taking no arguments and returning a 4-byte int:
    auto const function_ptr = reinterpret_cast<std::int32_t(*)()>(buffer);
    // call the function and store the result in a local std::int32_t object:
    auto const result = function_ptr();
    // free the executable memory:
    VirtualFree(buffer, 0, MEM_RELEASE);
    // use your std::int32_t:
    std::cout << result << "n";
}

与普通的C 内存管理相比,这是非常不寻常的,但并非真正的火箭科学。困难的部分是正确的机器代码。请注意,我的示例只是非常基本的X64代码。

扩展上述答案,一个好练习是:

  • VirtualAlloc和Read-Write-Access分配内存。
  • 用您的代码填充该区域
  • VirtualProtect更改该地区的保护,以执行阅读访问
  • 跳到/调用此区域中的入口点

所以看起来像这样:

adr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// write code to the region
ok  = VirtualProtect(adr, size, PAGE_EXECUTE_READ, &oldProtection);
// execute the code in the region

,如VirtualAlloc

的文档中所述

flprotect [in]

要分配页面区域的内存保护。如果要投入页面,则可以指定任何一个内存保护常数。

其中之一是:

page_execute 0x10 启用执行访问页面的访问。试图写信给授权区域的尝试导致违反访问。 此标志不受CreateFileMapping函数的支持。

page_execute_read 0x20 启用执行或仅读取页面的区域。试图写信给授权区域的尝试导致违反访问。 Windows Server 2003和Windows XP:此属性不受CreateFileMapping功能的支持

page_execute_readwrite 0x40 启用执行,仅阅读或读/写/写入页面的访问。 Windows Server 2003和Windows XP:此属性不受CreateFileMapping功能的支持

等等

c版本,基于克里斯蒂安·哈克(Christian Hackl)的答案
我认为VirtualAlloc的CC_11应该是字节中代码的大小,而不是system_info.dwPageSize(如果SizeOf代码大于system_info.dwPageSize,该怎么办?)。
我不知道C足够知道sizeof(code)是否是"正确"获取机器代码大小的方法
这是在C 下编译的,所以我想这不是主题大声笑

#include <Windows.h>
#include <stdio.h>
int main()
{
    // double add(double a, double b) {
    //     return a + b;
    // }
    unsigned char code[] = { //Antonio Cuni - How to write a JIT compiler in 30 minutes: https://www.youtube.com/watch?v=DKns_rH8rrg&t=118s
        0xf2,0x0f,0x58,0xc1, //addsd  %xmm1,%xmm0
        0xc3, //ret
    };
    LPVOID buffer = VirtualAlloc(NULL, sizeof(code), MEM_COMMIT, PAGE_READWRITE);
    memcpy(buffer, code, sizeof(code));
    //protect after write, because protect will prevent writing.
    DWORD oldProtection;
    VirtualProtect(buffer, sizeof(code), PAGE_EXECUTE_READ, &oldProtection);
    double (*function_ptr)(double, double) = (double (*)(double, double))buffer; //is there a cleaner way to write this ?
    // double result = (*function_ptr)(2, 234); //NOT SURE WHY THIS ALSO WORKS
    double result = function_ptr(2, 234);
    VirtualFree(buffer, 0, MEM_RELEASE);
    printf("%fn", result);
}

在编译时,链接器将通过将内存分配为数据部分和代码部分来组织程序的内存足迹。CPU将确保程序计数器(硬CPU寄存器)值保留在代码部分中,否则CPU将为违反内存界限提供硬件例外。通过确保您的程序仅执行有效代码,这提供了一些安全性。Malloc旨在分配数据存储器。您的应用程序具有堆,堆的大小由链接器建立,并标记为数据存储器。因此,在运行时,Malloc只是从您的堆中抓住一些虚拟内存,这将永远是数据。

我希望这可以帮助您更好地了解正在发生的事情,尽管这可能不足以使您能够到达所需的位置。也许您可以为运行时生成的代码预先分配"代码堆"或内存池。您可能需要与链接器大惊小怪来完成此操作,但我不知道任何细节。