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?
  • 需要解决以下三点
    1. 如何设置自己的库
    2. 如何利用LD_PRELOAD
    3. 如何更改正在运行的进程的环境变量

问题点1:如何设置自己的库

只能利用二进制文件中上传文件的过程

  • 目标处理将用户输入的数据保存为文件
  • 通常没有这样的代码,并且存在严格限制
  • 如果服务器上同时运行多个问题服务,则可以通过其他问题获取的shell将文件保存到/tmp等目录

问题点2:如何利用LD_PRELOAD

问题点3:如何更改正在运行的进程的环境变量

  • 在程序启动前修改环境变量很简单
  • libc中的putenv()函数可以修改运行中进程的环境变量
    • 但是,通过ROP调用putenv,不如直接调用system
  • 如果存在足够的溢出空间,则可以伪造stack envp[]
  • 如果envp[]中一个元素被修改为”LD_PRELOAD=./mylib.so” 就可以了
  • 通过溢出修改envp
  • 在return前(canary检查前)调用system

运行测试

首先生成自定义的库

这个函数具有constructor属性,因此会在库被加载时自动执行

通过溢出使函数返回前调用system()

  • 运行后,打开了另外一个shell
  • 通过伪造环境变量成功执行了mylib.so

总结

这种方式的条件非常严格:

  1. 远程shell类型(没有suid/sgid)
  2. 可以在远程环境设置任意文件
  3. 存在足够的溢出空间
  4. 栈溢出后,返回之前调用system()
  5. 可以向固定地址区域写入任意数据

当然,遇到了的话就直接用以上知识解决吧