我会在这里变得很老套,所以抓住你的马裤。
标准 C api 带有两个函数,称为 setjmp 和 longjmp。除了糟糕的命名之外,它们本质上所做的是将当前状态(包括堆栈位置和寄存器值)的副本存储到 jmp_buf(或者,技术名称,continuation)中。
现在,假设您创建了一个函数:
jmp_buf jb;
void sfunc(void) {
void *sp_minus1 = 0xBEEFBABE;
setjmp(jb);
}
当你调用 sfunc 时,会创建一个栈帧。因为这个函数没有参数,堆栈中的第一个条目将是返回地址,紧随其后的是 sp_minus1 对象。
为什么这是相关的?嗯,sp_minus1 的地址是相对于堆栈帧的开始的。如果你能在jb找到栈帧的地址,你可以把它改成……比如说,堆中的某个位置?
我们现在得到的是一种为堆上的 longjmp 函数调用创建堆栈帧的方法,该堆栈帧可以包含有关调用它们的上下文的附加状态;或者换句话说,闭包。
我认为我从未见过有人以这种方式使用 longjmp/setjmp,但如果您正在寻找一种在 C 中动态生成和返回函数的方法,我认为这将是您的最佳途径。
编辑:
这是我所描述的 hack 的示例实现:
#include <inttypes.h> // intptr_t
#include <setjmp.h> // longjmp, setjmp
#include <stdio.h> // printf
#include <stdlib.h> // malloc, free
#include <string.h> // memcpy
typedef struct {
jmp_buf jb;
int fixupc;
int fixupv[10];
size_t stack_size; // this is only an approximation
void *stack_ptr;
} CLOSURE;
int getclosure(CLOSURE *closure) {
unsigned int i, size;
void *i_ptr = &i, *sp;
unsigned char *data = (unsigned char *)(void *)closure->jb;
memset(closure, 0, sizeof(CLOSURE));
if (!setjmp(closure->jb)) {
printf("looking for 0x%08X...\n\n", (unsigned int)(intptr_t)i_ptr);
for (i = 0; i < sizeof(closure->jb); i++) {
memcpy(&sp, &data[i], sizeof(void *));
size = (unsigned int)(intptr_t)(sp - i_ptr);
if (size < 0x300) {
closure->fixupv[closure->fixupc++] = i;
printf(" fixup @ 0x%08X\n", (unsigned int)(intptr_t)sp);
if (sp > closure->stack_ptr) {
closure->stack_size = size;
closure->stack_ptr = sp;
}
}
}
if (!closure->stack_ptr)
return 0;
printf("\nsp @ 0x%08X\n", (unsigned int)(intptr_t)closure->stack_ptr);
printf("# of fixups = %i\n", closure->fixupc);
/*
* once we allocate the new stack on the heap, we'll need to fixup
* any additional stack references and memcpy the current stack.
*
* for the sake of this example, I'm only fixing up the pointer
* to the stack itself.
*
* after that, we would have successfully created a closure...
*/
closure->stack_size = 1024;
sp = malloc(closure->stack_size);
memcpy(sp, closure->stack_ptr, closure->stack_size);
memcpy(&data[closure->fixupv[0]], &sp, sizeof(void *));
closure->stack_ptr = sp;
return 1;
} else {
/*
* to this bit of code right here
*/
printf("holy shit!\n");
return 0;
};
}
void newfunc(CLOSURE *closure) {
longjmp(closure->jb, 1);
}
void superfunc(CLOSURE *closure) {
newfunc(closure);
}
int main(int argc, char *argv[]) {
CLOSURE c;
if (getclosure(&c)) {
printf("\nsuccess!\n");
superfunc(&c);
free(c.stack_ptr);
return 0;
}
return 0;
}
这在技术上是一种堆栈粉碎形式,因此默认情况下,GCC 会生成堆栈金丝雀,从而导致程序中止。如果你用'-fno-stack-protection'编译,它会起作用。