Last active
January 29, 2023 20:12
-
-
Save lpereira/5062388 to your computer and use it in GitHub Desktop.
Partial functions in C This program illustrates a hack to create partial functions in C. The way it works is that it generates a template function (partial_template_function) with known pointers, that is later copied to a region of memory obtained with mmap(), patched up with the address and data to be passed to the real function, and then made …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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; | |
} |
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;
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
#define FUNCTION_CONSTANT 0xABAD1DEA
You have good taste and you should feel good.