0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

揭露内核黑科技 - 热补丁技术真容

Linux阅码场 来源:Linuxer 2020-06-28 09:46 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

内核热补丁是一种无需重启操作系统,动态为内核打补丁的技术。系统管理员基于该技术,可以在不重启系统的情况下,修复内核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。那就看看这个函数吧。

klp_ftrace_handler(ip, parent_ip, fops, regs) klp_arch_set_pc(regs, func->new_func) 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函数。

#include int add(int a, int b){ return a + b;} int main(){ int a = 3; a = a + 3; add(a, 2); return 0;}

验证返回地址在堆栈上

使用gdb在add返回前停住,然后用下面的指令查看状态。

(gdb) disassembleDump of assembler code for function add: 0x00000000004004ed <+0>: push %rbp 0x00000000004004ee <+1>: mov %rsp,%rbp 0x00000000004004f1 <+4>: mov %edi,-0x4(%rbp) 0x00000000004004f4 <+7>: mov %esi,-0x8(%rbp) 0x00000000004004f7 <+10>: mov -0x8(%rbp),%eax 0x00000000004004fa <+13>: mov -0x4(%rbp),%edx 0x00000000004004fd <+16>: add %edx,%eax 0x00000000004004ff <+18>: pop %rbp=> 0x0000000000400500 <+19>: retqEnd of assembler dump.(gdb) info registers rsprsp 0x7fffffffe2e8 0x7fffffffe2e8(gdb) x/1xw 0x7fffffffe2e80x7fffffffe2e8: 0x00400523

首先我们看到在执行retq前,堆栈上的内容是0x00400523。

接着我们再执行一次stepi

(gdb) stepimain () at main.c:1313 return 0;(gdb) info registers rsprsp 0x7fffffffe2f0 0x7fffffffe2f0(gdb) info registers riprip0x4005230x400523此时我们看到堆栈变化了,而且rip的值和刚才堆栈上的值是一样的。

然后再反汇编一下,看到此时正要执行的指令就是callq后面的一条指令。

(gdb) disassembleDump of assembler code for function main: 0x0000000000400501 <+0>: push %rbp 0x0000000000400502 <+1>: mov %rsp,%rbp 0x0000000000400505 <+4>: sub $0x10,%rsp 0x0000000000400509 <+8>: movl $0x3,-0x4(%rbp) 0x0000000000400510 <+15>: addl $0x3,-0x4(%rbp) 0x0000000000400514 <+19>: mov -0x4(%rbp),%eax 0x0000000000400517 <+22>: mov $0x2,%esi 0x000000000040051c <+27>: mov %eax,%edi 0x000000000040051e <+29>: callq 0x4004ed => 0x0000000000400523 <+34>: mov $0x0,%eax 0x0000000000400528 <+39>: leaveq 0x0000000000400529 <+40>: retqEnd of assembler dump.

修改返回地址

接下来我们还能模拟热补丁,来修改这个返回值。(当然比较简陋些。)

我们在add函数执行retq前停住,用gdb改变堆栈上的值,让他指向mov的下一条指令leaveq。

(gdb) disassembleDump of assembler code for function add: 0x00000000004004ed <+0>: push %rbp 0x00000000004004ee <+1>: mov %rsp,%rbp 0x00000000004004f1 <+4>: mov %edi,-0x4(%rbp) 0x00000000004004f4 <+7>: mov %esi,-0x8(%rbp) 0x00000000004004f7 <+10>: mov -0x8(%rbp),%eax 0x00000000004004fa <+13>: mov -0x4(%rbp),%edx 0x00000000004004fd <+16>: add %edx,%eax 0x00000000004004ff <+18>: pop %rbp=> 0x0000000000400500 <+19>: retqEnd of assembler dump.(gdb) info registers rsprsp 0x7fffffffe2e8 0x7fffffffe2e8(gdb) x/1xw 0x7fffffffe2e80x7fffffffe2e8: 0x00400523(gdb) set *((int *) 0x7fffffffe2e8) = 0x00400528(gdb) x/1xw 0x7fffffffe2e80x7fffffffe2e8: 0x00400528然后我们再执行stepi

(gdb) stepimain () at main.c:1414 }(gdb) info registers riprip 0x400528 0x400528 (gdb) disassembleDump of assembler code for function main: 0x0000000000400501 <+0>: push %rbp 0x0000000000400502 <+1>: mov %rsp,%rbp 0x0000000000400505 <+4>: sub $0x10,%rsp 0x0000000000400509 <+8>: movl $0x3,-0x4(%rbp) 0x0000000000400510 <+15>: addl $0x3,-0x4(%rbp) 0x0000000000400514 <+19>: mov -0x4(%rbp),%eax 0x0000000000400517 <+22>: mov $0x2,%esi 0x000000000040051c <+27>: mov %eax,%edi 0x000000000040051e <+29>: callq 0x4004ed 0x0000000000400523 <+34>: mov $0x0,%eax=> 0x0000000000400528 <+39>: leaveq 0x0000000000400529 <+40>: retqEnd of assembler dump.

瞧,这下是不是直接走到了leaveq,而不是刚才的mov?我们轻松的黑了一把。

好了,到这里就真的结束了,希望大家有所收获。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    4

    文章

    1436

    浏览量

    42483
  • 补丁
    +关注

    关注

    0

    文章

    27

    浏览量

    8756
  • 黑科技
    +关注

    关注

    14

    文章

    132

    浏览量

    38440

原文标题:揭露内核黑科技 - 热补丁技术真容

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    技术分享 | RK3588增加Xenomai3实时补丁

    Xenomai是一套为嵌入式系统设计的实时开发框架,通过“双内核”架构,让Linux既能处理复杂的通用任务,又能可靠地完成那些对响应时间有极端要求的任务,广泛用于工业自动化、机器人、航空航天等对实时性要求极高的场景。本篇文章以启扬RK3588开发板为例,分享如何增加Xenomai3实时
    的头像 发表于 11-27 17:29 1034次阅读
    <b class='flag-5'>技术</b>分享 | RK3588增加Xenomai3实时<b class='flag-5'>补丁</b>

    开源鸿蒙技术大会2025丨OS内核与视窗分论坛:筑基开源鸿蒙核心内核,共拓视窗技术边界

    开源鸿蒙技术大会2025 OS内核与视窗分论坛在湖南长沙国际会议中心圆满举行。来自西北工业大学、浙江大学、厦门大学、华为等高校和企业的学者专家齐聚一堂,围绕鸿蒙内核通信机制、智能渲染、内存缓存优化、端云协同备份及空间视窗架构等前
    的头像 发表于 11-20 17:29 454次阅读
    开源鸿蒙<b class='flag-5'>技术</b>大会2025丨OS<b class='flag-5'>内核</b>与视窗分论坛:筑基开源鸿蒙核心<b class='flag-5'>内核</b>,共拓视窗<b class='flag-5'>技术</b>边界

    别让小疏忽酿成大风险,这些补丁误区你避开了吗?

    补丁管理始终是维护系统安全与稳定的核心环节,它能确保操作系统、应用程序及终端设备时刻保持最新状态,获取最新的安全防护与功能支持。这一关键环节中的细微疏漏,往往成为引爆安全危机的导火索,可能让企业直面
    的头像 发表于 11-12 17:02 938次阅读
    别让小疏忽酿成大风险,这些<b class='flag-5'>补丁</b>误区你避开了吗?

    【米尔RK3506国产开发板评测】3、实时补丁以及EtherCAT IGH移植

    的指令格式如下 patch -p1 < path/to/patch-x.y.z Rockchip SDK中的doc/Real-Time-Performance目录下有相关的实时内核补丁
    发表于 10-27 10:09

    从微秒级响应到确定性延迟:深入解析米尔全志T536核心板的实时性技术突破

    树实现O(log n)调度复杂度,追求整体吞吐量最大化。 实时缺陷:内核不可抢占、自旋锁阻塞、中断屏蔽窗口等因素导致延迟不可预测。 2.2 标准内核+软隔离方案核心技术:在标准Li
    发表于 10-22 17:25

    深入解析米尔全志T536核心板的实时性技术突破

    内核补丁方案核心技术:将Linux内核改造成完全可抢占,用RT-mutex替代自旋锁,实现优先级继承。优势:兼容性好,无需修改应用程序。挑战:需要重新编译
    发表于 10-17 17:41

    国产!全志T113-i 双核Cortex-A7@1.2GHz 工业开发板—Linux-RT应用开发案例

    PREEMPT机制进行补丁。PREEMPT_RT补丁的关键是最小化不可抢占的内核代码量,同时最小化必须更改的代码量,以便提供这种附加的可抢占性。PREEMPT_RT补丁利用Linux
    的头像 发表于 07-30 10:33 576次阅读
    国产!全志T113-i 双核Cortex-A7@1.2GHz 工业开发板—Linux-RT应用开发案例

    迅为RK3568开发板OpeHarmony学习开发手册1.1-内核移植优化

    在上一小节的内核移植过程中,我们重新创建了内核补丁文件。然而,对于频繁修改内核源代码进行开发的情况来说,每次都制作内核
    发表于 07-26 10:37

    详解储能系统启动技术

    储能系统的“启动”是指在电力系统发生大规模停电或故障后,利用储能系统作为备用电源,重新启动电网的过程。在这种情况下,电网处于完全停电的状态,即所谓的“”状态,而储能系统则提供了必要的电能来逐步恢复电网的正常运行。
    的头像 发表于 05-29 10:16 3048次阅读
    详解储能系统<b class='flag-5'>黑</b>启动<b class='flag-5'>技术</b>

    用DevEco Studio增量补丁修复功能,让鸿蒙应用的调试效率大增

    DevEco Studio中得到了很好的解答,而增量补丁修复便是其中的核心特性之一。今天,我们要深入探讨鸿蒙应用增量补丁修复及其两个能够大幅加速开发进度的强大功能——Hot Reload和Apply
    发表于 04-14 17:35

    用DevEco Studio增量补丁修复功能,让鸿蒙应用的调试效率大增

    DevEco Studio中得到了很好的解答,而增量补丁修复便是其中的核心特性之一。今天,我们要深入探讨鸿蒙应用增量补丁修复及其两个能够大幅加速开发进度的强大功能——Hot Reload和Apply
    发表于 04-14 14:47

    解锁树莓派新玩法:Ubuntu 实时内核安装指南

    警告:这只是我在业余时间进行的一个个人项目,并未得到Canonical的认可或支持。你即将安装的内核可能不会收到任何补丁或安全更新。它未经彻底测试,可能会使你的系统无法使用,甚至损坏你的数据。请自行承担风险。
    的头像 发表于 03-25 09:40 640次阅读
    解锁树莓派新玩法:Ubuntu 实时<b class='flag-5'>内核</b>安装指南

    树莓派4 性能大比拼:标准Linux与实时Linux 4.19内核的延迟测试

    引言本文是对我之前关于RaspberryPi3同一主题的帖子的更新。与之前的帖子一样,我使用的是随Raspbian镜像提供的标准内核,以及应用了RT补丁的相似内核版本。对于实时版,我
    的头像 发表于 03-25 09:39 655次阅读
    树莓派4 性能大比拼:标准Linux与实时Linux 4.19<b class='flag-5'>内核</b>的延迟测试

    技术分享】迅为RK3568开发板使用TFTP加载内核设备树

    技术分享】迅为RK3568开发板使用TFTP加载内核设备树
    的头像 发表于 02-21 14:04 1120次阅读
    【<b class='flag-5'>技术</b>分享】迅为RK3568开发板使用TFTP加载<b class='flag-5'>内核</b>设备树

    南亚科技与补丁科技携手开发定制超高带宽内存

    Memory)的开发。 此次合作将充分融合南亚科技在10nm级DRAM技术领域的深厚积累,以及补丁科技在定制内存产品设计方面的卓越能力。双方将强强联手,共同打造出针对AI与边缘应用需求的高附加值、高性能、低功耗的定制超高带宽内存解决方案。 这一战略合作的达成,标志着南亚
    的头像 发表于 12-20 14:28 931次阅读