Linux下显式加载的libA.so拦截方案及共享库自识别库名问询
解决显式加载共享库的拦截问题
1. 共享库能否确定自身的库名?
完全可以!你可以借助dladdr()函数获取当前函数所在共享库的完整路径。举个实际代码例子:
#include <dlfcn.h> #include <stdio.h> void sample_func_in_libA() { Dl_info info; // 传入当前函数的地址,填充Dl_info结构体 if (dladdr((void*)sample_func_in_libA, &info) != 0) { printf("当前所在库的路径:%s\n", info.dli_fname); } }
dladdr()会把当前函数所属共享库的路径写入info.dli_fname,这个方法在显式加载的库中能稳定生效。
2. 替换libA.so为wrapper库的方案是否可行?
这个方案完全可行,是拦截显式加载共享库的经典思路。具体操作流程是:
- 将原
libA.so重命名为libA-org.so - 编写新的
libA.so,导出原库的所有符号;内部通过dlopen()加载libA-org.so,再用dlsym()获取原函数的地址,在封装函数里调用原逻辑(同时可以插入你的拦截代码)
不过有个小细节要注意:如果原库的符号数量很多,手动写每个函数的封装会非常繁琐。你可以用脚本(比如Python或Shell)解析原库的符号表(通过nm -D libA-org.so命令),自动生成封装代码,能大幅节省工作量。
3. 更优/更简单的实现方案?
推荐一个更优雅的方案:利用Linux的LD_AUDIT机制。这是动态链接器提供的审计接口,可以拦截所有动态链接相关操作(包括dlopen()、dlsym()等),不需要修改或移动原库,侵入性极低。
LD_AUDIT的使用步骤
- 编写审计库,实现
la_objopen函数(该函数会在dlopen打开共享库前被调用):
#define _GNU_SOURCE #include <link.h> #include <stdio.h> #include <string.h> // 必须实现的版本声明函数 unsigned int la_version(unsigned int version) { return LAV_CURRENT; } // 拦截dlopen操作的核心函数 struct la_objinfo* la_objopen(struct link_map* map, Lmid_t lmid, uintptr_t* cookie) { // 检测是否是目标库libA.so if (strstr(map->l_name, "libA.so") != NULL) { printf("拦截到dlopen请求:%s\n", map->l_name); // 这里可以修改map->l_name为你自定义的库路径,实现替换加载 // strcpy(map->l_name, "/path/to/your/custom_libA.so"); } return NULL; }
- 编译审计库:
gcc -shared -fPIC -o libaudit.so audit.c -ldl - 运行目标程序时注入审计库:
LD_AUDIT=./libaudit.so ./your_target_program
这个方案的优势很明显:
- 无需改动原库,对系统和目标程序的影响极小
- 可以通用拦截所有
dlopen操作,不止针对libA.so - 不需要手动封装大量符号,逻辑更简洁易维护
另外,如果你想做主动式的通用加载框架,LD_AUDIT本身就是绝佳的基础,你可以在审计库中扩展更多规则,比如根据配置自动替换特定共享库,或者在加载前后注入自定义逻辑。
对比之下,ptrace虽然也能实现拦截,但需要跟踪进程的系统调用,复杂度高且容易出现兼容性问题,LD_AUDIT是更轻量、更可靠的选择。
内容的提问来源于stack exchange,提问作者Frank Bergemann




