能否将进程系统调用重定向至其他动态库?Ubuntu下能否劫持读写调用转存MySQL?
当然可以!在Ubuntu这类Linux系统下,LD_PRELOAD 就是实现这种系统调用劫持的绝佳工具,能帮你把应用A的read/write操作无缝转接到MySQL数据库。下面我一步步给你讲具体怎么做:
核心思路:用LD_PRELOAD劫持动态链接函数
Linux的动态链接器会优先加载LD_PRELOAD环境变量指定的动态库,这意味着我们可以编写自己的read和write函数,覆盖系统默认的实现,在函数里把原本的文件读写逻辑替换成MySQL操作。
具体实现步骤
1. 编写自定义劫持库
我们需要写一个C语言的动态库,里面实现自己的read和write函数,同时要保留调用原系统函数的能力(用于处理非目标文件的读写操作)。示例代码如下:
#define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> #include <mysql/mysql.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> // 保存原系统函数的指针 ssize_t (*original_read)(int fd, void *buf, size_t count) = NULL; ssize_t (*original_write)(int fd, const void *buf, size_t count) = NULL; MYSQL *conn; // 初始化MySQL连接,程序启动时自动执行 __attribute__((constructor)) void init() { // 获取原系统函数的地址 original_read = dlsym(RTLD_NEXT, "read"); original_write = dlsym(RTLD_NEXT, "write"); // 初始化并建立MySQL连接 conn = mysql_init(NULL); if (!mysql_real_connect(conn, "localhost", "your_username", "your_password", "your_dbname", 0, NULL, 0)) { fprintf(stderr, "MySQL连接失败: %s\n", mysql_error(conn)); exit(1); } } // 清理MySQL连接,程序退出时自动执行 __attribute__((destructor)) void cleanup() { if (conn) { mysql_close(conn); } } // 自定义read函数:拦截目标文件的读操作,从MySQL读取数据 ssize_t read(int fd, void *buf, size_t count) { struct stat st; if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) { char path[256]; snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); char real_path[256]; if (readlink(path, real_path, sizeof(real_path)-1) != -1) { // 替换成你要拦截的目标文件路径 if (strcmp(real_path, "/path/to/your/target/file") == 0) { MYSQL_RES *res; MYSQL_ROW row; if (mysql_query(conn, "SELECT data FROM your_table LIMIT 1") == 0) { res = mysql_store_result(conn); if (res && (row = mysql_fetch_row(res))) { size_t data_len = strlen(row[0]); size_t return_len = data_len < count ? data_len : count; memcpy(buf, row[0], return_len); mysql_free_result(res); return return_len; } mysql_free_result(res); } } } } // 非目标文件,调用原read函数处理 return original_read(fd, buf, count); } // 自定义write函数:拦截目标文件的写操作,将数据写入MySQL ssize_t write(int fd, const void *buf, size_t count) { struct stat st; if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) { char path[256]; snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); char real_path[256]; if (readlink(path, real_path, sizeof(real_path)-1) != -1) { // 替换成你要拦截的目标文件路径 if (strcmp(real_path, "/path/to/your/target/file") == 0) { char escaped_data[2048]; // 转义特殊字符,防止SQL注入 mysql_real_escape_string(conn, escaped_data, buf, count); char query[2048]; snprintf(query, sizeof(query), "INSERT INTO your_table (data) VALUES ('%s')", escaped_data); if (mysql_query(conn, query) == 0) { return count; // 模拟原函数返回写入的字节数 } else { fprintf(stderr, "MySQL写入失败: %s\n", mysql_error(conn)); } } } } // 非目标文件,调用原write函数处理 return original_write(fd, buf, count); }
2. 编译动态库
用gcc将代码编译成共享库,需要链接MySQL客户端库和动态链接库:
gcc -shared -fPIC -o libmysqlhook.so hook.c -ldl -lmysqlclient
如果提示找不到mysqlclient库,先安装依赖包:
sudo apt-get install libmysqlclient-dev
3. 运行应用A
设置LD_PRELOAD环境变量,让应用A优先加载我们的自定义库:
LD_PRELOAD=./libmysqlhook.so ./appA
如果应用A是系统级程序,可能需要加上sudo,同时确保库的路径正确(或者把库放到/usr/lib这类系统库目录)。
关键注意事项
- 精准拦截目标文件:一定要通过文件路径准确判断哪些
read/write操作需要拦截,否则会破坏应用A的其他正常功能(比如日志、配置文件读写)。 - SQL注入防护:插入MySQL的数据必须用
mysql_real_escape_string转义特殊字符,避免安全风险。 - 错误处理:要处理MySQL连接失败、读写失败等异常情况,必要时可以回退到原函数行为,防止应用A崩溃。
- 静态编译应用的限制:如果应用A是静态编译的(不依赖动态链接库),
LD_PRELOAD会失效,这种情况需要用ptrace工具劫持系统调用,但实现复杂度会高很多。 - 安全性与权限:自定义库要确保没有漏洞,运行应用的用户需要拥有MySQL的读写权限。
内容的提问来源于stack exchange,提问作者Sreram
相关产品推荐
相关产品推荐

