LD_PRELOAD重写read函数无法捕获fd=3/4读取内容的问题
问题
我希望通过LD_PRELOAD重写netstat -tunap的read函数,经strace观察到该命令会读取/proc/net/tcp(对应文件描述符3),但当前重写的read函数仅能捕获文件描述符5的读取缓冲区内容,无法捕获fd=3、4的内容。
strace 相关片段
openat(AT_FDCWD, "/proc/net/tcp", O_RDONLY) = 3 read(3, " sl local_address rem_address "..., 4096) = 300 write(1, "tcp 0 0 192.168.32.1"..., 101tcp 0 0 192.168.32.128:59904 192.168.32.129:9001 ESTABLISHED 17186/python ) = 101 read(3, "", 4096) = 0 close(3)
目标捕获的read调用
read(3, " sl local_address rem_address "..., 4096) = 300
当前仅能捕获的read调用
read(5, "unconfined\n", 4095) = 11
重写的代码
#define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> #include <unistd.h> static ssize_t (*read_original)(int fd, void *buf, size_t count) = NULL; ssize_t read(int fd, void *buf, size_t count) { ssize_t result; // Initialize the original read function if it hasn't been already if (!read_original) { read_original = dlsym(RTLD_NEXT, "read"); if (!read_original) { fprintf(stderr, "Error: Unable to load the original read function\n"); return -1; } } // Call the original read function result = read_original(fd, buf, count); // Print the buffer's contents for non-zero reads if (result > 0 && (fd == 3 || fd == 4 || fd == 5)) { printf("Read from fd %d, %zd bytes: ", fd, result); for (ssize_t i = 0; i < result; ++i) { printf("%02x ", ((unsigned char *)buf)[i]); } printf("\n"); } return result; }
编译命令
gcc -shared -fPIC -o hider.so hide_read4.c -ldl
运行命令
LD_PRELOAD=/path/to/lib/hider.so netstat -tunap
当前输出
Read from fd 5, 11 bytes: 75 6e 63 6f 6e 66 69 6e 65 64 0a Read from fd 5, 11 bytes: 75 6e 63 6f 6e 66 69 6e 65 64 0a Read from fd 5, 11 bytes: 75 6e 63 6f 6e 66 69 6e 65 64 0a (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 ip1:59904 ip2:9001 ESTABLISHED 17186/python udp 0 0 ip1:68 ip3:67 ESTABLISHED -
解决方案
问题根源
硬编码要监控的文件描述符(3、4、5)是错误的,文件描述符是动态分配的,每次运行程序时,/proc/net/tcp、/proc/net/udp等文件对应的fd可能不同。比如strace中显示的fd=3,在实际LD_PRELOAD运行环境中可能被其他文件占用,导致判断条件失效。
修改后的代码
替换硬编码的fd判断逻辑,改为通过/proc/self/fd/<fd>符号链接获取文件真实路径,判断是否为目标文件:
#define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> #include <unistd.h> #include <string.h> #include <limits.h> static ssize_t (*read_original)(int fd, void *buf, size_t count) = NULL; // 判断fd对应的文件是否是/proc/net下的目标文件 int is_target_fd(int fd) { char path[PATH_MAX]; char real_path[PATH_MAX]; snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); if (readlink(path, real_path, sizeof(real_path) - 1) == -1) { return 0; } real_path[sizeof(real_path)-1] = '\0'; // 匹配需要监控的/proc/net下的文件 return strstr(real_path, "/proc/net/tcp") != NULL || strstr(real_path, "/proc/net/udp") != NULL || strstr(real_path, "/proc/net/tcp6") != NULL || strstr(real_path, "/proc/net/udp6") != NULL; } ssize_t read(int fd, void *buf, size_t count) { ssize_t result; if (!read_original) { read_original = dlsym(RTLD_NEXT, "read"); if (!read_original) { fprintf(stderr, "Error: Unable to load the original read function\n"); return -1; } } result = read_original(fd, buf, count); // 仅当读取目标文件且读取字节数大于0时输出 if (result > 0 && is_target_fd(fd)) { printf("Read from fd %d, %zd bytes: ", fd, result); for (ssize_t i = 0; i < result; ++i) { printf("%02x ", ((unsigned char *)buf)[i]); } printf("\n"); } return result; }
关键修改点
- 新增
is_target_fd函数:通过readlink读取/proc/self/fd/<fd>的真实路径,判断是否属于/proc/net下的TCP/UDP相关文件 - 替换原有的硬编码fd判断逻辑,改为调用
is_target_fd函数,确保无论fd是多少,只要是目标文件就能被捕获
编译与运行
保持原有的编译和运行命令不变:
gcc -shared -fPIC -o hider.so hide_read4.c -ldl
LD_PRELOAD=/path/to/lib/hider.so netstat -tunap
额外说明
- 如果需要监控更多
/proc/net下的文件(比如/proc/net/raw),只需在is_target_fd的判断条件中添加对应的路径匹配 - 代码中对
readlink的返回值做了处理,避免缓冲区溢出
内容的提问来源于stack exchange,提问作者Andreas Frangos
相关产品推荐
相关产品推荐

