Linux 库打桩机制

库打桩机制

Linux 链接器截获对共享库函数调用,转而执行自己的代码。

创建一个包装函数,对库函数进行包装(代理模式)。利用打桩机制欺骗系统去调用包装函数。

编译时打桩

// malloc.c 
// 对 malloc、free 的包装函数

#ifdef COMPILETIME
#include <stdio.h>
#include <malloc.h>
void *my_malloc(size_t size) {
    void *ptr = malloc(size);
    printf("malloc(%d) = %p\n", (int) size, ptr);
    return ptr;
}
void my_free(void *ptr) {
    free(ptr);
    printf("free(%p)\n", ptr);
}
#endif
// malloc.h
// 包装函数声明,并且利用宏替换目标函数和包装函数

#define malloc(size) my_malloc(size)
#define free(ptr) my_free(ptr)

void *my_malloc(size_t);
void my_free(void *);
// main.h
// 测试代码

#include <stdio.h>
#include <malloc.h>

int main() {
    int *p = malloc(32);
    free(p);
    return 0;
}
$ gcc -c -DCOMPILETIME malloc.c # 编译模块
$ gcc -I. -o main main.c malloc.o # 进行链接,-I. 先从当前目录开始找头文件
$ ./main 
# 执行结果如下
malloc(32) = 0xf5e2a0
free(0xf5e2a0)

在链接时引入的是自己的头文件,所以会执行宏替换,转而执行自己的包装函数。

链接时打桩

// malloc.c 
// 对 malloc、free 的包装函数

#ifdef LINKTIME
#include <stdio.h>

void *__real_malloc(size_t);
void __real_free(void *);

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
$ gcc -DLINKTIME malloc.c -c
$ gcc -c main.c
# -W1 对链接器传递参数,`,` 会被转义为空格
$ gcc -Wl,--wrap,malloc -Wl,--wrap,free -o main main.o malloc.o
$ ./main
# 执行结果如下
malloc(32) = 0x22342a0
free(0x22342a0)

--wrap f 告诉链接器把 f 解析成 __wrap_f,然后把 __real_f 解析成原本的 f

换句话说,就是原来的 f 就是 __real_f,而别人调用的 f 就成了包装后的 __wrap_f

所以 main 中调用的 malloc free 就成了包装后的函数。

运行时打桩

#ifdef RUNTIME
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

void *malloc(size_t size) {
    // printf 函数会调 malloc,所以要判断一下,防止无限递归爆栈
    static __thread int cnt = 0;
    cnt++;

    void *(*mallocp)(size_t) = dlsym(RTLD_NEXT, "malloc");
    void *ptr = mallocp(size);
    
    if (cnt == 1)
        printf("malloc(%d) = %p\n", (int) size, ptr);
    
    return ptr;
}
void free(void *ptr) {
    void *(*freep)(void *) = dlsym(RTLD_NEXT, "free");
    freep(ptr);
    printf("free(%p)\n", ptr);
}
#endif
#endif
$ gcc -DRUNTIME -shared -fpic -o malloc.so malloc.c -ldl
$ gcc -o main main.c
$ ./main
# 没有输出
$ LD_PRLOAD="./malloc.so" # 将路径加入环境变量下
$ ./main
# 执行结果如下
malloc(32) = 0x8b12a0
free(0x8b12a0)

动态链接器会先搜索环境变量 LD_PRELOAD 下的库,在搜索其他库。所以在 LD_PRELOAD 中放入自己的动态库,就可以实现运行时打桩。

总结

编译时打桩,需要访问源码,属于硬编码。

链接时打桩,虽然不用硬编码,但是必须要用到可重定位文件,再将其链接。也不够方便。

运行时打桩,只需要在 LD_PRELOAD 环境变量下加入自己的动态链接库地址。及其方便,也容易对别人的设备搞破坏。

上一篇:c内存操作函数干到发抖的干货


下一篇:C day08