You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

关键修改点

  1. 新增is_target_fd函数:通过readlink读取/proc/self/fd/<fd>的真实路径,判断是否属于/proc/net下的TCP/UDP相关文件
  2. 替换原有的硬编码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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 13:45:21