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 同时 可写和可执行。如果我在 mmap
或 mprotect
的调用中写入 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.)
有没有办法将处理器指令放入数组,使其内存段可执行并运行它作为一个简单的函数:
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);
}
您可能会看到此代码与
当使用这样的代码时,重要的是要知道许多现代操作系统不允许您有一页 RAM 同时 可写和可执行。如果我在 mmap
或 mprotect
的调用中写入 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.)