本文提及所有代码都是在https://github.com/iovisor/bcc/tree/master/tools
很多时候,生产环境是需要对操作人的操作进行审计,防止有人做删库跑路这种脑残且毫无技术含量的行为。所以,就需要一些技术来跟踪用户的操作。
要学点有技术含量的,请学一下恶意软件相关技术吧。
用户操作的跟踪,就是跟踪用户名称,命令,以及命令的结果。
命令在Linux
下,大多数时候都是进程来表示。所以,很自然,就需要监控进程实时启动了。
目前有很多技术可以做到实时进程启动的监控。如audit
和内核驱动。
audit
是在系统调用上挂钩子,是静态的钩子,因为内核本身就提供了这样的钩子,只要打开就行。
内核驱动也是挂钩子,它是使用kprobe
和kretprobe
两种机制来挂钩,是动态的钩子,也就是原来不存在,现在它挂上了。
那么,eBPF
能不能做到呢?当然可以了。它可以支持kprobe
和kretprobe
的。
而它的工具bcc
就有专门的脚本做得这事。
[email protected]:~/Downloads$cat /etc/passwd>/dev/null
[email protected]:~/Downloads$cat /etc/user>/dev/null
cat: /etc/user: No such file or directory
[email protected]:~/Downloads$ps aux>/dev/null
[email protected]:~/Downloads$echo "hello"
hello
[email protected]:~/Downloads$cd
[email protected]:~$ld
ld: no input files
另外一个屏幕在这些命令执行之前运行
[email protected]:/usr/share/bcc/tools$sudo ./execsnoop -U -T -x
TIME UID PCOMM PID PPID RET ARGS
13:07:18 1000 cat 5458 5135 0 /usr/bin/cat /etc/passwd
13:07:22 1000 cat 5459 5135 0 /usr/bin/cat /etc/user
13:07:30 1000 ps 5462 5135 0 /usr/bin/ps aux
13:08:13 1000 ld 5464 5135 0 /usr/bin/ld
可以看到是, 上面例子捕获到用户ID为1000的用户使用了cat
,ps
和ld
三个命令,连cat
命令操作哪些文件也显示出来。
但有一个问题,像echo
和cd
这些shell
内置的命令,却捕获不到。
如果我们看一下execsnoop
这个脚本,可以看到这几行
b = BPF(text=bpf_text)
execve_fnname = b.get_syscall_fnname("execve")
b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
b.attach_kretprobe(event=execve_fnname, fn_name="do_ret_sys_execve")
它是使用kprobe
和kretprobe
来对execve
进行挂钩来跟踪实时进程启动的。
那么,怎么获取那些shell
内置命令的操作记录?
非常土的方法是读取用户目录下的.bash_history
文件,当然不同的shell
有不同的名称。
但这种手段,不是实时的,而且同时多个会话拼接在一起,往往会丢失一些记录。
如果是需要实时,怎么办?以往在某大厂时,是直接魔改bash
,这样确实能够采集得到。但会遇到版本兼容和稳定性的问题,经常出问题被业务部门各种操。
而eBPF
呢,它对用户态也有类似kprobe/kretprobe
之类的机制,叫uprobe/uretprobe
,基本能够对用户态函数挂钩,能够以非侵入式的方式跟踪用户操作记录。
bcc
的工具里,有一个叫bashreadline
,就是使用uprobe
机制,用来捕获bash
里任何的命令输入。
在第二个屏幕输入:
[email protected]:~/Downloads$echo "hello"
hello
[email protected]:~/Downloads$alias lls='ls -l'
[email protected]:~/Downloads$cd
[email protected]:~$cd -
/home/nainiu/Downloads
[email protected]:~/Downloads$export DOFU="sfjsk"
[email protected]:~/Downloads$ps aux>/dev/null
在这之前,在第一屏幕先运行bashreadline
[email protected]:/usr/share/bcc/tools$sudo ./bashreadline
TIME PID COMMAND
13:20:12 5135 echo "hello"
13:22:07 5135 alias lls='ls -l'
13:23:26 5135 cd
13:23:29 5135 cd -
13:23:56 5135 export DOFU="sfjsk"
13:24:36 5135 ps aux>/dev/null
可以看出来,这个工具能够完美把bash
上所有命令,包括外在的还是内置都显示出来。这里唯一不足,是没办法知道哪个用户。不过,要添加一下代码,也是非常容易做到的。
这里看一下它的代码,
b = BPF(text=bpf_text)
b.attach_uretprobe(name=name, sym="readline", fn_name="printret")
就是用自定义的函数printret
对readline
函数挂了一个uretprobe
的钩子。
很多时候,不光是想获取用户执行命令的记录,还想获取结果,也就是所谓的录屏。
这个操作,以往是一般改造logind
或sshd
。一个本地登录,启动各种shell
的守护进程,一个是远程登录的。
改造方式有两种:
PAM
模块,让logind
或sshd
启动时,都加载到,在这个模块里进行录屏。这种方式呢,对兼容性要求没上一种那么高,稳定性也好一些,毕竟启动PAM
模块时,会启动一个子进程处理,如果出现问题,也只是这个子进程会崩溃。但无论如何,这两种方法都是侵入式修改。我以前都用过,也背过堪称三座大山的锅,所以,对于这两种方式,都是避而远之。
eBPF
却可以对tty_write
之类的内核函数进行kprobe/kretprobe
挂钩,从而实现录屏功能。
bcc
里就有一个ttysnoop
的工具,使用上面机制进行录屏功能。
在第二屏幕执行:
[email protected]:~/Downloads$ls
VMwareTools-10.3.10-13959562.tar.gz vmware-tools-distrib
[email protected]:~/Downloads$ps
PID TTY TIME CMD
5135 pts/1 00:00:00 bash
6727 pts/1 00:00:00 ps
[email protected]:~/Downloads$date
2022年 12月 10日 星期六 16:05:18 CST
[email protected]:~/Downloads$time ls
VMwareTools-10.3.10-13959562.tar.gz vmware-tools-distribreal 0m0.003s
user 0m0.000s
sys 0m0.003s
在这之前,在第一屏幕执行sudo ./ttysnoop 1 -C
:
在这里第二屏幕是在
/dev/pts/1
,所以参数是1
[email protected]:/usr/share/bcc/tools$sudo ./ttysnoop 1 -C
ls
VMwareTools-10.3.10-13959562.tar.gz vmware-tools-distrib
[email protected]:~/Downloads$ps
PID TTY TIME CMD
5135 pts/1 00:00:00 bash
6727 pts/1 00:00:00 ps
[email protected]:~/Downloads$date
2022年 12月 10日 星期六 16:05:18 CST
[email protected]:~/Downloads$time ls
VMwareTools-10.3.10-13959562.tar.gz vmware-tools-distribreal 0m0.003s
user 0m0.000s
sys 0m0.003s
[email protected]:~/Downloads$
请注意第一行命令提示符和后面那些,是不是不一样了?
看一下ttysnoop
的代码:
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 11)
int kprobe__tty_write(struct pt_regs *ctx, struct file *file,
const char __user *buf, size_t count)
{
if (file->f_inode->i_ino != PTS)
return 0; return do_tty_write(ctx, buf, count);
}
#else
KFUNC_PROBE(tty_write, struct kiocb *iocb, struct iov_iter *from)
{
const char __user *buf;
const struct kvec *kvec;
size_t count;
if (iocb->ki_filp->f_inode->i_ino != PTS)
return 0;
/**
* commit 8cd54c1c8480 iov_iter: separate direction from flavour
* `type` is represented by iter_type and data_source seperately
*/
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
if (from->type != (ITER_IOVEC + WRITE))
return 0;
#else
if (from->iter_type != ITER_IOVEC)
return 0;
if (from->data_source != WRITE)
return 0;
#endif
kvec = from->kvec;
buf = kvec->iov_base;
count = kvec->iov_len;
return do_tty_write(ctx, kvec->iov_base, kvec->iov_len);
}
#endif
可见,它确实是使用了kprobe
对tty_write
进行挂钩来实现这个效果。
bcc
这三个工具,其实都是能够扩展的。如果黑客攻陷了一台主机,这台主机也能够运行eBPF
,他就可以通过类似工具来嗅探到这台主机(包括主机上运行的容器)任何操作,从而获取相关业务系统的用户密码或者横向移动。
那么,应该怎么防治呢?
bcc
有个工具叫bpflist
,能够获取当前系统运行的eBPF
实例。
[email protected]:/usr/share/bcc/tools$ sudo ./bpflist -vv
open kprobes:open uprobes:
PID COMM TYPE COUNT
1 systemd prog 18
6768 python map 2
6768 python prog 1
[email protected]:/usr/share/bcc/tools$ ps aux|grep 6768
root 6768 1.4 3.4 179796 137976 pts/3 S+ 16:18 0:02 python ./ttysnoop 1 -C
可以看到当前系统运行了两个程序,systemd
和ttysnoop
。
至于bpflist
的实现,大家自己看代码,非常简单的,就是读/sys/kernel/debug/tracing/*_events
的结果。
暗号:d9510