- A technique that allows us to intercept an executable's calls to some shared library's functions.
- Can be used to trace number of times a particular library function is called, as well as to validate and trace the inputs and output values to the function
- Basically creating a wrapper function for some target function to be interposed whose prototype is identical to the target function, then using some interpositioning mechanism we can trick the system to call the wrapper function instead of the target.
- We can perform interpositioning at compile time, link time or run time.
Compile-Time Interpositioning
- We interpose C preprocessor to interpose at compile time, and then at compile time we compile with -I . flag which makes the compiler look for external functions first withing the same file(. means same file, u can use other files too) before looking into the system file.
- Example:
/*int.c*/
#include <stdio.h>
#include "malloc.h"
int main(){
int *p = malloc(32);
free(p);
return 0;
}
/*malloc.h*/
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)
void *mymalloc(size_t size);
void myfree(void *ptr);
/*mymalloc.c*/
#ifdef COMPILETIME
#include <stdio.h>
#include <malloc.h>
void *mymalloc(size_t size){
void *ptr = malloc(size);
printf("malloc(%d) = %p\n", (int)size, ptr);
return ptr;
}
void myfree(void *ptr){
free(ptr);
printf("free(%p)\n", ptr);
}
#endif
- we first convert C library into .o file with -DCOMPILETIME, to enable COMPILETMIE flag
linux> gcc -DCOMPILETIME -c malloc.c
- then we compile our int.c with -I flag to make the C proprocessor look for stuff inside current directory before trying the system directory
linux> gcc -I. -o intc int.c mymalloc.o
Link-Time Interpositioning
- During linking we can use --wrap f flag where f is the function we want to interposition.
- On using the flag, linker resolves references to symbol f as __wrap_f and resolve references to __real_f as f.
- Example:
/*mymalloc.c*/
#ifdef LINKTIME
#include <stdio.h>
void *__real_malloc(size_t size);
void __real_free(void *ptr);
void *__wrap_malloc(size_t size){
void *ptr = __real_malloc(size);
printf("malloc(%d)= %p\n", (int)size, ptr);
return ptr;
}
void __wrap_free(void *ptr)
{
__real_free(ptr);
printf("free(%p)\n", ptr);
}
#endif
- then we link this to our original int.c's object file to this creating the final executable that will call our given malloc wrap over the original system provided stuff
linux> gcc -DLINKTIME -c mymalloc.c
linux> gcc -c int.c
linux> gcc -W1,--wrap,malloc -W1,--wrap,free -o intl int.o myalloc.o
- W1 is passing args to the linker, and comma means space so:
- we are passing --wrap malloc and --wrap free as args to the linker
Run-Time Interpositioning
/*mymalloc.c*/
#ifdef RUNTIME
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
void *malloc(size_t size){
void*(*mallocp)(size_t size);
char *error;
mallocp = dlsym(RTLD_NEXT,"malloc");
if((error = dlerror()) != NULL){
fputs(error, stderror);
exit(1);
}
char *ptr = mallocp(size);
printf("malloc(%d) = %p\n", (int)size, ptr);
}
void free(void *ptr)
{
void (*freep)(void *) = NULL;
char *error;
if (!ptr)
return;
freep = dlsym(RTLD_NEXT, "free"); /* Get address of libc free */
if ((error = dlerror()) != NULL) {
fputs(error, stderr);
exit(1);
}
freep(ptr); /* Call libc free */
printf("free(%p)\n", ptr);
}
#endif
- First we build our shared library
linux> gcc - DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
- make our executable (or we could already have on at hand)
linux> gcc -o intr int.c
- then we run it with LD_PRELOAD
linux> LD_PRELOAD="./mymalloc.so" ./intr
- Some notes regarding mymalloc.c:
- Here, libc.so which contains actual definition of the real malloc is already load time linked, and is called with plt and got table at run time.
- When we give LD_PRELOAD with options during execution, for every execution our dynamic linker will first look into stuff in LD_PRELOAD path before going to the standard library
- BUT in mymalloc.c, inside the void* malloc() function we should call the real malloc for actual memory allocation too, but just calling malloc() will do infinite recursion so we use dlsym to call functions for libc.so dynamically at runtime.
- Normally we would need handler as first argument, but instead we use RTLD_NEXT for "malloc" which skips the first time it find the definition of "malloc" symbol and finally choses the one it finds the second which wouldbe libc.so in our case