目标读者:系统安全爱好者
阅读时长:约 7分钟
本文概要:一种 Linux 中进程隐藏的思路、操作与步骤截图
前置知识:最好对 Linux 下编译程序略知一二(文末有知识注解与扩展阅读,最后奉上网易小风景)
感谢 NSRC 老司机团长的分享~
之前团队内技术交流时小伙伴分享的自建蜜罐深深的引起了我的兴趣,于是本人决定山寨一个类似的蜜罐把玩一番~但是摆在眼前的一个问题就是:
一些监控进程需要运行在蜜罐中,一但“请君入罐”后被黑客察觉到这些奇奇怪怪的进程,就十分尴尬了
所以当务之急是需要将这些监控进程隐藏起来,对Linux略知一二的楼主自然也就接受挑战啦。
众所周知,Linux 操作系统天生自带一个虚拟分区 /proc,该分区下保存硬件信息、内核运行参数、系统状态信息等等,进程运行时的一些信息自然也就存在这个分区下。
如上图所示,系统里运行的每一个进程都会在 /proc 分区下新建一个以自己 pid 命名的目录,并将本进程的参数存到该目录下。在该目录下我们可以通过修改 cgroup 文件暴力的将进程绑定在某个 CPU 上,也可以通过修改 cpuset 文件来优化进程在 NUMA 架构系统上的运行效率,还可以通过修改 oom_adj 文件让系统 OOM 机制永远高抬贵手。而 ps、top 这类查看进程的命令恰恰也就是在 /proc 分区下收集信息。换句话说,如果让 ps、top 命令选择性失明,也就能达到我们隐藏进程的目的了。
网上关于 Linux 隐藏进程的方式有很多,比如:
1)强行将进程 pid 变为 0,这种方法存在破绽因此不予考虑。
2)还有一种简单的方法:系统启动时会依据 /etc/fstab 文件内容来挂载分区,在 proc 分区挂载参数中加入 hidepid=2 参数后,登陆系统的用户只能查看到当前用户启动的进程的信息。也就是说, tomcat 用户只能看到属于 tomcat 用户进程的信息。
这种方法也存在弊端,罐中的黑客只能看到有限的进程信息,可能就会产生会怀疑。而且如果再存在个最近流行的 tomcat 提权漏洞,那罐子的身份就会瞬间露馅。所以这种方式也不合适,究竟如何才能将我们的问题解决在系统最底层呢?
对于 Linux 系统来说有着得天独厚的优势,我们可以从内核解决一切问题。可是楼主的 C语言 实在是捉急,不由得又开始在互联网上寻找巨人的肩膀。一篇相关的干货贴引起了我的注意 http://blog.csdn.net/billpig/article/details/6038330(文末有引用说明),这位作者的思路很明确,在内核中新增两个信号,当进程向内核发出 hide 信号时,内核将不会为该进程在 /proc 目录下生成对应的目录,从而也就从底层铲除了进程的信息,即使黑客获得了 root 权限也无法通过常规手段察觉到蛛丝马迹。除此之外,新增的unhide信号作用恰好与 hide 信号相反。
通过查阅 crux 官方文档发现,Demo 中使用的内核版本为 2.6.15。在include/asm-i386/unistd.h 文件中定义新信号 294 和 295。
系统在接收到我们新定义的 294 和 295 两个信号之后需要调用对应的函数来做出相应的动作。在 kernel/sys.c 中我们实现 294 和 295 信号调用的函数。如下图所示,sys_hide 和 sys_unhide 两个函数主要功能是修改进程 hide 变量的值。
proc 相关内核代码位于 fs/proc/base.c 中,在进程相关结构体中新声明变量hide,通过发送信号来修改 hide 的值,最后在 base.c 文件 proc_pid_readdir 函数中将 hide 变量的值作为进程是否在 proc 文件系统中体现的依据。
除此之外还有些零碎的步骤,查看上文中的原帖地址即可,不再赘述。
新内核编译完成后本以为可以愉快的开始玩耍了,结果新内核根本无法启动。通过各种修改启动参数发现,内核报错 “kernel too old” ,这就很尴尬了。再次站在巨人的肩膀上发现,确实是因为 kernel too old 。之前缺乏编译老旧内核的经验,现在才知道原来 gcc 调用的 libc 不是完全向下兼容的,我们可以通过
file /lib/libc-*.*.so
来查看当前 libc 能够编译的最低版本的内核,如下图所示 libc-2.5.so 可以编译最低到 2.6.9 版本的内核,如果编译内核版本低于 2.6.9 的话就会产生 kernel too old 的报错导致内核无法启动。
综上,我们的 libc 版本不可太高,又因为 Demo 中修改的是 i386 架构的内核源码,所以我选择的实验环境为 CentOS 5.11 (32bit) 。
至此,我们就可以成功安装并启动 2.6.15 版本内核了,然而事情并没有结束。系统供外部进程使用的信号仅有如下 64 个。
所以一般情况下我们的进程只能发出上图这 64 种信号,我们想要发出 294 和 295 号信号就必须借助于与系统关系更密切的 C语言 程序完成。
然后通过执行命令
gcc hide.c -fPIC -shared -o hide.so
将 hide.c 编译为 hide.so 的动态链接库。
以后我们如果有进程需要隐藏的话,只需要在代码里调用 hide.so 中的 hide 函数即可,同样 unhide 函数是他的逆过程。如下图所示,在 Python 中 import ctypes ,使用它调用 hide.so 库即可实现当前 Python2.7 进程的隐藏与恢复。
至此,已经实现了内核级别的进程隐藏,可以愉快地做一些想做而又怕被别人发现的事情了…
就跟Windows编译程序是一样的思路,Windows 下程序编译之后是一个可执行的文件,Linux下呢,编译内核的时候,一般也是从Git上把代码拖下来,是一个代码工程,编译后,生成一个对应格式的文件,这个时候该文件在Linux下 就跟其他文件一样,就是一个普通的文件。如果你想要把编译的这个内核在当前的系统中启动起来的话,由于当前系统中已经有一个正在运行的内核了,一个思路是你可以设置Linux一个变量(类似于Windows的环境变量),将该变量指向这个新编译好的内核文件,然后这个时候再重启当前Linux系统,系统启动时会读取那个变量的值,然后启动新的内核。(实验的时候建议在虚拟机里操作哦)
拓展阅读
《linux 隐藏进程 – crux实现》
发布日期:2010.11.26
链接:http://blog.csdn.net/billpig/article/details/6038330
《linux内核编译过程的最终总结版》
发布日期:2012.12.18
链接:http://blog.csdn.net/fjt19900921/article/details/8316481
*作者:网易安全应急响应中心(企业账号),转载请注明来自FreeBuf(FreeBuf.COM)
已有 35 条评论
感谢分享
操作系统小白一枚,不过那个应该叫中断向量吧。。信号量不应该是PV操作用的么?
@ zhanghaoyil 是写的时候搞错了,应该是 信号,下回注意
@ NSRC 已改~
小菜弱弱的问一句,为什么不HOOK获取pid的函数呢?类似于Windows下的进程隐藏。。。
@ hkt 作者也弱弱地表示,因为当时想到文中的这种方式,就实践了一把,并没有尝试更多的方式。不过,您说的这种方式在linux下应该也是可行的,下次折腾试试。谢谢
【不友善言论,部分和谐】
再说了,想隐藏进程还需要用rootkit么
信号和信号量是两码事……
@ s10e 嗯,描述错了,应该是“信号”,感谢指正~
= = 新建一个文件退出再登陆 如果文件没了 那就是蜜罐
很不错啊。当初我大二的时候还要用一天时间才能写出类似的三种方案,还不敢编译内核。没想到楼主一篇文章就解决了!厉害~~
@ QEver 死了不要紧,防止不是自己的机器。
@ QEver 想当年Q神在课堂上讲rootkit和进程隐藏还历历在目,膜拜一发
不错的文章
作者人长的特别帅,还特别幽默,技术还特别牛掰。
点赞!!!撒花!!!! 小白路过!!
@ 小花 活捉作者小号一枚
不是信号,也不是信号量,作者用的是系统调用
@ Jim 评论出真相,已经反省了一夜 ,感谢指正
文章不错!鼓励作者。
美中不足的是unistd.h中存放的是系统调用中断码,从汇编来看在系统调用的时候会引发int 0×80中断,此时eax寄存器中就存放的是这个中断码,syscall那里相当于引发了自己定义的系统调用中断码。内核通过中断码执行相关的系统调用。它和信号概念还是不同的。
@ 霁泫 已经反省了一夜 ,感谢指正~
系统调用号,文章里写成信号,评论里还说中断码。洗洗睡吧
@ Anonymous 已经反省了一夜
signal和syscall都分不清怎么写的文章[二哈]。。。
@ 刘一吨 已经反省了一夜 ,感谢指正~
安全哥,请问,之前绑定的手机号不再用了,怎样才能改密保手机啊
@ 枪神纪同好会 这类问题在NSRC的微信公众号后台也经常收到,这也是小编时常抓狂的问题,因为小编也只能按照网站上的流程一步步来尝试啊 好吧,这里就再说一遍,可以参考的是下边两个站点(有种只能帮你到这的悲凉):
1、网易客服服务:http://help.mail.163.com/service.html
2、网易通行证帮助中心:http://help.163.com/special/sp/urs_ans.html
可以劫持system_call,使用lkm rootkit之类的技术
@ l13t 每次学习评论的内容,总能收获很多。谢谢分享~
是好文章,但有以下几点:
1、当有人说是信号的时候作者说是描述错了是信号,当大家说是系统调用的时候又改成信号量;
2、提到的编译内核,你这只是一个简单的c代码的编译,和编译内核没关系;
3、调用so动态链接库也不是内核级。
这种感觉有点……
手误:1、当有人说是信号的时候作者说是描述错了是信号,当大家说是系统调用的时候又改成系统调用;
@ anonymous 技术这东西,讨论讨论总能找到正确答案的。其实作者最初文中也表示了对Linux略知一二,实践一下这种方法然后分享出来,但是通过学习评论里提到的各种知识点,倒是督促着小编这几天补了一些知识点。学习了 以后写技术文章会多做功课,严谨一些,争取表达到位一些。多谢评论区各位同学指正~感谢你
有简单的方法, 劫持ps,file,ls,bash,等一系列工具也是妥妥的。ptrace也可以~~~
@ malokch Get了,每次学习评论的思路,总能有收获。谢谢分享~
contos6 编译的时候会报错内核有改动怎么解决?
kernel/sys.c: In function ‘sys_unhide’:
kernel/sys.c:1869: error: ‘struct task_struct’ has no member named ‘hide’
kernel/sys.c:1872: error: ‘struct task_struct’ has no member named ‘hide’
make[1]: *** [kernel/sys.o] Error 1
make: *** [kernel] Error 2
@ Dr.v 这个版本作者没有试验成功,就没有继续折腾 。按照作者试验成功的版本,可以尝试下centos5.11 32位 版本,然后还有一些小细节 在http://blog.csdn.net/billpig/article/details/6038330 这篇帖子里有提到。