写在前面
很久没写博客了,感觉已经摆烂了很久,所以今天下定决心要把之前遇到的一个点解决掉。
去年打广东省强网杯决赛的时候上午算是安全运维赛,遇到了一道题,和Linux库文件劫持有关,于是趁此机会把这个点讲清楚,也算是问了后面网鼎杯半决赛的RDG(实景防御)复习开个好头。
Linux库文件
在讲库文件劫持之前需要了解Linux的库文件到底是什么。Linux的库文件分为动态库和静态库。
对于静态库文件来说,所有代码在编译的时候就会被加载,因此可执行程序体积较大。
本次主要讨论动态库,在编译时引入动态库(so)并不会将动态库中的代码编译到可执行程序中,而是在可执行程序中记录了对so文件的引用,当执行时,才会去加载so文件,以节省内存空间。
动态库文件加载顺序
gcc 编译时指定的运行时库路径 -Wl,-rpath
环境变量 LD_LIBRARY_PATH
ldconfig 缓存 /etc/ld.so.cache
系统默认库位置 /lib /usr/lib
第一个Linux动态库demo
为了模拟赛时场景,需要写一个so文件并且调用,先写一个最简单的demo,后面再将赛时场景加入。
当前有三个文件,分别是main.c add.c add.h
内容分别如下
main.c:
add.c:
Add.h:
add.c中定义了方法adds,返回a+b的值,add.h中给出了方法adds的声明
main.c中调用了adds方法并且输出值
生成so文件
1 | gcc add.c -fPIC -shared -o libadd.so |
-fPIC是编译选项,PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性; -shared是链接选项,告诉gcc生成动态库而不是可执行文件。
1 | gcc main.c -L. -ladd -o main |
-ladd表示链接libadd.so文件
-L.表示搜索要链接的库文件时包含当前路径。
注意,如果同一目录下同时存在同名的动态库和静态库,比如 libadd.so 和 libadd.a 都在当前路径下, 则gcc会优先链接动态库。
最后生成的为main可执行文件
直接执行会报错,提示
./main: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
出现这个错误的原因是没有找到libadd.so文件,原来Linux是通过 /etc/ld.so.cache 文件搜寻要链接的动态库的。 而 /etc/ld.so.cache 是 ldconfig 程序读取 /etc/ld.so.conf 文件生成的。
所以,需要修改/etc/ld.so.conf文件内容,添加so文件的路径,并且使用ldconfig命令更新。
ldd与nm命令
ldd命令可以查看一个可执行程序依赖的共享库,比如刚才写的libadd.so
nm命令
查看一个库中到底有哪些函数,nm命令可以打印出库中的涉及到的所有符号。
strace命令
strace命令用于跟踪系统调用,常用命令如下
1 | strace -o output.txt -T -tt -e trace=all -p 28979 |
上面的含义是 跟踪28979进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(并以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。
通过strace可以看到调用了哪些动态链接库,例如下面给出的whoami的例子(后面一张图是截的别人的,自己的好像没有看到那么多库)
1 | strace /usr/bin/whoami |
挖矿场景下的动态库劫持分析
在处理过的挖矿事件中,挖矿程序通常会隐藏真实的挖矿进程,导致通过ps top等命令无法看到挖矿进程。
写一个类似的demo去模拟该场景,我自己服务器性能太差了 跑不动,就把socket连接换成了print ok
1 | #!/usr/bin/python |
python2 evil.py 1.1.1.1 8888
运行之后使用top命令和ps aux 可以看到所运行的进程
接下来挖矿程序的目的是隐藏该CPU/内存占用率过高的进程。
简单地说,ps命令是通过遍历/proc目录获取与进程相关的信息(pid status cmdline等)
进程隐藏方法
alias命令
alias ‘ps’=’ps aux | grep -v python’
这里的python可以换成任意目标字符串或者获取到的pid
替换二进制文件
默认ps的链接文件为
可以通过替换ps命令或者新建连接实现二进制文件替换/修改,如将ps删除,新上传一个恶意的ps,实现进程隐藏。
预加载(preloading)
通过预加载,Linux给了我们一个选项,在其他正常的系统库被加载之前加载一个自定义共享库。这意味着,如果自定义库导出的函数与系统库中的函数名相同,我们就可以用我们库中的自定义代码覆盖它,而所有的进程都会自动选择我们的自定义函数)
例如,通过重写readdir()方法,在去去读/proc目录的时候,刻意隐藏某个进程。
1 |
|
repo:https://github.com/gianlucaborello/libprocesshider.git
clone下来之后直接make,就可以生成so文件
使用预加载有几种方式:
1.LD_PRELOAD 环境变量
LD_PRELOAD环境变量是会及时生效的,使用LD_PRELOAD加载恶意动态链接库方法如下:
LD_PRELOAD=/lib/evil.so LD_PRELOAD的值设置为要预加载的动态链接库
export LD_PRELOAD 导出环境变量使该环境变量生效
unset LD_PRELOAD 解除设置的LD_PRELOAD环境变量
运行evil.so 并且通过ps查看
Unset LD_PRELOAD后再次通过ps查看
LD_PRELOAD不仅可以通过shell设置后然后export,还可以通过修改bash_profile 永久保存
2./etc/ld.so.preload文件
/etc/ld.so.preload是一种全局性的修改,影响范围比第一种方式更大,可以在该文件中指定so文件,以预加载恶意so文件。
具体操作方法如下:
先看一下刚才执行evil.py时ps的结果,可以看到evil.py
现在再执行evil.py,查看ps aux的结果:
可以看到彻底隐藏了evil.py的进程.
当清空/etc/ld.preload中的内容后,可以看到进程又出现了
广东省强网杯Final-安全运维
题目环境为Linux,当时状况如下:
1.netstat 出现疑似外连行为
2.ps aux发现反弹shell命令
3.通过lsof查看端口 进程信息 无收获
只能找到路径/root/qwb
赛时解
比赛的时候做到这里实在是不知道怎么做了,实际上也没有找到运行的elf文件,最后随意翻了翻翻到了一个so文件,IDA打开之后看到内容就是反弹shell执行的命令,把so文件删掉之后这道题就算是patch了。
正确解
场景1.可以找到可执行文件
通过ldd命令分析可执行文件所调用的动态库文件
这里可以看到有libqwb.so(或者其他奇奇怪怪的so文件名)
将该文件直接删除即可
场景2.找不到可执行文件(so文件不在/usr/lib 或者/lib下)
查看LD_PRELOAD环境变量,是否有可疑文件
查看/etc/ld.so.conf文件内容 是否有可疑路径
最终定位到tmp目录下的so文件,将其删除即可
参考
本文所用demo均可在以下链接中找到
https://sysdig.com/blog/hiding-linux-processes-for-fun-and-profit/
https://www.cadosecurity.com/linux-attack-techniques-dynamic-linker-hijacking-with-ld-preload/