2016-12-02 86 views
3

我想分配一个缓冲区,我可以在Win32上执行,但我有一个在Visual Studio中的异常cuz malloc函数返回一个不可执行的内存区域。我读到有一个NX标志禁用...我的目标是将字节码转换为asm x86,并牢记性能。如何分配一个可执行的内存缓冲区?

有人可以帮助我吗?

JS

+1

你可能想看看[asmJit](https://github.com/asmjit/asmjit)这是一个免费代码,它为编写JIT的程序集/内存管理部分完成所有繁重的工作,包括设置内存保护位,以便您可以执行您的代码。 – BeeOnRope

回答

7

对此,您不使用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代码。

+1

在C++程序中使用malloc的原因是从堆中分配动态内存。非常好,很正常。 –

+0

@KyleSweet:在C++中,您可以使用'new'(或者像'std :: allocator'这样的“普通”或者新的位置)来从免费商店(而不是堆)动态分配。你可以在技术上使用'malloc',但是你有效地写C而不是(惯用的)C++。对于OP的问题,'VirtualAlloc'和'malloc'之间的区别也很重要。 –

+1

我只是回答你提出的问题。如果你想要的只是堆中的一些快速而脏的内存,不要害怕使用malloc! –

4

documentationVirtualAlloc

flProtect [IN]

将要分配给的页区域内的存储器保护说明。如果页面正在提交,您可以指定任何一个内存保护常量。

其中之一是:

PAGE_EXECUTE 0x10的 允许执行访问的页面提交区域。尝试写入已提交的区域会导致访问冲突。 CreateFileMapping函数不支持此标志。

PAGE_EXECUTE_READ 0x20 启用对页面的已提交区域的执行或只读访问。尝试写入已提交的区域会导致访问冲突。 Windows Server 2003和Windows XP:在使用SP2的Windows XP和使用SP1的Windows Server 2003之前,CreateFileMapping函数不支持此属性。

PAGE_EXECUTE_READWRITE 0x40 允许执行,只读或读/写访问页面的承诺区域。 Windows Server 2003和Windows XP:在使用SP2的Windows XP和使用SP1的Windows Server 2003之前,CreateFileMapping函数不支持此属性。

等从here

+0

它看起来像属于*页*的内存。这在这里适合吗? –

+0

是的。如果您需要控制模式,则需要分配_pages_。 – bmargulies

-2

在编译时,链接器将分配的内存为数据段和代码段组织你的程序的内存占用。 CPU将确保程序计数器(硬件CPU寄存器)的值保持在代码段内,否则CPU将因违反内存边界而引发硬件异常。这通过确保您的程序仅执行有效代码来提供一些安全性。 Malloc用于分配数据存储器。您的应用程序有一个堆,堆的大小由链接器确定并标记为数据存储器。所以在运行时,malloc只是从堆中获取一些永远是数据的虚拟内存。

我希望这可以帮助你更好地理解正在发生的事情,尽管它可能不足以让你到达需要的地方。也许你可以为运行时生成的代码预先分配一个“代码堆”或内存池。你可能需要与链接器做文章来完成这个,但我不知道任何细节。

+0

*动态分配内存页面上的读/写/执行权限与链接器无关。如果你想要一些*静态分配的*写入+执行页面,你可以通过链接器脚本或者GNU C'__attribute__'来实现。 –

+1

另外,不,通过检查程序计数器(x86-64上的RIP)是否保持在特定范围内,内存保护不起作用。这就是分段如何工作(使用基准/限制),但x86-64甚至不支持64位模式下的分段限制。内存保护以页为单位,页表中的位(由OS设置)。有关图表,请参阅https://stackoverflow.com/questions/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the-。 –

3

扩展上面的答案,一个好的做法是:

  • 分配内存VirtualAlloc和读写访问。
  • 填补该区域与您的代码
  • 改变该地区与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 
+1

根据规格如果最后一个参数为NULL,VirtualProtect将失败。使用指向(虚拟)输出变量的指针是强制性的。 – user3042599

+0

@ user3042599:Thx。修复。 – zx485