前言
今天看到了一篇关于通过ld_preload劫持实现后门的文章,为了搞清楚ld_preload的原理,就有了这篇文章。
由于本人水平有限,文章中可能会出现一些错误,欢迎各位大佬指正,感激不尽。如果有什么好的想法也欢迎交流~~
LD_PRELOAD简介
LD_PRELOAD是linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。
程序的链接方式
上面说了LD_PRELOAD可以影响程序的运行时的链接,那么链接的方式都有哪些呢?
程序的链接可以分为以下三种
- 静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。
- 装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。
- 运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。
静态链接库,在 Linux 下文件名后缀为 .a,如 libstdc++.a 。在编译链接时直接将目标代码加入可执行程序。
动态链接库,在 Linux 下是 .so 文件,在编译链接时只需要记录需要链接的号,运行程序时才会进行真正的“链接”,所以称为“动态链接”。如果同一台机器上有多个服务使用同一个动态链接库,则只需要加载一份到内存中共享。因此, 动态链接库也称共享库 或者共享对象。
动态链接库的文件名规则
libname.so.x.y.z lib:统一前缀。 so:统一后缀。 name:库名,如 libstdc++.so.6.0.21 的 name 就是 stdc++。 x: 主版本号 。表示库有重大升级,不同主版本号的库之间是不兼容的。如libstdc++.so.6.0.21 的主版本号是 6。 y: 次版本号 。表示库的增量升级,如增加一些新的接口。在主版本号相同的情况下, 高的次版本号向后兼容低的次版本号 。如 libstdc++.so.6.0.21 的次版本号是 0 。 z: 发布版本号 。表示库的优化、bugfix等。相同的主次版本号,不同的发布版本号的库之间 完全兼容 。如 libstdc++.so.6.0.21 的发布版本号是 21。
如何搜索动态链接库
编译目标代码时指定的动态库搜索路径(可指定多个搜索路径,按照先后顺序依次搜索); 环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径(可指定多个搜索路径,按照先后顺序依次搜索); 配置文件 /etc/ld.so.conf 中指定的动态库搜索路径(可指定多个搜索路径,按照先后顺序依次搜索); 默认的动态库搜索路径 /lib; 默认的动态库搜索路径 /usr/lib;
加载顺序:LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib>/usr/lib
程序中我们经常要调用一些外部库的函数,以rand为例,如果我们有个自定义的rand函数,把它编译成动态库后,通过LD_PRELOAD加载,当程序中调用rand函数时,调用的其实是我们自定义的函数
使用ld_proload函数的方式
1)编写要劫持函数的文件
PS:这里要求函数的名称,变量以及变量类型、返回值及返回值类型都要与要替换的函数完全一致。因此在写动态链接库之前要先看一下对应的函数手册。
2)编译成.so文件:gcc inject.c --shared -fPIC -o inject.so
$ gcc -shared -o hack.so hack.c
3)加载so文件
3.1)修改/etc/ld.so.preload配置文件。
3.2)设置变量:export LD_PRELOAD="./hack1.so" 设置以后通过env可以看到。
例子1:劫持gets() 函数
1)首先编写劫持函数文件hook.c
#include<stdio.h> #include<dlfcn.h> //用于搜索原函数 /* 要求:函数的形式必须和原函数一样(返回类型,函数名,函数参数)*/ char* gets(char* str){ /* 自定义的操作区域 */ p