linux内核热修复,揭露内核黑科技 - 热补丁技术真容
原创 理查德 Linux阅码场 6月12日
内核热补丁是一种无需重启操作系统,动态为内核打补丁的技术。系统管理员基于该技术,可以在不重启系统的情况下,修复内核BUG或安全漏洞,可以在最大程度上减少系统宕机时间,增加系统的可用性。
一直很好奇内核热补丁这个黑科技,今天终于可以揭露它的真容了。当然这章的内容强烈依赖于前一章探秘ftrace[1]。有需要的小伙伴请自取。
从一个例子开始
作为一个小白,当然是从一个例子开始入手会比较简单。感谢内核社区开发着贴心的服务,在内核代码中,就有热补丁的例子在samples/livepatch目录下。
我们来看一个非常简单的例子,因为太简单了,我干脆就把整个代码都贴上来了。
我想,有一些内核开发经验的小伙伴,从这个例子中就可以猜出这个代码的作用。
将函数cmdline_proc_show替换成livepatch_cmdline_proc_show
怎么样,是不是炒鸡简单?
来点难的
上面的代码实在是太没有难度了,让我们来点挑战。看看这个klp_enable_patch究竟做了点什么。
怎么样,是不是有点傻眼了?这么多调用都是点啥?别急,其实这么多调用大多是花架子。如果你了解了klp_patch这个数据结构,我想一切都迎刃而解了。
klp_patch的数据结构
所以说大学时候学习算法和数据结构是非常有道理的,只可惜当年我压根就没有好好学习,以至于工作后不得不拼命补课。瞧,这时候又能用上了。
想要了解上面列出的klp_enable_patch这个函数的逻辑,还是要从klp_patch这个结构体入手。
大家可以对照这例子代码中的klp_patch和这个图来帮助理解。
这个klp_patch就好像是一个二维数组
第一维是klp_object
第二维是klp_func
最后落实到klp_func标注了要替换的目标函数和替换成的新的函数。
知道了这个后,再回过去看刚才那一坨初始化的代码是不是会简单点?其实就是做了几个循环,把这个二维数组上所有的klp_object和klp_patch都初始化好。所有的初始化,大部分是创建对应的kobj,这样在/sys/kernel/livepatch/目录下就能控制每个热补丁点了。
真正的干货
到此为止,看了半天其实都没有看到热不定究竟是怎么打到内核代码上的。别急,小编这就给您娓娓道来。
在前面初始化的代码中,大家有没有看到一个函数–klp_patch_func?这个函数会对每个klp_func数据执行一遍。对了,魔鬼就在这里。
这几个可以说都是重量级的选手,让我慢慢给您一一讲解。
klp_get_ftrace_location
这个函数呢,就是要给出被替换的函数地址。首先我们在定义中并没有给出这个old_func的地址,所以第一步是要算出这个old_func。这部分工作在函数klp_init_object_loaded中通过klp_find_object_symbol查找symbol来得到。
ops->fops.func = klp_ftrace_handler
这是什么呢?对了,如果你对ftrace还有印象,这就是我们会替换掉ftrace探针的那个函数。也就是说,当我们的想要修改的函数被执行到时,这个klp_ftrace_handler就会被调用起来干活了。
ftrace_set_filter_ip
在探秘ftrace中,我们并没有展开这个ftrace_ops结构体。那这里我们就来展开看一下。
每个ftrace_ops上都有两个哈希表,还记得我们操作ftrace时候有两个文件 set_ftrace_filter / set_ftrace_notrace么?这两个文件分别用来控制我们想跟踪那个函数和不想跟踪那个函数。这两个集合在代码中就对应了ftrace_ops中的两个哈希表 filter_hash / notrace_hash。
所以 ftrace_set_filter_ip 就是用来将我们想要补丁的函数加到这个哈希表上的。
register_ftrace_function
这个函数的功效在探秘ftrace中已经描述过了一部分,这里我们将从另一个角度再次阐述。
register_ftrace_function函数的功效之一是将ftrace_ops结构体添加到全局链表ftrace_ops_list上,这么做有什么用呢?我们来看一下被ftrace插入到代码中的函数ftrace_ops_list_func。
可以看到,每一个被ftrace改变的函数,如果在有多个ftrace_ops的情况下,会通过ftrace_ops_test()来判断当前函数是否符合这个ftrace_ops。如果符合才会执行op->func。(注意,这个func就是刚才设置的klp_ftrace_handler了。
而这个ftrace_ops_test()是怎么做判断的呢?对了,我想你已经猜到了,咱不是有两个哈希表么?
惊人一跃
到此为止,我们还是围绕着热补丁怎么利用ftrace的框架,让自己在特定的探针上执行,还没有真正看到所谓的补丁是怎么打上去的。是时候来揭开这层面纱了。
通过上述的操作,klp成功的在某个探针上嵌入了函数klp_ftrace_handler。那就看看这个函数吧。
1. klp_ftrace_handler(ip, parent_ip, fops, regs)
2. klp_arch_set_pc(regs, func->new_func)
3. regs->ip = ip;
怎么样,是不是有点吃惊,所谓的热补丁就是这么一个语句?理论上讲到这里,意思上也明白了,但是我依然想要弄清楚这个究竟是怎么一回事儿。
这一切还是要从ftrace的探针开始说起。
因为klp在设置ftrace_ops时添加了FTRACE_OPS_FL_SAVE_REGS,所以对应的探针是ftrace_reg_caller。经过一番刨根问底,终于发现了秘密。
在探针执行ftrace_ops_list_func的前,会将调用探针的rip保存到堆栈上的regs参数中。然后在返回探针前,将rges->ip上的内容再恢复到函数返回地址上。此时如果有klp的探针函数,那么这个值就改变为了我们想改变成的函数了。
怎么样,原来黑科技是这么玩的!
这事儿有点抽象,让我画一个简易的堆栈示意一下。
一切的秘密都在这个堆栈上的return address里了。
到这里我才反应过来,原来黑科技就是***用的科技啊 :)
补充知识 – 函数返回地址
上面的这个黑科技运用到了一个x86架构下,如何保存函数返回是运行的地址的原理。也就是指令callq/retq是如何改变堆栈的。
那先说一下原理:
callq指令在跳转到目标代码前,会将自身的下一条指令的地址放到堆栈上。retq执行返回时,会从堆栈上取出目标地址然后跳转到那里。
这么说有点抽象了,咱们可以用gdb做一个简单的实验。
实验代码
一个再简单不过的add函数。
1. #include
2.
3. int add(int a, int b)
4. {
5. return a + b;
6. }
7.
8. int main()
9. {
10. int a = 3;
11. a = a + 3;
12. add(a, 2);
13. return 0;
14. }
验证返回地址在堆栈上
使用gdb在add返回前停住,然后用下面的指令查看状态。
1. (gdb) disassemble
2. Dump of assembler code for function add:
3. 0x00000000004004ed : push %rbp
4. 0x00000000004004ee : mov %rsp,%rbp
5. 0x00000000004004f1 : mov %edi,-0x4(%rbp)
6. 0x00000000004004f4 : mov %esi,-0x8(%rbp)
7. 0x00000000004004f7 : mov -0x8(%rbp),%eax
8. 0x00000000004004fa : mov -0x4(%rbp),%edx
9. 0x00000000004004fd : add %edx,%eax
10. 0x00000000004004ff : pop %rbp
11. => 0x0000000000400500 : retq
12. End of assembler dump.
13. (gdb) info registers rsp
14. rsp 0x7fffffffe2e8 0x7fffffffe2e8
15. (gdb) x/1xw 0x7fffffffe2e8
16. 0x7fffffffe2e8: 0x00400523
首先我们看到在执行retq前,堆栈上的内容是0x00400523。
接着我们再执行一次stepi。
1. (gdb) stepi
2. main () at main.c:13
3. 13 return 0;
4. (gdb) info registers rsp
5. rsp 0x7fffffffe2f0 0x7fffffffe2f0
6. (gdb) info registers rip
7. rip 0x400523 0x400523
此时我们看到堆栈变化了,而且rip的值和刚才堆栈上的值是一样的。
然后再反汇编一下,看到此时正要执行的指令就是callq后面的一条指令。
1. (gdb) disassemble
2. Dump of assembler code for function main:
3. 0x0000000000400501 : push %rbp
4. 0x0000000000400502 : mov %rsp,%rbp
5. 0x0000000000400505 : sub $0x10,%rsp
6. 0x0000000000400509 : movl $0x3,-0x4(%rbp)
7. 0x0000000000400510 : addl $0x3,-0x4(%rbp)
8. 0x0000000000400514 : mov -0x4(%rbp),%eax
9. 0x0000000000400517 : mov $0x2,%esi
10. 0x000000000040051c : mov %eax,%edi
11. 0x000000000040051e : callq 0x4004ed
12. => 0x0000000000400523 : mov $0x0,%eax
13. 0x0000000000400528 : leaveq
14. 0x0000000000400529 : retq
15. End of assembler dump.
修改返回地址
接下来我们还能模拟热补丁,来修改这个返回值。(当然比较简陋些。)
我们在add函数执行retq前停住,用gdb改变堆栈上的值,让他指向mov的下一条指令leaveq。
1. (gdb) disassemble
2. Dump of assembler code for function add:
3. 0x00000000004004ed : push %rbp
4. 0x00000000004004ee : mov %rsp,%rbp
5. 0x00000000004004f1 : mov %edi,-0x4(%rbp)
6. 0x00000000004004f4 : mov %esi,-0x8(%rbp)
7. 0x00000000004004f7 : mov -0x8(%rbp),%eax
8. 0x00000000004004fa : mov -0x4(%rbp),%edx
9. 0x00000000004004fd : add %edx,%eax
10. 0x00000000004004ff : pop %rbp
11. => 0x0000000000400500 : retq
12. End of assembler dump.
13. (gdb) info registers rsp
14. rsp 0x7fffffffe2e8 0x7fffffffe2e8
15. (gdb) x/1xw 0x7fffffffe2e8
16. 0x7fffffffe2e8: 0x00400523
17. (gdb) set *((int *) 0x7fffffffe2e8) = 0x00400528
18. (gdb) x/1xw 0x7fffffffe2e8
19. 0x7fffffffe2e8: 0x00400528
然后我们再执行stepi
1. (gdb) stepi
2. main () at main.c:14
3. 14 }
4. (gdb) info registers rip
5. rip 0x400528 0x400528
6. (gdb) disassemble
7. Dump of assembler code for function main:
8. 0x0000000000400501 : push %rbp
9. 0x0000000000400502 : mov %rsp,%rbp
10. 0x0000000000400505 : sub $0x10,%rsp
11. 0x0000000000400509 : movl $0x3,-0x4(%rbp)
12. 0x0000000000400510 : addl $0x3,-0x4(%rbp)
13. 0x0000000000400514 : mov -0x4(%rbp),%eax
14. 0x0000000000400517 : mov $0x2,%esi
15. 0x000000000040051c : mov %eax,%edi
16. 0x000000000040051e : callq 0x4004ed
17. 0x0000000000400523 : mov $0x0,%eax
18. => 0x0000000000400528 : leaveq
19. 0x0000000000400529 : retq
20. End of assembler dump.
瞧,这下是不是直接走到了leaveq,而不是刚才的mov?我们轻松的黑了一把。
好了,到这里就真的结束了,希望大家有所收获。
参考链接
(END)
linux内核热修复,揭露内核黑科技 - 热补丁技术真容相关推荐
- Android热修复之 阿里开源的热补丁
1.概述 上一期讲到Android热修复之 - 收集崩溃信息上传至服务器,我们获取到用户手中上线的崩溃信息上传到服务器后该怎么办?如果直接发布版本要用户去下载肯定不乐意.这一期我们来看一下怎么去打 ...
- Android热修复(1):热修复的介绍和原理解析
一.热修复的产生概述 在开发中我们会遇到如下的情况: 1.刚发布的版本出现了严重的bug,这就需要去解决bug.测试并打渠道包在各个应用市场上重新发布,这会耗费大量的人力物力,代价会比较大. 2.已经 ...
- 优酷播放黑科技 | 自由视角技术的全链路策略与落地实践
作者:李晓阳(苏铭) 在<优酷播放黑科技 | 自由视角技术体验优化实践>中我们提出对自由视角观影体验做了很多优化,为何需要做如此多的体验优化,下面将一一解答. 随着5G时代的到来,视频 ...
- 无人零售的黑科技:RFID技术
无人零售的黑科技:RFID技术 无人零售的黑科技:RFID技术 说起最近的热门话题,"无人零售商店"当属其一.自去年底,亚马逊推出第一家无人实体超市Amazon Go,到阿里.京东 ...
- 公众号点击图片后变化的互动效果怎么搞?教你用黑科技SVG排版技术提高图文互动性
公众号点击图片后变化的互动效果怎么搞?教你用黑科技SVG排版技术提高图文互动性 今天看到央视新闻发送的一则关于'武汉解封'的软文,发现中间点击图片显示的效果挺有意思,随即网上查找了一下原来使用SVG互 ...
- Linux系统那些硬核的黑科技技术
很早之前,我在极客星球提出一个有趣的问题: 这个问题涉及到类似上网行为管理,但大多数行为管理都是在windows端, 我们这里讨论微信和邮件运行在Linux系统,以后很有可能我们的办公都是在国产化操作 ...
- 优酷播放黑科技 | 自由视角技术体验优化实践
作者:邓小龙(白展) "本文为<优酷播放黑科技>系列文章第一篇<自由视角技术体验优化实践>,之后我们会陆续上线<基于WebRTC实现的直播"云多视角& ...
- Android热修复之 - 阿里开源的热补丁
1.1 基本介绍 我们先去github上面了解它https://github.com/alibaba/AndFix 这里就有一个概念那就AndFix.apatch补丁用来修复方法,接下来我们看看到底是 ...
- android热修复原理底层替换,Android 热修复 - 各框架原理学习及对比
写在开头 从15年开始各技术大佬们开始研究热修复技术,并陆续开源了许多的热修复框架.如 Jasonross 的 Nuwa,美团的 Robust,阿里的 Andfix,腾讯的 Tinker 等等...均 ...
最新文章
- secureCRt中文乱码问题
- 一天一个设计模式(一) - 总体概述
- 龙芯.NET正式发布 稳步推进生态建设
- 【数据结构与算法】二叉查找树的Java实现
- matlab抖g是什么,MATLAB中dither抖动函数的用法
- opencv 有无判断 模板匹配_opencv模板匹配
- java程序员期望薪资_11月程序员平均薪资达14327元,薪资最高的居然不是JAVA?
- python中的is和==
- 法拉科机器人编程软件_【新提醒】FANUC发那科机器人离线编程与设计模拟仿真软件Roboguide 步骤、功能与技巧...
- php 连接芒果数据库,芒果数据库配置文件
- 【干货分享】Color Fonts是什么?多彩字体详解
- bzoj 2101: [Usaco2010 Dec]Treasure Chest 藏宝箱【区间dp】
- Kaggle案例泰坦尼克号问题
- 如何快速开设海外银行账户
- 光驱读盘能力差的解决方法
- 微信小程序模拟车位选择功能(简陋版本)
- Streaming System 第一章:Streaming 101
- 高效| 工厂如何做好设备管理工作?看这篇就够了!
- 浏览器底层,内存分配,运行机制
- HTTPS下导出Excel, ie浏览器报“IE 无法下载 无法打开该站点” 解决办法
热门文章
- Vs2010架构设计-层图(Layer Diagram)
- asp.net下的“Eval()、XPath() 和 Bind() 这类数据绑定方法只能在数据绑定控件的上下文中使用。”错误的一个可能的成因...
- 怎么使用Nginx服务开启HTTPS
- vue实现的tabs标签组件
- Linux驱动(8)--内核编译与配置
- android mkv 字幕乱码,Android 西班牙语字幕乱码 字符编码
- mysql zpi版的如何配置_Mysql zip版 安装配置
- node.js go java_ABAP,Java, nodejs和go语言的web server编程
- 计算机更新80072f76,windows update 80072f76错误
- Windows平台下Git服务器搭建