# khm **Repository Path**: OpenCloudOS/khm ## Basic Information - **Project Name**: khm - **Description**: 内核加固/漏洞防御模块(Kernel Harden Module),自研开源的独立内核安全模块,用于对内核漏洞利用路径进行检测、自动阻断的防御目的 - **Primary Language**: Unknown - **License**: LGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-03-22 - **Last Updated**: 2024-03-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 简介 系统安全加固和防护,针对正常程序(特指用户态应用),可以快速更新版本来对新增漏洞快速响应。然而针对Linux内核漏洞,受制于业务复杂性,以及系统稳定性考量,运维很难实现批量、即时的内核更新重启;此外,面对发行版碎片化以及部分系统官方停止支持的问题,作为基础设施的系统特别是内核,消除漏洞的成本过高,漏洞缓解和漏洞利用防御,是很有必要的最后防线。 为Linux内核提供基于强制检查手段的安全方案,主要目标为: - 针对Linux内核漏洞常见利用方式和链路,如ROP,以通用方式实现阻断。这对于0day以及无法更新内核重启的Nday漏洞,提供阻断漏洞利用的热补丁防护; - 针对特定漏洞,以及漏洞利用中的关键数据(如漏洞利用中的污染,以及信息泄漏漏洞的地址相关信息),提供篡改检查以及混淆脱敏的防护; - 针对某些系统提供的安全机制,如SMEP,实现弱等价的检查。这对于没有开启这类机制的系统,提供近似等价的攻击防护。 # 技术原理 类似Linux热补丁的方式,防护方案以内核模块形式加载代码到内核态,借助ftrace,hook特定内核函数,在hook中针对不同防护逻辑执行检查,并在确认有漏洞利用发生的现场实行阻断。 借助ftrace的函数粒度插装,以及控制流劫持能力,可以在特定目标函数调用点,用以下方法服务于检查/阻断目的: - 控制流完备性, + 获取直接调用者,判断是否合法,如是否是`call`指令,调用者是否来自内核空间代码; + 获取上游调用链,对特定函数,判断调用链是否属于合法组合,实现定向CFI相似检查; - 数据流完备性, + 参数和关键数据结构校验,针对典型漏洞利用常用的参数、指针的构造和篡改,在入口做检查; + 敏感信息脱敏,在判断调用上下文非可信时,对某些会返回内核信息到用户态的函数,劫持函数或覆写返回值。 # 防御关键点 根据[cvedetails](https://www.cvedetails.com/product/47/Linux-Linux-Kernel.html?vendor_id=33)的统计口径,Linux内核注册有CVE编号的漏洞按标签划分,忽略DoS类型,分布集中于溢出、内存破坏、信息获取(泄漏),bypass、代码执行、提权,而标签有重叠性,如溢出类型又可根据适用性划入提权。 相比用户态程序漏洞的用途场景,内核漏洞的利用目的性单一,主要就是提权,以及服务于提权的内核信息泄漏、缓解机制绕过,所以防御的要点,应主要是在利用方式上,做通用的检查阻断;其次针对不同的上游行为,再做补充检查。 ## 常规漏洞利用技术防御 ### cred 提权关键为在内核态借root权限,修改进程task_struct对应的cred结构体,来保证返回用户态后保有root权限。 绝大多数的提权,是通过ret2user或ROP拼接,最终执行`commit_creds(prepare_kernel_cred(0))`,而后返回用户态。这两者可以通过下述对应防护方法,在`commit_creds`函数入口检查。 在特殊的[研究](https://www.povcfe.site/posts/kernel_rw1/)中,也存在借助任意地址写或竞争条件漏洞,直接定位并覆盖进程的cred数据实现提权。这种也许可以通过插装某些频繁执行的内核函数,判断是否在某两次之间未执行`commit_creds`可`cred`却发生改变,并还原备份的`cred`;但这也无法做到通用的、即时的阻断。 ### ret2usr 最朴素简单的内核漏洞利用方式是从内核态以root权限,直接访问执行内核态代码,从而修改进程权限后退回用户进程。 在未开启SMEP、KPTI防护机器上,可以劫持内核态下`$rip`到用户态exp的代码,在其中执行如`commit_creds`。这种情况下,可以对这种可能被从用户态执行的内核函数,检查是否有合法的调用指令地址可以判定。 ### ROP ROP本身也可归类为缓解机制绕过,不过因其通用化,需要设计通用解决方式。 针对ROP防御的难点在于通过拼凑gadget指令执行,先天绕过函数入口检查。例如,常被用来从内核态完成提权后返回用户态的函数`swapgs_restore_regs_and_return_to_usermode`,ROP跳转到函数跳过头部堆栈平衡指令之后的位置,此时无法检查。 因此,防御仅能在某些漏洞的ROP链中,会直接跳转到特定函数入口执行的情况下,针对性插装,检查其上游指令是否为合法的`call`指令。最显然的方式是提权中往往ROP直接ret到`prepare_kernel_cred`和`commit_creds`函数入口执行。而其它函数往往是特定利用方式(特别是缓解机制绕过方式)所通常用到的,例如下文提到的`call_usermodehelper`,`selinux_disable`,`orderly_poweroff`。 ### 堆喷射 堆喷射是各种内核UAF漏洞常见的利用方法,借助堆喷射可实现从UAF到提权的办法。一般有[三种利用方法](https://pkfxxxx.github.io/2020/04/01/ji-lu-san-dao-kernel-pwn/#toc-heading-4):碰撞cred的chunk;泄漏内核地址结合ROP;任意地址读写。 堆喷射的三个原则:需要使用低权限可调用的系统调用;内核堆块弹性大小;堆块中可控负载。TO BE CONTINUED ### UAF & ptmx UAF是对堆内存释放后指针未置为NULL,这样在重新申请相同大小堆块时,slub机制会将刚刚释放的内存分配给新对象,从而通过前一个释放指针操作后面新申请堆内存的方式。这在内核当中是很常见的类型。 在较旧版本,UAF可很容易被用于提权,只需要通过UAF申请并释放一个`cred`大小堆块,之后`fork`进程,新进程分配的`cred`会落入上述指针的目标,可直接改写。但是此后`cred`一般单独分配不再由slub管理,这种方法失效。 当前[流行的UAF利用方式](https://cc-sir.github.io/2019/08/24/Linux-kernel3/)是通过用户态可打开的设备/dev/ptmx,打开该设备会分配`tty_struct`结构内存,其中`tty_operations`成员指针指向一系列函数指针,其中的`write`指针在向设备写操作时被调用。因此在利用UAF漏洞控制了打开该设备分配的`tty_struct`结构体堆块后,就可以控制上述`write`指针,指向shellcode实现ret2usr,或指向ROP链。 对此种方法进行防御比较困难的点是,无法简单禁用ptmx设备,因为sshd、telnetd都依赖于虚拟终端设备pty,而pty是通过ptmx作为master操作分配的slave。此外,UAF漏洞利用ptmx是通过直接修改用户态可间接调用的函数指针,无法通过控制流完整性检测;而该指针是打开设备时在堆上分配的,不是进程相关唯一变量,也无法借助数据完整性检测。TODO:调试得到此时函数指针在内核中的调用栈,是否可以根据该指针的调用者做检查,判断函数指针是否指向疑似ROP或JOP gadget。 ## 漏洞缓解机制绕过技术防御 ### 禁用SMEP SMEP在系统中由CR4寄存器的第20bit决定。在较新内核版本,CR4的第20、21bit在系统启动后被锁定,即便通过任何方式覆盖,也会自动重置为1,无法绕过。而在较低版本,通过覆盖bit位来禁用SMEP可行。这一般是通过ROP调用到`native_write_cr4()`函数实现,因此对该函数hook检查即可。 ### ret2dir 作为ret2usr变体,ret2dir将用户态代码与内核地址别名映射,从而以内核地址执行用户态代码,规避SMEP/KPTI缓解机制。 这种方式,暂时没有在启动时检查某个内核地址是否与用户态内存有别名映射的关系的方法检查防御。待验证的两个思路从该方法的两种获取physmap基址的操作进行阻断:一种是阻断/proc/\/pagemap信息向用户态拷贝;一种是监控可疑的通过大量`mmap`堆喷行为。 ### VDSO劫持 与ret2dir很类似,VDSO是内核态与用户态都映射的内存空间,特定内核版本有[典型思路](https://bbs.pediy.com/thread-225488.htm#msg_header_h2_1),利用内核态任意写漏洞将提权代码在内核态写入、在用户态执行。 ### `modprobe_path`覆盖提权 在有任意内存地址写漏洞时,一个常用提权[方法](https://toutiao.io/posts/na4ko0s/preview)为,调用`modprobe_path`指向的用户态处理函数,因此覆盖该变量就可以root权限执行任意程序。 对此种类型漏洞防御,难以在变量覆盖的层面做轮询判断;退而求其次,需要在攻击者修改变量之后,主动回调触发对指定程序调用的路径上,做匹配阻断。其中一个路径为,构造非法ELF文件执行,触发错误并由内核调用`modprobe_path`注册程序,考虑到这种由非法`execve`目标格式触发回调的调用路径为 `(1)do_execve()(2)do_execveat_common()(3)bprm_execve()(4)exec_binprm()(5)search_binary_handler()(6)request_module()(7)call_usermodehelper()` 可以hook `call_usermodehelper`,回溯调用栈符合以上组合,就跳过对用户程序的启动,从而以废除对未知格式文件加载错误处置为代价,防御提权。 而[另一种攻击向量](https://www.qualys.com/2021/07/20/cve-2021-33909/sequoia-local-privilege-escalation-linux.txt)提出可以创建netlink socket来直接调用`request_module()`,这是一个更短的函数调用链,无法靠牺牲netlink socket的方式来阻断,此时需要 ## 内核信息泄漏 仍在分析完善中。内核信息泄漏往往仅在漏洞利用中体现,而Linux社区对这类问题认定为漏洞的积极性不高,所以需要持续搜集更大量真实exp来完善。 # 防御覆盖度 针对Linux内核漏洞与防御的覆盖度,选取外部广泛索引的[Linux Kernel Defence Map](https://github.com/a13xp0p0v/linux-kernel-defence-map)作为基准,归纳了所有已知的脆弱性和漏洞利用方法。其中,我们的缓解机制解决的有: - 控制流劫持技术,包括ret2dir,返回地址覆盖,ret2usr,以及ret2dir、ROP、ret2usr三者组合的方案; - 可写可执行内存和对象误用; - 信息泄漏与其它漏洞组合情况下实现的漏洞利用。 而本方案不会尝试解决的设计相关的漏洞或利用方式包括: - 竞争条件的通用缓解,如脏牛漏洞; - 针对bpf的JIT机制误用的特殊技术; - CPU层面侧信道攻击,如Spectre; - 其它仅涉及成因,无明确对应的利用技术的漏洞,如类型混淆、死锁与无限循环、内存泄漏、整型溢出、资源消耗。 根据前面引用的Linux内核漏洞统计,去除拒绝服务类型外,可用于利用的漏洞分布在溢出、机制绕过、提权、信息获取、代码执行、内存破坏等主要类型。因为上述不覆盖的类型主要是个例而非通用漏洞与利用类型,且考虑到无法防御的特殊漏洞利用方式(如ret2dir的部分利用方式),往往根据系统环境,仅有一定概率可利用成功,而没有通用的利用手法。因此本方案设计的漏洞利用防御覆盖度,可达到对80%以上漏洞利用攻击向量的防御。