Linux内核监控

本文将简单罗列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\n"
#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)的相关回调我们即可将所有的信息串联起来,有了这个信息我们就能够做进详细的检测与判定,以上就是我们解决问题的主要思路。

本项目主要以天狗R及天狗S为基础,但二者所依赖的监控点数量已经很多,我们会根据重要程度及回调类型在两个批次中分别实现,进程、文件及权限相关的核心操作列在第一批次,第一批为高优先级实现,具体涉及哪些监控点本文后面有详细介绍。

2 Syscall_Table HOOK

这种方式就类似于Windows SSDT HOOK,但Linux内核并没有类似于Windows内核的PatchGuard (KPP)的防护机制,可以说内核本身是不设防的。但要注意的一点就是:为了缓解Meltdown漏洞Linux内核引入的KPTI以及ARM64系统上引入的Syscall Table Wrapper (4.19之后) 都给Syscall Table HOOK带来挑战,应对此场景还需要特殊处理。

此外也确实有例外场景存在,如国产化PKS或龙芯理台实现了可信计算,可信计算会对内核的内存镜像及syscall table有进行安全度量以及内存防改保护。

3 代码Inline HOOK

代码Inline hook做为针对Syscall Table hook的延伸,但要针对不同的CPU架构及指令集做不同处理。Inline hook的方式同样面临可信度量及内存保护的挑战。

4 ftrace & kprobe机制

Linux内核为了提升在线解决问题的效率,实现了动态插桩机制,可以在内核”任意函数“点进行插桩,用以输出参数及函数变量,甚至改变函数执行流程。当然这种技术主要是用于排错应急,如果用于生产环境还需要更多的技术验证。这种技术的明显限制是针对同一个函数只能进行一个插桩,并且插桩行为是能够被可信计算发现的,亦可被阻止。

5 中断向量HOOK

X86体系下应用层通过syscall或sysenter切换至内核模式,ARM体系下通过SVC或SWI可以切换至内核,切换模式的指令最终是通过自陷阱(Trap)触使CPU进行模式切换,并自动执行事先指定位置处的代码。我们可以通过接管中断向量的方式实现Syscall的HOOK,但是此方式会有以下难点:

  1. 入口片要处理相当多的初始化工作,只能用汇编级指令实现
  2. 针对开启KPTI的内核,要保证入口代码在Shadow内核空间

6 GhostHOOK

这种其实是无HOOK方式,即采用高精度时钏或于硬件PMU以微妙甚至纳秒级间隔来执行检测代码,可通过劫持当前CPU的执行流程的方式实现控制流的转换,即内核HOOK。Intel及AMD在各自的CPU上均实现了类似的机制,ARM架构上的CP15协处理器亦实现了类似的功能,但相对Intel的PMU在功能上要弱一些,但均可以达成类似的HOOK效果。

7 内核热补丁

此功能主要应用服务Rebootless的需求而开发的,目前已有多个方案实现,不同的发行版亦有不同的选择及集成,如Ksplice, Kgraft, Kpatch, Livepatch, KernelCare等方案,有的是通过inline hook的方式实现,有的是通过篡改内核vmlinux镜像的elf导出表实现的。更多内容信息可参见History of Linux Kernel Live Patching

Continue reading » · Rating: · Written on: 02-02-20 · No Comments »