本文将简单罗列Linux监控的实现方式,安全及审计软件的前提就是能在关键点上接管系统的执行流程,而关键点的接管必须依赖Linux内核的监控能力:
1 LSM HOOK
Linux内核中为发实现其安全机制,已经预留了LSM HOOK埋点,以4.4.131的内核为例,系统共提供了多达198个监控点回调,4.4.131是当前麒麟操作操作系统的内核版本,而较新的5.4.2内核已经支持多达216监控回调:
类别 | 钩子函数个数 | 功能 |
---|---|---|
ptrace | 2 | Ptrace操作的权限检查 |
能力机制 | 3 | Capabilities相关操作的权限检查 |
磁盘配额 | 2 | 配盘配额管理操作的权限检查 |
超级块 | 13 | mount/umount/文件系统/超级块相关操作的权限检查 |
dentry | 2 | dentry相关操作的权限检查 |
文件路径 | 11 | 文件名/路径相关操作的权限检查 |
inode | 27 | inode相关操作的权限检查 |
file | 13 | file相关操作的权限检查 |
程序加载 | 5 | 运行程序时的权限检查 |
进程 | 27 | 进程/进程相关操作的权限检查 |
IPC | 21 | IPC消息/共享内存/信号量等操作的权限检查 |
proc文件系统 | 2 | proc文件系统相关操作的权限检查 |
系统设置 | 2 | 日志,时间等相关操作的权限检查 |
内存管理 | 1 | 内存管理相关操作的权限检查 |
安全属性 | 7 | 对安全相关数据结构的操作的权限检查 |
网络 | 37 | 网络相关操作的权限检查 |
XFRM | 11 | XFRM架构想要关操作的权限检查 |
密钥 | 4 | 密钥管理相关操作的权限检查 |
审计 | 4 | 内核审计相关操作的权限检查 |
Android Binder | 4 | Android Binder架构有关操作的权限检查 |
总计 | 198 |
其中我们最关心的是进程、程序加载、内存及文件相关的监控回调,从数量及功能上已足够强大,但具体能否满足我们的需求,还要在实现过程中进行分析和验证。比如以进程创建为例,Linux内核的进程创建是通过sys_clone(sys_fork/sys_vfork)及sys_execve实现的,但LSM的监控点为了避免多入口的问题,放在了task_alloc这个点上:调用栈如下所示:
backtrace
#0 security_task_alloc (task=0xffff923cad2f8000, clone_flags=4001536) at security/security.c:1472
#1 0xffffffffa0a88ff4 in copy_process (clone_flags=4001536, stack_start=<optimized out>, stack_size=<optimized out>, child_tidptr=<optimized out>, pid=0x0, trace=<optimized out>, tls=<optimized out>, node=-1) at kernel/fork.c:1746
#2 0xffffffffa0a8a59f in copy_process (node=<optimized out>, tls=<optimized out>, trace=<optimized out>, pid=<optimized out>, child_tidptr=<optimized out>, stack_size=<optimized out>, stack_start=<optimized out>, clone_flags=<optimized out>) at kernel/fork.c:1574
#3 _do_fork (clone_flags=4001536, stack_start=<optimized out>, stack_size=<optimized out>, parent_tidptr=<optimized out>, child_tidptr=<optimized out>, tls=<optimized out>) at kernel/fork.c:2056
#4 0xffffffffa0a8a969 in SYSC_clone (tls=<optimized out>, child_tidptr=<optimized out>, parent_tidptr=<optimized out>, newsp=<optimized out>, clone_flags=<optimized out>) at kernel/fork.c:2166
#5 SyS_clone (clone_flags=<optimized out>, newsp=<optimized out>, parent_tidptr=<optimized out>, child_tidptr=<optimized out>, tls=<optimized out>) at kernel/fork.c:2160
#6 0xffffffffa0a03ae3 in do_syscall_64 (regs=0xffff923cad2f8000) at arch/x86/entry/common.c:287
#7 0xffffffffa1400081 in entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:237
#8 0x00007ffc00600600 in ?? ()
#9 0x000056066c0ceec0 in ?? ()
#10 0x0000000000000000 in ?? ()
在进程创建这个点上我们需要取到哪些信息:主进程、子进程、调用栈及相应的符号信息等等,但security_task_alloc这个点不能满足我们的需求,但再利用程序加载(BPRM)的相关回调我们即可将所有的信息串联起来,有了这个信息我们就能够做进详细的检测与判定,以上就是我们解决问题的主要思路。
2 Syscall_Table HOOK
这种方式就类似于 SSDT HOOK,但Linux内核并没有类似于Windows内核的PatchGuard (KPP)的防护机制,目前天擎国产化就是使用的此技术,但在PKS系统上,可信计算以及澜起安全内存对syscall table以及内核镜像都是有安全度量以及内存防改保护。另外为了解决Meltdown,Linux内核引入的KPTI以及ARM64系统上引入的Syscall Table Wrapper (4.19之后)都给Syscall Table HOOK带来挑战
需要注意的事,Linux内核中会存存多个syscall table,比如X64为了支持32位的x86程序会保留32位移程序的syscall table,另外Linux还有一项特殊的syscall tabke专为X64_32程序使用,X64_32类别程序内部直接使用的是64位的syscall id
3 代码Inline HOOK
代码Inline hook做为针对Syscall Table hook的延伸,但同样亦面临可信度量及内存保护的挑战
4 ftrace & kprobe机制
Linux内核为了提升在线解决问题的效率,实现了动态插桩机制,可以在内核”任意函数“点进行插桩,用以输出参数及函数变量,甚至改变函数执行流程。当然这种技术主要是用于排错应急,如果用于生产环境还需要更多的技术验证。这种技术的明显限制是针对同一个函数只能进行一个插桩,并且插桩行为是能够被可信计算发现的,亦可被阻止。
5 中断向量HOOK
X86体系下应用层通过syscall或sysenter切换至内核模式,ARM体系下通过SVC或SWI可以切换至内核,切换模式的指令最终是通过自陷阱(Trap)触使CPU进行模式切换,并自动执行事先指定位置处的代码。我们可以通过接管中断向量的方式实现Syscall的HOOK,但是此方式会有以下难点:
- 入口片要处理相当多的初始化工作,只能用汇编级指令实现
- 针对开启KPTI的内核,要保证入口代码在Shadow内核空间
6 GhostHOOK
依赖于硬件PMU的HOOK实现方式,Intel及AMD在各自的CPU上均实现了类似的机制,ARM架构上的CP15协处理器亦实现了类似的功能,但相对Intel的PMU在功能上要弱一些,但均可以达成类似的HOOK效果。
当然通过高精度时间中断亦可达成同样效果
7 内核hot patch
ARM64平台可利用 aarch64_insn_patch_text 进行函数层的hook。