独自快乐|怎样Hack Linux的内核符号?( 二 )


独自快乐|怎样Hack Linux的内核符号?普通需求到这里就完事了 , 但是针对客户的特殊场景 , 稍微思考一下就会发现有很大缺陷 。 假如修复补丁中一共涉及到了数百个未导出的函数 , 我们则要在修复代码中把所有使用到这些函数的地方全部修改成函数指针调用的形式 , 工作量增加了不少 。 最简单的解决办法是内核加载修复模块时 , 单独走Kallsyms解析模块符号 , 而绕过export_symbols这个符号子集(前提是不引入新的内核安全风险) 。
【独自快乐|怎样Hack Linux的内核符号?】Linux内核模块的加载过程其实跟可执行程序加载动态链接库的过程是一样的 。 举个简单例子 , 在printf(“hello world”)中 , 我们其实并没有实现printf(由puts函数封装而来) 。 它实际是由Libc库实现 。 当我们运行HelloWorld程序的时候 , 操作系统会解析程序符号 , 载入依赖的动态链接库(每次加载的基址可能不同) , 计算重定位符号地址 , 并把地址填回HelloWorld程序中 。 我们可以通过下图过程来验证:
独自快乐|怎样Hack Linux的内核符号?对于Linux内核模块而言 , 它本质上也是动态链接库 , 因此加载模块时必然存在解析符号地址的函数 。 于是我们的思路是 , 动态拦截该函数 , 重定向到我们的替换函数中 , 并在替换函数中添加Kallsyms查找符号地址的逻辑即可:左图为我们的替换函数 , 右图为内核原始函数 。 这样达到的效果是 , 我们可以在CVE修复代码中直接使用诸如d_absolute_path这样的未导出函数 , 而不用做任何函数指针形式的改造 , 便于漏洞修复过程的自动化 。
独自快乐|怎样Hack Linux的内核符号?可能会有同学感兴趣我们是如何实现内核函数拦截的 , 即如何从find_symbol_in_section跳转到hook_find_symbol_in_section , 这里以ARM64架构CPU为例简单说明 。 我们在内核的find_symbol_in_section函数开头插入了下图所示的汇编指令(以二进制形式修改内核代码区) 。 这里借用了x0寄存器作远距离跳转(从内核跳转到内核模块) 。 由于无条件跳转不应该产生任何副作用(即栈帧和寄存器不能改变) , 因此我们需要先保存x0的值到栈上 , 远跳转后再恢复x0内容 。
ldr指令从.addr(low)和.addr(high)中把跳板函数地址装载进x0 , 注意到ARM64的地址长度为64位 , 而ARM64的指令长度为32位 , 因此跳板函数地址被折成低32位和高32位 。 进入跳板函数后先恢复x0寄存器值 , 再做近距离跳转(内核模块内部跳转) , 注意前图hook_find_symbol_in_section函数末尾有一行HOOK_FUNC_TEMPLATE(find_symbol_in_section) , 即为宏定义的find_symbol_in_section的跳板函数 。 这样经过连续无条件跳转后 , 执行流被拦截到我们的HOOK函数中 。
独自快乐|怎样Hack Linux的内核符号?此外顺便多提一下 , 上述使用Inline Hook技术的拦截方式跟CPU架构是强相关的 , 如果想实现ARM32或x86架构的函数拦截 , 则需要分别单独实现 。
文/ThoughtWorks刘涛


推荐阅读