在Linux如何隐藏一个进程呢?
平时,我们都是使用ps来查看进程信息。但ps做了什么呢?执行strace ls看一下,可以看到输出结果有如下几行
openat(AT_FDCWD, "/proc", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0555, st_size=0, ...},
getdents64(5, 0x55aa4987a120 /* 311 entries */, 32768) = 7856
好吧,果然是Unix思想“一切皆文件”,连ps命令只是读取/proc下面的内容。所以,很多主机入侵检测系统(HIDS)检查恶意进程也是读取/proc下的内容。安卓手机上很多检测rooted的方案也会使用这种方法来检测su进程来确定是否rooted过了。而同样,安卓不少rooted工具是通过挂钩读取文件相关的系统调用如open,openat,opendir,getdents等来隐藏su进程,从而躲避检测。
那么,在Linux隐藏一个进程,就和隐藏一个文件类似,一般是两种方法:
劫持 libc.so或系统调用的API,针对单个进程隐藏,可以给该进程设置LD_PRELOAD环境变量或者调整LD_LIBRARY_PATH环境变量里路径的顺序。对所有进程隐藏,则把劫持的so文件的路径加入到/etc/ld.so.preload最复杂,也是最困难,就是在内核里进程相关的函数挂钩劫持。
而Bvp47是在Linux内核挂钩劫持。看看它对内核里哪些进程相关的函数挂钩。
proc_root_lookup - 在/proc下查看进程 proc_pid_readdir - 读取某进程的/proc目录 "kill_"前缀 - 杀死进程 sys_kill - 杀死进程 sys_rt_sigqueueinfo - 进程信号队列信息 sys_tkill - 杀死进程 sys_tgkill - 杀死进程 sys_getpriority - 获取进程优先级 sys_setpriority - 设置进程优先级 sys_getpgid - 获取进程组id sys_getsid - 获取进程会话id sys_capget - 获取进程能力 setscheduler - 调度进程 sys_sched_getscheduler - 获取进程调度器 sys_sched_getparam - 进程参数获取 sched_getaffinity - 进程与cpu绑定的关系获取 sched_setaffinity - 设置进程绑定CPU sys_sched_rr_get_interval - 调度间隔 sys_ptrace - 调试进程 sys_wait4 - 等待进程执行结束 sys_waitid - 等待进程执行结束 do_execve - 执行命令 do_fork - 创建进程 release_task - 退出进程 do_acct_process - BSD进程审计功能
根据上面strace ls的结果,所以,Bvp47要隐藏它自身进程第一步,就是对遍历/proc的函数挂钩。由于proc是虚拟文件系统,所以,在内核态中,它并不是像隐藏文件那样挂钩vfs_readdir,而挂钩proc_root_lookup来隐藏自身进程。
由于pid的范围是一定的,最小值是1,最大值在/proc/sys/kernel/pid_max里, 比如我的电脑是4194304,那么可以通过检测/proc/<pid>这个目录是否存在来确定进程是否存在。所以,Bvp47就通过挂钩proc_pid_readdir来隐藏自身进程。
但对于HIDS开发人员来说,读取/proc下的方式实际上是一种指纹检测的方法,而HIDS往往会采用更多基于行为检测的方法。
当一个进程存在时,虽然它从proc系统里隐身了,但它还是存在于系统中,它可以接收信号,接受调度,可以被调试,接受系统调用对它的状态查询。由于pid的范围是一定的,可以通过枚举整个范围pid,对它们发信呈,调度,调试,状态查询,再对照proc的结果来检测出进程是否隐藏。
最简单的是使用kill命令,用shell脚本就可以实现。
kill -0 <pid>
echo $?
-0并不会杀掉进程,只是获取它的存在,如果存在,$?就是0。
而kill命令其实就是使用kill这个系统调用,所以,Bvp47必须对内核态对应的函数sys_kill,sys_tkill,sys_tgkill,kill_前缀的挂钩,从而屏蔽这些信号的探测。
同理,要信号屏蔽,Bvp47就肯定要对sys_rt_sigqueueinfo挂钩。
如果对这些函数原型进行查看,它们的参数里有一个是pid或参数的成员是pid,都可以通过枚举所有pid来检测进程是否隐藏,所以,这也是Bvp47对这些函数挂钩来隐藏的原因。
sys_getpriority - 获取进程优先级 sys_setpriority - 设置进程优先级 sys_getpgid - 获取进程组id sys_getsid - 获取进程会话id sys_capget - 获取进程能力 setscheduler - 调度进程 sys_sched_getscheduler - 获取进程调度器 sys_sched_getparam - 进程参数获取 sched_getaffinity - 进程与cpu绑定的关系获取 sched_setaffinity - 设置进程绑定CPU sys_sched_rr_get_interval - 调度间隔 sys_ptrace - 调试进程 sys_wait4 - 等待进程执行结束 sys_waitid - 等待进程执行结束
貌似通过上面手法,Bvp47已经可以隐藏掉它的进程,那为什么它还要对这些函数挂钩呢?
do_execve - 执行命令 do_fork - 创建进程 release_task - 退出进程 do_acct_process - BSD进程审计功能
由于上面的检测方法都是主动检测,只能定时,从而有时间间隙来绕过。而目前大多数HIDS都使用实时进程事件检测的方式来建模,发现威胁。
而Linux实时进程事件检测的方法主要是几种:
用户态挂钩系统调用 fork,execve,clone,exit等系统调用,从而捕获它的事件。腾讯洋葱HIDS,滴滴驭龙HIDS采用这种方式用户态使用 netlink的kernel connector模式。华为云HIPS,腾讯洋葱HIDS采用这种方式。用户态调用 audit框架。青藤云HIDS采用这种方式内核态 eBPF+kprobe方式。美团HIDS采用这种方式,据说阿里云HIDS也是这种。内核态驱动 kprobe方式。字节跳动HIDS采用这种方式
而上面这些方式,在内核里面,最后都会落入到do_execve,do_fork, release_task,do_acct_process,其中前两者创建的,后两者是退出的(均在do_exit函数的执行流)。
所以,Bvp47就可以通过挂钩这四个函数,直接把进程创建和退出事件直接扼杀在摇篮中,让外界都无法知晓。
那,现在的HIDS有没有可能检测得到呢?按照目前的执行流来看,无论进程是否隐藏,它做任何操作都需要调用系统调用。而每个操作均可以分为几阶段:
进程调用系统调用,如 fork由用户态切换到内核态 内核态入口按照调用号去调用对应内核接口函数,如 sys_fork进入内核接口函数,如 sys_fork内核接口函数调用实际函数,进入实际函数,如 do_fork实际函数执行完,返回结果 内核接口函数返回结果 内核态入口返回结果,切换到用户态
HIDS在第3,4,7,8步挂钩,是可以检测到一些异常情况的。(3,8这两步启用audit框架,会自动挂钩,而4,7这两步一般是eBPF或驱动级使用kprobe来挂钩)
如创建一个进程,却发现获取不到当前进程的信息。但这种消息会淹没大量进程创建事件中,需要非常细心地筛选才能够找到。
不过,本人对Linux内核所知甚少,也许会有其它方法可以检测得到。希望有大佬提供一下,谢谢!

推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……




还没有评论,来说两句吧...