安全逆向分析实战
前言
本文记录了对某发行版 Linux 中一个安全模块(LSM)的逆向过程,该 LSM 对系统中待运行的程序进行安全校验,数据流穿越内核态与用户态,涉及系统内核及系统服务。此 LSM 对系统安全性的增强效果明显,其设计思路值得防守方研究学习,可于个人终端或服务器安全防护中应用。特此对逆向内容记录,希望能为读者在终端防护方面拓宽思路,同时欢迎感兴趣的师傅们交流学习。
一. LSM 框架简介
Linux 安全模块(Linux Security Module,LSM)框架是 Linux 操作系统内核提供的一种安全机制,它通过内核扩展实现 hook 函数以完成多种安全检查,通常用于强制访问控制(Mandatory Access Control)。虽然被称作“模块”,但不同于 LKM,这些扩展并不是可加载的内核模块,而是和内核代码一起编译在内核文件(vmlinuz)中。可以通过如下命令查看本机启用的 LSM,cat /sys/kernel/security/lsm。常见的 LSM 包括 SELinux、Yama 等。
LSM 框架的 hook 点设置于内核访问关键对象前,通过调用 LSM 中实现的 hook 函数,判断是否可以进行访问。如果有多个 LSM,则会根据初始化的顺序依次判断,都允许才能进行访问。上述关键对象包括程序、进程、套接字、文件系统等,可在/usr/src/linux-headers-YOURVERSION/include/linux/lsm_hooks.h 中查看详细的 hook 说明。
这里以程序启动过程为例,简单说明 LSM 工作的机制。当通过 execve 系统调用执行一个新程序时,内核最终会执行到__do_execve_file 函数完成相关工作,在这里会调用 prepare_binprm 函数填充 struct linux_binprm,填充前会调用 security_bprm_set_creds 进行安全检查。security_bprm_set_creds 就是 LSM 框架提供的 hook,它会依次调用注册在这个钩子上的回调函数,完成安全检查。此流程上相关代码以及此钩子的说明如下。
LSM 开发时,通过如下函数定义安全模块的 hook 函数,逆向时通过此函数可快速定位具体的 LSM 以及相关回调函数。
2021最新整理网络安全/渗透测试/安全学习/100份src技术文档(全套视频、CTF、大厂面经、精品手册、必备工具包、路线)一>获取<一
二. 安全模块逆向分析
2.1 分析准备
本次分析的对象为某发行版 Linux,此系统提供了可执行文件的签名校验功能,仅有签名的程序可以被执行,本次逆向的目标就是试图还原校验功能的框架和逻辑。由于此安全检查的存在,提权、后门等程序因为此机制的存在而无法直接运行,从某种程度提高了操作系统的安全性。编译一个 hello world 程序,运行,会提示无法通过系统安全校验目前不能运行。strace 跟一下,看到在 execve 时就被干掉了(如下图),可以肯定是在内核中完成这个动作的,大概率是通过内核扩展实现。
下面尝试定位此内核扩展。lsmod 看一眼,没有发现明显相关的。查看此系统的所有 LSM,有一个名为 elfverify 的模块,通过此命名可以推断,就是此模块完成了程序的校验功能。
由于 LSM 是被编译在内核中的,因此接下来需要获取到此系统的内核文件。系统的内核文件通常可以在/boot 目录中找到,其中 vmlinuz 的文件是压缩后的内核,可以通过 extract-vmlinux 工具提取未压缩的内核文件 vmlinux 进行分析。然而提取后的文件是没有符号的,符号信息存储在同目录下 System.map 文件中。这里推荐使用vmlinux-to-elf这个工具完成提取与符号修复工作,通过此工具可输出一个带符号的 ELF 文件,方便逆向分析。
2.2 LSM 分析
反编译查看恢复后的内核,在 elfverify 的初始化函数 elfverify_init()中,security_add_hooks()的第二个参数 count 为 5,即此 LSM 一共注册了 5 个 hook 函数;跟踪一下第一个参数 security_hook_list,可得到所有实现的回调函数,总结如下。
可以看出此 LSM 还对套接字、设备挂载进行了安全校验,这里专注于程序运行的校验,锁定 hook_bprm_set_creds 进行进一步分析。
跟进 hook_bprm_set_creds,该函数首先对一些参数进行检查,接着判断是否开启了开发者模式(判断依据是某个文件的内容),如果符合条件则放行(返回 0),否则调用 access_verify 进行进一步判断。此函数的参数类型是 struct linux_binprm, 源码中此结构体被标记为__randomize_layout,这是 Linux 内核中的一项防御机制,有此标记的结构体其中的元素将作乱序排列,从而攻击者难以找到偏移具体对应的元素。因此通过静态分析,也暂时无法确定传递给 access_verify 的参数。
在 access_verify 中,会将当前进程通过 add_wait_queue 挂起等待,并将 90-pid-UNKNOWN 信息写入某个设备中,这里的 90 应该是 LSM 开发者自定义的一个“魔数”(socket 校验中是 91),而第三段的内容由于结构体的随机化也暂时无法确定(之后分析会知道其实是程序路径)。之后再从此设备中读取信息,根据内容选择是否继续执行,并从队列中移除进程。
LSM 中关于 elf 校验的流程到这个函数基本就结束了,并不包含具体的校验逻辑,只是将待校验的进程信息写入某个设备并等待结果,可见校验应该是在另一处完成。经过跟踪分析,此 LSM 注册了一个杂项设备(在 register_elf_verifier_dev()中),名为 elf_verifier。下一步找到了谁从这个设备中读取信息,大概率就能定位到完成校验的具体代码。
然而,在内核中并没有找到相关的代码,下一步得去用户态的程序找找。
2.3 安全校验逻辑分析
查看一下系统进程,发现一条程序名为*-elf-verify,其 ppid 为 1,看了下是系统服务,推断这就是处理杂项设备中数据的程序了。为验证推断,将此服务停止,运行一个自己编译的程序,待运行的进程“僵死”,推断应该是 LSM 将进程送入等待队列后,没有从杂项设备中读到校验结果,就造成了一直挂起的局面。这也确认了正是这个程序处理设备数据并给与返回结果,校验逻辑应当就在其中。
对此系统服务程序进行分析与调试,在进行 ELF 文件安全校验时,它会循环的从/dev/elf_verifier 这个设备中读取内容,读取到的内容包括 PID 和完整的程序路径,并依据此信息进行校验,其主要检查如下两点:
判断此路径的程序是否在白名单或黑名单中
在 ELF 文件头的特殊节中提取签名(PKCS7),然后进行验证(证书在系统某路径中)
上述的黑白名单位于系统/usr 目录下,仅 root 用户可编辑。而 ELF 文件头中的特殊节在其他普通的 ELF 文件中不会出现,应当是在对 ELF 文件进行签名时加上的,而将签名信息添加到文件头中又不会对程序的正常运行造成影响。如果能够顺利读取到签名信息,则调用 openssl 的相关函数进行校验。校验完成后,将检验结果写入/dev/elf_verifier,通知内核进行后续动作;同时如果校验不通过,会通过 dbus 通知 GUI 弹出提示告知用户。
至此,一次校验完成,整个签名校验功能的脉络基本摸清了。在其中还有一些细节检查,例如文件格式、ELF 文件头等等,就不一一展开介绍了。
三. 总结
该系统的可执行程序签名校验功能,通过安全模块(LSM)elfverify、系统服务联合完成,二者通过杂项设备/dev/elf_verifier 传递数据,完成了用户态和内核态的交互。在内核中,通过实现 LSM 的 security_bprm_set_creds 钩子在程序运行前获取到待运行程序的完整路径,将进程暂时挂起,同时将信息写入设备中;用户态程序从设备中读取到信息后,判断此路径程序是否在黑白名单、程序是否是经过签名的 ELF 程序,并将判断结果写入设备;内核 LSM 根据返回的结果确定是否允许执行。
整个过程涉及 LSM、设备、ELF 文件格式、签名校验等知识,有深有浅,本文记录的比较简单,欢迎感兴趣的师傅深入交流。同时,该方式利用 LSM 框架提供的钩子,对某些操作进行安全校验,同时将复杂的校验逻辑在用户态完成,通过设备完成数据传递,此类模式在终端安全防护上亦可借鉴。
评论