有时候,我们需要hook一个c库的函数,加一些自己的逻辑,然后再调用c库的函数。然后方法就是编译一个共享库,这个共享库实现同名c库函数,使用LD_PRELOAD的方式提前加载这个共享库。在同名函数中,使用
dlsym(RTLD_NEXT, "<func name>");
的方式,获得c库函数的地址,这样就可以调用原函数。
dlsym手册,有专门描述:
RTLD_NEXT
Find the next occurrence of the desired symbol in the search order after the current object.
This allows one to provide a wrapper around a function in another shared object, so that, for
example, the definition of a function in a preloaded shared object (see LD_PRELOAD in ld.so(8))
can find and invoke the "real" function provided in another shared object (or for that matter,
the "next" definition of the function in cases where there are multiple layers of preloading).
例如,我们要hook pthread_kill函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #define _GNU_SOURCE #include <dlfcn.h> #include <setjmp.h> #include <signal.h> #include <pthread.h> static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; static sigjmp_buf jump; static void segv( int sig) { siglongjmp(jump, 1); } int pthread_kill(pthread_t thread , int sig) { static int (*pthread_kill_real)(pthread_t thread , int sig); int ret; if (!pthread_kill_real) { pthread_kill_real = dlsym(RTLD_NEXT, "pthread_kill" ); if (!pthread_kill_real) return -1; } pthread_mutex_lock(&lock); signal (SIGSEGV, segv); if (!sigsetjmp(jump, 1)) ret = pthread_kill_real( thread , sig); else ret = -1; signal (SIGSEGV, SIG_DFL); pthread_mutex_unlock(&lock); return ret; } |
编译出共享库:
$ mipsel-openwrt-linux-gcc --shared -fPIC -o hook_pthread_kill.so hook_pthread_kill.c -ldl -lpthread
运行:
$ LD_PRELOAD=./hook_pthread_kill.so ./testApp
这样testApp就会使用hook_pthread_kill.so中的pthread_kill()。
testApp的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <pthread.h> #include <unistd.h> #include <signal.h> #include <stdint.h> #include <stdio.h> static pthread_t thread ; void *start_routine( void *arg) { return NULL; } int main( int argc, char **argv) { pthread_create(& thread , NULL, start_routine, NULL); pthread_join( thread , NULL); pthread_kill( thread , SIGUSR1); return 0; } |
musl c库,pthread_t存储在线程局部存储区,当线程结束后,访问将导致非法内存访问。
所以pthread_jion之后,再pthread_kill将导致段错误。
$ ./testApp
[10424.534752]
do_page_fault(): sending SIGSEGV to testApp for invalid read access from 77ce7da0
[10424.543320] epc = 77d83a00 in libc.so[77d0c000+92000]
[10424.548418] ra = 77d83a00 in libc.so[77d0c000+92000]
[10424.553477]
Segmentation fault (core dumped)
hook pthread_kill后,可以判断pthread_t是否合法,避免段错误。
$ LD_PRELOAD=./hook_pthread_kill.so ./testApp
这样调用就不会有段错误。
参考:
https://tbrindus.ca/correct-ld-preload-hooking-libc/