Linux安全系列是一套完整的检查linux系统是否被入侵,后门排查,木马清除,黑客溯源,以及恢复攻击路径的方法介绍。
由于是第一篇,在说主题前面先简单介绍一下目前linux木马的现状,目前主流的linux木马都不是内核级的,因为内核木马很难满足各个发行版的兼容性要求且需要root权限,所以几乎都是用户层的。从功能上看,基本都是实现一个挖矿功能和一个反弹rootshell功能。rootshell的触发机制,使用定时反弹固定iP或Ping触发,也有更底层的任意端口触发和无端口触发(netfilter prerouting)。
它们最显著的三个共同特点:
1、运行后删除自身,只驻扎在内存中
有时使用lsof|grep deleted会有惊喜哦,如图:
2、运行后改名,且大多数木马改为内核形态的进程命名方式即[xxx],用以迷惑视线
3、修改/etc或环境变量进行动态链接库劫持,达到隐藏进程和端口的目的
cat /etc或使用ldd/bin/ps命令,可以看到异常,如图:
我们进入正题说下进程:
- Linux下有3个特殊的进程,idle进程(PID = 0), init进程(PID = 1)和kthreadd(PID = 2)
- idle进程,linux的第一个进程
- /sbin/init进程由idle通过kernel_thread创建,是所有用户进程的父进程或祖先
- [kthreadd]进程由idle通过kernel_thread创建,是所有内核进程的父进程
举个例子了解一下:
首先,内核进程的命名规则都是[xxx],用中括号括起来的,而用户层的进程正常情况下不会这样,那么,如果一个[xxx]形式的进程他的父进程是1(也就是/sbin/init),那是不是有问题?因为带中括号的进程的父进程正常情况下应该是2(也就是[kthreadd]),如图:
这是一个针对改名的例子,改名代码很简单,所以利用广泛
strncpy(argv[0],FAKE_NAME, strlen(argv[0]));
就能实现,有兴趣的可以自行测试做实验
那么如果后门没有改成[xxx]形式又如何发现呢?
我们来观察几条能得到进程名字的命令,看看哪些得到的是真名,哪些是假名:
ps -ef
cmdline
pstree
ls -l/proc/pid/exe
可以看到前两个得到的名字是一样的为[locked],后两个得到的名字也是一样的为rootkit
前面两为假名,后面两为真名
那么未改名前的名字(即真实名字)就可以通过后面这两种方式查出来
我们也可以把它写成脚本来进行全部循环比较,知识星球里给出了脚本下载。
进程隐藏:
进程的隐藏可以在不同的层面实现
用户层---系统调用层---内核层---文件系统层
常用的隐藏方法如下:
- 替换ELF文件
- 劫持LD_LIBRARY_PATH
- 劫持syscall_table
- 篡改syscall_table劫持系统调用
- Inlinehook 系统调用
- VFS层劫持
- kprobe
- 感染系统关键内核模块
对于ELF文件被替换,我们可以使用
rpm -Vf/bin/ps 来校验
S为 大小 5为 MD5 T为时间 M为属性
如果执行命令后出现S等字样,就说明被改变了,如果S和5同时改变,那么就是有问题的,ps命令将变得不可信
上图,可以看到SM5T全部改变了
关于 LD_LIBRARY_PATH的劫持,劫持的方法有两种,环境变量和 /etc
如何发现?我们使用file /bin/ps观察如下:
证实ps命令它是动态编译的,使用了系统的共享库,如果此时动态链接被劫持,那么运行该程序的时候也会把后门程序劫持后的共享库加载进去(类似windows下的LPK劫持),通常实现一些隐藏的目的
使用 ldd /bin/ps 命令可发现异常
正常应该全部为/lib 或者 /lib64下的系统动态链接库
如果最后一行(也有可能不是最后一行)是其他的so文件,那么就要考虑是否动态库被劫持了
进一步确认的话,可以跟干净的系统进行比较或者使用gdb调试,简单点也可以使用strings静态查看关键字符
对于上述两种应用层隐藏的方式,我们的反制方法:
当发现PS命令不可信的情况下,我们可以使用Linux静态命令工具箱busybox来执行操作系统命令,以求达到一个干净的环境
我们可以file busybox看到它是静态编译的,不受动态库环境变量的影响
使用busybox 执行ps命令
那么内核层的呢?
系统调用下的劫持,我们后面开篇单独讲。
关于VFS层的劫持,这个在rootkit中用得很多
大名鼎鼎的adore-ng和enyelkm就是使用的这个方式
Linux下一切皆文件,说一下ps命令的本质,ps命令的本质还是读取文件,ps程序通过 open /proc 打开 fd,通过 getdents <fd> 遍历目录,如图执行命令strace/bin/ps
可以看到它最终其实是实现了系统调用getdents
而通过劫持它在VFS层的回调函数readdir/iterate(高版本)的地址可以实现一切基于文件操作的隐藏,包括ls,dir,ps,top等
检查隐藏进程
不管是用户层的隐藏还是内核层的隐藏,我们想出一个办法,如果在内核态直接把所有进程遍历打印出来,然后再在用户态模拟ps执行命令,那么把两种结果进行循环比较,如果内核态的进程比ps模拟执行的进程多,那么隐藏进程就会被筛选出来
使用这个内核函数for_each_process()可以遍历内核态所有进程
根据这个原理,我写了一个隐藏进程检测工具,如下图检测工具运行结果:
对隐藏进程检测工具感兴趣的可以加入知识星球进行下载。
执行 tar -xvf
是简单的一个脚本,测试在cen环境下,其他发行版的大家可以自己延申
最后给大家一个一条命令隐藏进程的小技巧:
mount --bind
可以看到两次执行ps -ef|greprootkit的返回结果是不一样的,第二次看不到rootkit了
原因就是我们中间执行了这么一条命令
mount--bind /root/aaa /proc/12755
把12755的进程目录绑定到一个空目录上,这样ps遍历该进程目录的时候就为空,达到了隐藏进程的目的(包括top命令都隐藏了)
如果需要卸载用umount/proc/12755
那么怎么检查呢,我们观察,除kcore外只有其在/proc下字节为非0
因此我们给出一条检查命令
ls -l/proc|awk '$9!="kcore"&&$5==4096 {print $9}'
完整卸载命令如下:
umount/proc/`ls -l /proc|awk '$9!="kcore"&&$5==4096 {print $9}'`