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

无需LD_PRELOAD覆写C语言open系统调用的可行性及覆写后无open/open64输出的修复问询

解答你的两个问题

1. 不用LD_PRELOAD也能覆写open系统调用吗?

当然可以!LD_PRELOAD只是用户空间动态劫持的常用方式,还有不少其他方案可选:

  • 静态编译替换:如果目标程序是静态链接的,你可以在编译时直接用自定义的open实现覆盖标准库版本,或者替换静态库中的对应符号。
  • 内核层拦截:比如用kprobes、ftrace这类内核工具,或者编写内核模块(LKM),直接在内核层面hook系统调用入口,适合全局监控场景。
  • 动态插桩框架:像Frida这种工具,能在运行时注入代码替换函数实现,不需要修改程序本身或依赖环境变量。
  • ELF文件修改:通过编辑目标程序的ELF重定位表,把open的调用指向你自己的函数,不过这需要你对ELF格式有一定了解。

2. 修复你的dlhook代码无日志输出的问题

你的代码有几个关键问题,导致没打出预期的open:/open64:日志,我来帮你梳理并修复:

问题出在哪?

  1. 函数查找时机不对:你在每个hook函数内部调用dlsym,不仅效率低,还会引发递归问题——比如printf内部可能调用open,导致反复进入你的hook函数,甚至让dlsym无法正确拿到原始函数地址。
  2. 运行方式错误:你把代码编译成了可执行文件直接运行,此时程序自己定义的open会覆盖标准库的实现,RTLD_NEXT找不到“下一个”符号(因为当前程序已经是第一个定义open的了),自然没法调用原始的open,日志也打不出来。
  3. 没处理dlsym错误:如果dlsym查找失败,你的代码会直接调用空指针,可能导致崩溃,而你完全没做错误检查。
  4. fopen实际调用的是openat:现代glibc里fopen内部是调用openat而非open/open64,但你的openat函数也存在同样的问题。

修复后的代码

我把原始函数的查找移到了程序启动时(用__attribute__((constructor))标记的函数会在main之前执行),同时改成编译成共享库的形式,还加上了错误检查:

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <stdarg.h>

// 全局保存原始函数指针,只查找一次
typedef int (*orig_open_func_type)(const char *__file, int flags, ...);
typedef int (*orig_openat_func_type)(int dirfd, const char *__file, int flags, ...);

static orig_open_func_type orig_open;
static orig_open_func_type orig_open64;
static orig_openat_func_type orig_openat;

// 构造函数:在main执行前初始化原始函数指针
__attribute__((constructor))
void init_hooks() {
    orig_open = (orig_open_func_type)dlsym(RTLD_NEXT, "open");
    if (!orig_open) {
        fprintf(stderr, "Failed to get original open: %s\n", dlerror());
    }

    orig_open64 = (orig_open_func_type)dlsym(RTLD_NEXT, "open64");
    if (!orig_open64) {
        fprintf(stderr, "Failed to get original open64: %s\n", dlerror());
    }

    orig_openat = (orig_openat_func_type)dlsym(RTLD_NEXT, "openat");
    if (!orig_openat) {
        fprintf(stderr, "Failed to get original openat: %s\n", dlerror());
    }
}

int open(const char *__file, int __oflag, ...) {
    if (!orig_open) return -1;

    int res;
    if (__oflag & O_CREAT) {
        va_list ap;
        va_start(ap, __oflag);
        unsigned mode = va_arg(ap, unsigned);
        res = orig_open(__file, __oflag, mode);
        va_end(ap);
    } else {
        res = orig_open(__file, __oflag);
    }
    printf("open: %d (%s)\n", res, __file);
    return res;
}

int open64(const char *__file, int __oflag, ...) {
    if (!orig_open64) return -1;

    int res;
    if (__oflag & O_CREAT) {
        va_list ap;
        va_start(ap, __oflag);
        unsigned mode = va_arg(ap, unsigned);
        res = orig_open64(__file, __oflag, mode);
        va_end(ap);
    } else {
        res = orig_open64(__file, __oflag);
    }
    printf("open64: %d (%s)\n", res, __file);
    return res;
}

int openat(int dirfd, const char *__file, int __oflag, ...) {
    if (!orig_openat) return -1;

    int res;
    if (__oflag & O_CREAT) {
        va_list ap;
        va_start(ap, __oflag);
        unsigned mode = va_arg(ap, unsigned);
        res = orig_openat(dirfd, __file, __oflag, mode);
        va_end(ap);
    } else {
        res = orig_openat(dirfd, __file, __oflag);
    }
    printf("openat: %d (%s)\n", res, __file);
    return res;
}

正确的使用步骤

  1. 把代码编译成共享库:
gcc -shared -fPIC -o emload.so emload.c -ldl
  1. 用LD_PRELOAD加载这个库,监控任意调用open的程序,比如查看你自己的代码文件:
LD_PRELOAD=./emload.so cat emload.c

这时你就能看到所有open/open64/openat的调用日志了。

为什么之前直接运行不行?因为当你把hook代码编译成可执行文件时,程序自己定义的open函数会被优先使用,动态链接器不会去加载标准库的open,RTLD_NEXT找不到后续的符号,所以原始函数指针是空的,自然不会有日志输出。


内容的提问来源于stack exchange,提问作者Vadim Kantorov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 18:29:12