├── .gitignore ├── Makefile ├── README.md ├── inject.c └── testProgram.c /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | testProgram 3 | inject.dylib 4 | inject.o 5 | inject.so 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: main run clean 2 | 3 | PLATFORM := $(shell uname -s) 4 | 5 | build: 6 | ifeq ($(PLATFORM),Linux) 7 | gcc -fpic -c -o inject.o inject.c 8 | gcc -shared -o inject.so inject.o -ldl 9 | gcc -rdynamic testProgram.c -o testProgram 10 | else ifeq ($(PLATFORM),Darwin) 11 | clang -dynamiclib inject.c -o inject.dylib 12 | clang testProgram.c -o testProgram 13 | endif 14 | 15 | run: 16 | ifeq ($(PLATFORM),Linux) 17 | LD_PRELOAD=$(PWD)/inject.so ./testProgram 18 | else ifeq ($(PLATFORM),Darwin) 19 | DYLD_INSERT_LIBRARIES=inject.dylib ./testProgram 20 | endif 21 | 22 | clean: 23 | rm -f testProgram inject.o inject.dylib inject.so 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRuntimeFunctionHooker 2 | This is an example of how C functions can be hooked at runtime, similar to [Cydia Substrate](http://www.cydiasubstrate.com/) and [Substitute](https://github.com/comex/substitute). It allows any function in a pre-compiled binary to be replaced at runtime with an arbitrary function written by the user. This doesn't modify the original binary at all, and doesn't require knowledge of the source code (although you do need to know the target function's signature, i.e. name, arguments, and return type). 3 | 4 | This is mostly an experiment, if you need a function hooking library you should use Cydia Substrate or Substitute as they're much more robust. 5 | 6 | **For a writeup about how this code works check out [this link](http://thomasfinch.me/blog/2015/07/24/Hooking-C-Functions-At-Runtime.html).** 7 | 8 | ## Running the Code 9 | 10 | This code (currently) only works on 64 bit Mac OS X (tested on 10.10). Linux *almost* works, but when I tested it the dynamic library was loaded at a very high memory address out of range of a 32 bit offset from the main binary. 11 | 12 | ``` 13 | $ make 14 | $ ./testProgram 15 | Calling original function! 16 | The number is: 5 17 | $ DYLD_INSERT_LIBRARIES=inject.dylib ./testProgram 18 | Calling replacement function! 19 | The number is: 3 20 | ``` 21 | 22 | The makefile also includes a target for running the code with the dylib injected (`make run`) so you don't have to type it out every time. 23 | -------------------------------------------------------------------------------- /inject.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | //Set this to 0 if you don't want to see log messages 10 | const int PRINT_INFO = 1; 11 | 12 | int hookReplacementFunction() { 13 | printf("Calling replacement function!\n"); 14 | return 3; 15 | } 16 | 17 | //Print the first 16 bytes after the given address 18 | static void printBytes(int64_t *address) { 19 | int i; 20 | for (i = 0; i < 4; i++) { 21 | printf("*(address+%d):\t%08x\n", i * 4, htonl((int)*(address+i))); 22 | } 23 | } 24 | 25 | __attribute__((constructor)) 26 | static void ctor(void) { 27 | if (PRINT_INFO) 28 | printf("Injection dylib constructor called.\n"); 29 | 30 | //"If filename is NULL, then the returned handle is for the main program." 31 | void *mainProgramHandle = dlopen(NULL, RTLD_NOW); 32 | 33 | //Get a pointer to the original function using dlsym 34 | int64_t *origFunc = dlsym(mainProgramHandle , "hookTargetFunction"); 35 | if (PRINT_INFO) 36 | printf("Original function address: 0x%llx\n", (int64_t)origFunc); 37 | 38 | //Get a pointer to the replacement function 39 | int64_t *newFunc = (int64_t*)&hookReplacementFunction; 40 | if (PRINT_INFO) 41 | printf("Replacement function address: 0x%llx\n", (int64_t)newFunc); 42 | 43 | //Calculate the relative offset needed for the jump instruction 44 | //Since relative jumps are calculated from the address of the next instruction, 45 | // 5 bytes must be added to the original address (jump instruction is 5 bytes) 46 | int32_t offset = (int64_t)newFunc - ((int64_t)origFunc + 5 * sizeof(char)); 47 | if (PRINT_INFO) 48 | printf("Offset: 0x%x\n", offset); 49 | 50 | //Make the memory containing the original funcion writable 51 | //Code from http://stackoverflow.com/questions/20381812/mprotect-always-returns-invalid-arguments 52 | size_t pageSize = sysconf(_SC_PAGESIZE); 53 | uintptr_t start = (uintptr_t)origFunc; 54 | uintptr_t end = start + 1; 55 | uintptr_t pageStart = start & -pageSize; 56 | mprotect((void *)pageStart, end - pageStart, PROT_READ | PROT_WRITE | PROT_EXEC); 57 | 58 | if (PRINT_INFO) { 59 | printf("Before replacement: \n"); 60 | printBytes(origFunc); 61 | } 62 | 63 | //Set the first instruction of the original function to be a jump 64 | // to the replacement function. 65 | //E9 is the x86 opcode for an unconditional relative jump 66 | int64_t instruction = 0xe9 | offset << 8; 67 | *origFunc = instruction; 68 | 69 | if (PRINT_INFO) { 70 | printf("After replacement: \n"); 71 | printBytes(origFunc); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /testProgram.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int hookTargetFunction() { 4 | printf("Calling original function!\n"); 5 | return 5; 6 | } 7 | 8 | int main() { 9 | printf("The number is: %d\n", hookTargetFunction()); 10 | return 0; 11 | } 12 | --------------------------------------------------------------------------------