是否可以编写执行以下操作的 C 函数?
- 在堆中分配一堆内存
- 在其中写入机器代码
- 执行那些机器指令
当然,我必须将堆栈的状态恢复到手动执行这些机器指令之前的状态,但我想知道这是否可行。
是否可以编写执行以下操作的 C 函数?
当然,我必须将堆栈的状态恢复到手动执行这些机器指令之前的状态,但我想知道这是否可行。
这当然是可能的。出于各种原因,我们在过去 30 到 40 年里付出了很多努力,试图让它变得尽可能困难,但这是可能的。在现在的大多数系统中,都有试图保护数据空间不被执行的硬件和软件机制。
不过,基础非常简单:您构建一段代码,然后手动或通过编译器4 组装它。然后你需要一段代码空间,所以你将代码插入到你的程序中
unsigned int prgm[] = { 0x0F, 0xAB, 0x9A ... }; // Random numbers, just as an example
因为你想使用堆,所以你需要 malloc 空间
void * myspace ;
if((myspace= malloc(sizeof(prgm))) != NULL) {
memcpy(myspace, pgrm, sizeof(pgrm));
} else { // allocation error
}
现在,您需要一种让程序计数器指向该数据块的方法,该数据块也是您的代码块。这就是你需要一点技巧的地方。设置程序计数器没什么大不了的;这只是底层机器的 JUMP 指令。但是怎么做呢?
最简单的方法之一是故意弄乱堆栈。再次从概念上讲,堆栈看起来像这样(细节取决于您的操作系统和编译器对,以及您的硬件):
| 子程序返回地址 | | 参数... | | 自动变量 |
这里的基本技巧是偷偷地将你的代码地址放入返回地址;当一个例程返回时,它基本上会跳转到那个返回地址。如果您可以伪造它,PC 将被设置到您喜欢的位置。
所以,你需要的是一个例程,我们称之为“goThere()”
void goThere(void * addr){
int a ; // observe above; this is the first space
// on the stack following the parameters
int * pa; // so we use it's address
pa = (&a - (sizeof(int)+(2*sizeof(void*))) ; // so use the address
// but back up by the size of an int, the pointer on the
// stack, and the return address
// Now 'pa' points to the routine's return add on the stack.
*pa = addr; // sneak the address of the new code into return addr
return ; // and return, tricking it into "returning"
// to the address of your special code block
}
它会起作用吗?好吧,也许,取决于硬件和操作系统。大多数现代操作系统将保护堆(通过内存映射或类似方法)免受 PC 移动到其中。出于安全目的,这很有用,因为我们也不会让您完全控制。
这与这个问题非常相似:)
从 vc++读取存储在堆中的调用代码。在 posix 上,mprotect
似乎是合适的(查看man mprotect
):
char *mem = malloc(sizeof(code));
mprotect(mem, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC);
memcpy(mem, code, sizeof(code));
// now arrange some code to jump to mem. But read the notes here on casting
// from void* to a function pointer:
// http://www.opengroup.org/onlinepubs/009695399/functions/dlsym.html
但是,它说:
PROT_EXEC 是否具有与 PROT_READ 不同的效果取决于体系结构和内核版本。在某些硬件架构(例如 i386)上,PROT_WRITE 意味着 PROT_READ。
所以更好,首先检查您的操作系统是否有效。
RE:手动恢复堆栈
如果您在生成的机器代码中遵循平台/编译器使用的调用约定,那么您不必进行任何手动堆栈恢复。当您这样做时,编译器会为您执行此操作
*pfunc(参数)
它应该添加任何必要的适当的调用前或调用后堆栈操作步骤。
但是,请确保您在生成的代码中遵循正确的约定。