-
-
Save mfukar/6057175 to your computer and use it in GitHub Desktop.
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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment