Shared Library Injection
关注环境变量LD_PRELOAD
LD_PRELOAD这个对于CTF选手来说应该很常见
例如这样使用
- hook libc中的函数(例如printf),将其替换成自定义函数
- $ export LD_PRELOAD=./mylib.so
- $ ./a.out
- mylib.so中重写了printf()函数,a.out调用printf时,实际调用的是重写后函数
LD_PRELOAD环境变量的细节文档
正常情况下, Linux 动态加载器
ld-linux
(见 man 手册 ld-linux(8)) 会搜寻并装载程序所需的共享链接库文件, 而LD_PRELOAD
是一个可选的环境变量, 包含一个或多个指向共享链接库文件的路径. 加载器会先于 C 语言运行库之前载入LD_PRELOAD
指定的共享链接库,也就是所谓的预装载 (preload
)。预装载意味着会它的函数会比其他库文件中的同名函数先于调用, 也就使得库函数可以被阻截或替换掉. 多个共享链接库文件的路径可以用
冒号
或空格
进行区分. 显然不会受到LD_PRELOAD
影响的也就只有那些静态链接的程序了.当然为避免用于恶意攻击, 在
ruid != euid
的情况下加载器是不会使用LD_PRELOAD
进行预装载的.更多阅读: https://blog.fpmurphy.com/2012/09/all-about-ld_preload.html#ixzz569cbyze4
就是一个能够重新加载函数的环境变量,但是如果设置了suid/sgid的话就不可用
思路
- 对于获取远程shell的题目,没有设置suid/sgid
- 这种情况下,可能使用LD_PRELOAD加载库来获取shell
问题
- LD_PRELOAD具体是否可以用于远程获取shell?
- 需要解决以下三点
- 如何设置自己的库
- 如何利用LD_PRELOAD
- 如何更改正在运行的进程的环境变量
问题点1:如何设置自己的库
只能利用二进制文件中上传文件的过程
- 目标处理将用户输入的数据保存为文件
- 通常没有这样的代码,并且存在严格限制
- 如果服务器上同时运行多个问题服务,则可以通过其他问题获取的shell将文件保存到/tmp等目录
问题点2:如何利用LD_PRELOAD
假设可以在正在运行的进程中伪造LD_PRELOAD环境变量
- 具体如何操作,之后说明
能够影响LD_~系列环境变量的,基本是在外部命令运行时
- 也就是说,应该先调查system()或者execve()之类的
首先,确认system()对环境变量的处理
整个流程跟踪下来,可以确定system()具有与调用进程相同的环境变量
也就是说,system()环境变量自动继承
而通过exp rop等方式可以调用system()
二进制文件自身也可能调用system()
问题点3:如何更改正在运行的进程的环境变量
- 在程序启动前修改环境变量很简单
- libc中的putenv()函数可以修改运行中进程的环境变量
- 但是,通过ROP调用putenv,不如直接调用system
- 如果存在足够的溢出空间,则可以伪造stack envp[]
- 如果envp[]中一个元素被修改为”LD_PRELOAD=./mylib.so” 就可以了
- 通过溢出修改envp
- 在return前(canary检查前)调用system
运行测试
首先生成自定义的库
这个函数具有constructor属性,因此会在库被加载时自动执行
通过溢出使函数返回前调用system()
- 运行后,打开了另外一个shell
- 通过伪造环境变量成功执行了mylib.so
总结
这种方式的条件非常严格:
- 远程shell类型(没有suid/sgid)
- 可以在远程环境设置任意文件
- 存在足够的溢出空间
- 栈溢出后,返回之前调用system()
- 可以向固定地址区域写入任意数据
当然,遇到了的话就直接用以上知识解决吧