I'm gonna get really hacky here, so hold on to your breeches.
The standard C api comes with a 2 functions called setjmp
and longjmp
. Poor naming aside, what they essentially do is store a copy of the current state (including the stack position and register values) into a jmp_buf
(or, the technical name, a continuation
).
Now, lets say you create a function:
jmp_buf jb;
void sfunc(void) {
void *sp_minus1 = 0xBEEFBABE;
setjmp(jb);
}
When you call sfunc, a stack frame will be created. Because there are no arguments to this function, the first entry in the stack will be the return address and immediately after it will be the sp_minus1 object.
Why is this relevant? Well, the address of sp_minus1 is relative to the start of the stack frame. If you can find the address of the stack frame in jb
, you can change it...say, to a location in the heap?
What we've got at this point is a way to create stack frames for longjmp function calls on the heap that can contain additional state about the context in which they were called; or in other words, closures.
I don't think I've ever seen anyone use longjmp/setjmp this way, but if you're looking for a way to dynamically generate and return functions in C, I think this would be your best route.
EDIT:
Here's an example implementation of the hack I'm describing:
#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;
}
This is technically a form of stack smashing, so by default, GCC will generate stack canaries that cause the program to abort. If you compile with '-fno-stack-protection', it'll work.