-
-
Save lpereira/5062388 to your computer and use it in GitHub Desktop.
| /* | |
| * Partial applied functions in C | |
| * Leandro Pereira <[email protected]> | |
| */ | |
| #include <assert.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <stdint.h> | |
| #include <stdbool.h> | |
| #include <sys/mman.h> | |
| struct Partial { | |
| void *caller; | |
| size_t caller_len; | |
| }; | |
| #define PARAMETER_CONSTANT 0xFEEDBEEF | |
| #define FUNCTION_CONSTANT 0xABAD1DEA | |
| static bool | |
| patch_pointer(void *code_addr, size_t code_len, void *look_for, void *patch_with) | |
| { | |
| unsigned char *code = code_addr; | |
| intptr_t look = (intptr_t)look_for; | |
| do { | |
| if (*((intptr_t *)code) == look) { | |
| union { | |
| unsigned char octet[sizeof(void *)]; | |
| void *ptr; | |
| } patch; | |
| patch.ptr = patch_with; | |
| code[0] = patch.octet[0]; | |
| code[1] = patch.octet[1]; | |
| code[2] = patch.octet[2]; | |
| code[3] = patch.octet[3]; | |
| return true; | |
| } | |
| code++; | |
| } while (code_len--); | |
| return false; | |
| } | |
| /* | |
| * Make sure this function is always declared before partial_new(). | |
| */ | |
| static void | |
| partial_template_function(void) | |
| { | |
| ((void (*)(void *))FUNCTION_CONSTANT)((void *)PARAMETER_CONSTANT); | |
| } | |
| /* | |
| * Make sure no functions get declared here. | |
| */ | |
| struct Partial * | |
| partial_new(void (*func)(void *data), void *data) | |
| { | |
| struct Partial *t; | |
| if (!func) return NULL; | |
| t = calloc(1, sizeof(*t)); | |
| if (!t) return NULL; | |
| t->caller_len = (size_t)((intptr_t)partial_new - (intptr_t)partial_template_function); | |
| assert(t->caller_len > 0); | |
| /* | |
| * Map it as RW, without EXEC bits, and only turn it on after pointers | |
| * have been patched up | |
| */ | |
| t->caller = mmap(0, t->caller_len, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | |
| if (t->caller == MAP_FAILED) goto no_mmap; | |
| /* | |
| * Copy the code the compiler generated to call an arbitrary function with | |
| * an arbitrary parameter | |
| */ | |
| memcpy(t->caller, partial_template_function, t->caller_len); | |
| /* | |
| * Change the arbitrary constants to the ones passed as arguments to this | |
| * function. This thing will only work on 32-bit, little endian | |
| */ | |
| if (!patch_pointer(t->caller, t->caller_len, (void *)FUNCTION_CONSTANT, func)) goto no_patch; | |
| if (!patch_pointer(t->caller, t->caller_len, (void *)PARAMETER_CONSTANT, data)) goto no_patch; | |
| /* Make sure no one can change this later, and that we can execute it */ | |
| mprotect(t->caller, t->caller_len, PROT_EXEC | PROT_READ); | |
| return t; | |
| no_patch: | |
| munmap(t->caller, t->caller_len); | |
| no_mmap: | |
| free(t); | |
| return NULL; | |
| } | |
| void | |
| partial_del(struct Partial *t) | |
| { | |
| if (!t) return; | |
| munmap(t->caller, t->caller_len); | |
| free(t); | |
| } | |
| void | |
| (*partial_to_function(struct Partial *t))() | |
| { | |
| if (!t) return NULL; | |
| return (void (*)(void *))(t)->caller; | |
| } | |
| static void | |
| test(void *data) | |
| { | |
| printf("it worked! received data: %p\n", data); | |
| } | |
| static void | |
| another_test(void *data) | |
| { | |
| printf("another test with different data: %p\n", data); | |
| } | |
| int main(void) | |
| { | |
| struct Partial *t, *t2; | |
| void (*func)(); | |
| void (*func2)(); | |
| t = partial_new(test, (void *)0x12341337); | |
| if (!t) goto end; | |
| t2 = partial_new(another_test, (void *)0xbebac0ca); | |
| if (!t2) goto end; | |
| func = (void *)partial_to_function(t); | |
| if (!func) goto end; | |
| func2 = partial_to_function(t2); | |
| if (!func2) goto end; | |
| /* | |
| * Now pass `func` around as a callback. When it is called, the | |
| * parameter passed to partial_new() will be passed to the original | |
| * function. Magic. | |
| */ | |
| atexit(func); | |
| /* Or call it directly. */ | |
| func2(); | |
| /* | |
| * atexit() callbacks are called in the dtor, so make sure partial_del(t) | |
| * does not run. | |
| */ | |
| partial_del(t2); | |
| end: | |
| return 0; | |
| } |
Nicely done, sir!
how can this possibly be useful?
#define FUNCTION_CONSTANT 0xABAD1DEA
You have good taste and you should feel good.
Hahaha :) Reminds me of the good ol' IA32 asm code, this kind of tricks was just used over and over - because of, no choice.
Well nice piece !
Now you say you wouldn't recommend it (I wouldn't either) but with some more work, I think it's even packageable in a (dangerous) library. Just have to make freaking sure you get the good length for partial_template_functions (why not use labels in the code for that ?) and also be a lil' more generic for IA32/64, big or little endian, and you're good to go !
BUT you could also use a global.
👍
Nice trick.
I don't see the point/need of this cast inside partial_to_function:
return (void (*)(void *))(t)->caller;
if you keep the cast shouldn't it be:
return (void (*)())(t)->caller;
What witchcraft is this? You are playing with dark magic :P