如何使用LD_PRELOAD拦截普通符号?
如何用LD_PRELOAD拦截非动态符号?
LD_PRELOAD仅能拦截动态导出的符号,普通符号(未加入全局符号表的内部符号)无法直接通过常规LD_PRELOAD方式拦截,原因是动态链接器加载库时不会暴露这类符号,导致预加载库无法找到对应符号,出现你遇到的undefined symbol错误。以下是几种可行的解决方法:
方法1:修改目标库的符号可见性
通过objcopy将目标普通符号转为动态导出符号,让动态链接器能识别它:
# 先备份原库,避免破坏系统文件 cp /usr/lib64/libc-2.17.so /usr/lib64/libc-2.17.so.bak # 将__opendirat设为全局可见(动态符号) objcopy --add-symbol __opendirat=__opendirat:global /usr/lib64/libc-2.17.so
操作后用nm -D /usr/lib64/libc-2.17.so就能看到__opendirat,此时你的预加载库可以正常拦截该函数。若不想修改系统库,可复制一份libc到本地目录修改,再通过LD_LIBRARY_PATH指定使用修改后的库。
方法2:使用内联钩子(无需修改原库)
直接修改原库中目标函数的机器码,强制跳转到你的自定义实现,步骤如下:
- 用
nm libc-2.17.so找到__opendirat在库中的偏移地址(比如0x123456); - 编写钩子代码(以x86_64架构为例):
#include <stdio.h> #include <sys/mman.h> #include <dlfcn.h> #include <stdint.h> // 保存原函数指针 static int (*original___opendirat)(int, const char*) = NULL; // 自定义拦截实现 int __opendirat(int fd, const char* path) { printf("已拦截__opendirat调用:fd=%d, path=%s\n", fd, path); return original___opendirat(fd, path); } // 程序加载时自动执行钩子初始化 __attribute__((constructor)) void init_hook() { // 获取libc的基地址 void* libc_base = dlopen("/usr/lib64/libc-2.17.so", RTLD_LAZY); // 计算__opendirat的实际内存地址(基地址+偏移) uintptr_t target_addr = (uintptr_t)libc_base + 0x123456; // 替换为nm查到的偏移 original___opendirat = (void*)target_addr; dlclose(libc_base); // 修改内存页权限为可写可执行 uintptr_t page_addr = target_addr & ~(4095); // 对齐到页起始 mprotect((void*)page_addr, 4096, PROT_READ | PROT_WRITE | PROT_EXEC); // 写入x86_64的跳转指令(jmp rel32) uint8_t jmp_code[] = {0xE9, 0x00, 0x00, 0x00, 0x00}; int32_t offset = (uintptr_t)__opendirat - target_addr - 5; // 计算相对偏移 memcpy(&jmp_code[1], &offset, sizeof(offset)); memcpy((void*)target_addr, jmp_code, sizeof(jmp_code)); // 恢复内存页权限为只读可执行 mprotect((void*)page_addr, 4096, PROT_READ | PROT_EXEC); }
- 编译预加载库:
gcc -shared -fPIC hook.cpp -o hook.so -ldl
- 运行目标程序时加载钩子:
LD_PRELOAD=./hook.so ./your_target_program
方法3:重编译目标库(需源码)
如果能获取libc源码,可在__opendirat函数定义处添加__attribute__((visibility("default"))),强制将其设为动态导出符号,重新编译libc后即可用常规LD_PRELOAD拦截。
内容的提问来源于stack exchange,提问作者Berlin
相关产品推荐
相关产品推荐

