ILD

LD_PRELOAD for hooking libc functions
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2023-9-20 站点:Inside Linux Development

有时候,我们需要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 threadint sig)
{
        static int (*pthread_kill_real)(pthread_t threadint 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/


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.