在 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 页。如果我在对mmap 或mprotect 的调用中写了PROT_READ|PROT_WRITE|PROT_EXEC,它将失败。这称为W^X policy;首字母缩写词代表 Write XOR eXecute。它originates with OpenBSD,其想法是让缓冲区溢出漏洞更难将代码写入 RAM 然后执行它。 (这仍然可能,漏洞利用只需要find a way to make an appropriate call to mprotect first。)