写点什么

应用程序热补丁(一):几行代码构造免重启修复补丁

  • 2019-11-12
  • 本文字数:2507 字

    阅读完需:约 8 分钟

应用程序热补丁(一):几行代码构造免重启修复补丁

热补丁是一种在程序运行时动态修复内存中代码 bug 的技术。在 UCloud,我们使用内核热补丁和应用程序热补丁(也就是进程热补丁)来在线修复核心业务的缺陷和安全漏洞。


多年来我们使用内核热补丁技术避免了系统重启导致的业务中断、保证了操作系统的可用性。属于核心业务组件的应用程序,尤其是单点的虚拟化组件和有状态的应用程序,同样面对高可用的挑战,每次重启都会导致服务受损。


然而业界并没有成熟可靠的应用程序热补丁方案可以参考,原因在于应用程序热补丁比内核热补丁更加困难和复杂。比如内核对外提供完整的模块加载功能,可以直接加载内核模块形式的热补丁。


而应用程序需要通过外部程序通过一系列对其内存和寄存器的复杂操作来注入动态链接库形式的热补丁;应用程序包含多线程;内核在编译时会被限定使用特定的编译方式,而应用程序的编译方式则更加宽泛;内核的二进制相对简单,而应用程序二进制因为需要链接到多种的动态链接库,本身的结构会更复杂。


经过大量的研究和实践,我们针对应用程序如何免重启修复 bug,自研了一套应用程序热补丁技术而且在 UCloud 内部已经经过数十万台次修复验证。后面通过一系列文章分享其技术实现。本文先介绍一种简单实用的应用程序热补丁技术,不少场景下采用该方法编写几行代码即可免修复应用程序 bug。

原理

一般来说,应用程序热补丁的流程是:首先通过编译器将热补丁源码制作成可加载的动态链接库,然后通过加载程序将热补丁加载到目标进程的地址空间,最后在进行一致性模型检查确认安全的情况下,把原始代码替换成新的代码,完成在线修复的过程。


下面我们分别介绍热补丁本身和热补丁加载程序,热补丁本身是因 patch 而异的,加载程序是通用的。假设我们有热补丁加载程序 Loader、目标进程 T、热补丁 patch.so,目标程序的 func 函数替换为 func_v2。

热补丁

  • 编写热补丁源码,编译成动态链接库的格式的热补丁 patch.so,patch.so 中包含 func 和 func_v2 的信息。

  • 热补丁 patch.so 在被加载程序 Loader 加载到目标进程 T 地址空间的过程中,通过 dlsym 调用找到 func 的地址,并将 func 的入口指令改为可写,同时改变为跳转到 func_v2。

  • 至此,所有对 func 的调用都会被重定向到 func_v2,func_v2 执行完毕后返回,程序继续运行。

  • 如图所示:


热补丁加载程序

  • 加载程序 Loader 找到目标进程 T 的 dlopen 函数入口地址。

  • Loader 通过 ptrace 依附到目标进程 T,Loader 将热补丁的名字放入放入目标进程 T 的堆栈,将 IP 寄存器设置为 dlopen 函数的地址。

  • Loader 使目标进程 T 继续运行。因为 IP 寄存器已经设置为 dlopen 函数的入口,目标进程 T 会调用 dlopen 把热补丁加载到 T 的地址空间中。

  • 如图所示:



了解原理之后,我们一步步实现一种简单的基于 x86_64 的热补丁。(对于需要制作热补丁的同学,只需自己编写 patch.so,而 Loader 是通用的。patch.so 编写可以参考下面的例子,往往只需几行代码做相应替换。)

实现

热补丁

  1. 目标进程 T 执行 dlopen 的过程中,通过预先在热补丁(动态链接库)中写入的 constructor 函数,在加载过程中函数 func_v1 替换函数 func。


热补丁加载程序

1.Loader 得到目标进程 T 地址空间中 dlopen 入口地址


  • dlopen 函数有 libdl 提供,并不是所有的程序都加载 libdl,幸运的是,libc 中提供了同样功能的函数 __libc_dlopen_mode,并且接受的参数和 dlopen 相同。除非特殊情况,所有程序都会加载 libc。所以我们需要找到 __libc_dlopen_mode 在目标进程 T 地址空间中的函数入口地址。

  • 我们知道,不同进程中 libc 会被加载到不同的基地址,但是 libc 中函数的地址相对基地址的偏移是不变的。

  • 通过 Loader 和目标进程 T 的 /proc/pid/maps,我们可以得到 libc 在 Loader 和目标进程 T 中加载的基地址。通过 Loader 运行 dlsym,我们可以得到 Loader 中的__libc_dlopen_mode 的地址。这样我们可以得到目标进程 T 中__libc_dlopen_mode 的地址(Loader_dlopen - Loader_libc + T_libc)。



2.Loader 对目标进程 T 使用 ptrace attach,并保存 T 此时的寄存器信息。



3.将目标进程 T 的 %RIP 指向 dlopen,热补丁的名字的字符串放入堆栈,字符串的地址写入 %rdi,RTLD_NOW 的值写入 %rsi 作为 dlopen 的 flag。同时把 dlopen 返回地址设置为非法地址 0x0(把 0x0 压入栈中),这样 Loader 可以捕获目标进程 T 产生的 SIGSEGV 信号进而重新获得 T 的控制权。



  1. Loader 使目标进程 T 继续运行。当 T 执行完 dlopen 之后,T 产生的 SIGSEGV 信号被 Loader 捕获,Loader 重新获得 T 进程的控制权。



5.Loader 通过读取目标进程 T 此时的 %rax 寄存器得到 dlopen 的返回值,恢复 T 最开始的执行状态,最后释放对 T 的控制。



至此对目标进程 T 的热补丁就完成了,下面我们看一个例子。

验证

假设我们运行 target 程序,每隔一秒打印 Hello 一次:



target 程序由 target 本身和 libold.so 组成,分别代码如下:



编译如下:



我们想要修改 print 函数,变成打印“Goodbye”。我们需要编写热补丁 new.c,并添加新函数和 constructor:



编译:



然后通过加载程序对 target 进程打入热补丁 libnew.so,最后我们对 target 程序打入这个热补丁,观察变化:



我们发现热补丁确实改变了 print 函数,最后通过 gdb 进一步确认,可以看出 print 函数的入口被修改成 48 b8 dc b6 15 a9 c1 7f 00 00 ff e0,与我们的预期相符:


总结

我们介绍了应用程序热补丁的基本原理,实践了一个应用程序热补丁 demo。此类热补丁适用于动态替换共享链接库中的可见函数,可以修复例如 glibc “GHOST 漏洞”(CVE-2015-0235)等等,在 UCloud 我们利用热补丁修复了若干缺陷,在用户没有感知的情况下把 bug 快速及时的修复。这些热补丁修复程序里,绝大多数代码是通用的,只需少数几行做特殊替换。


本文介绍的热补丁技术对于适用的场景非常理想,简单可靠,但存在几个缺点:


  • 手写热补丁代码门槛较高,特别是被修复函数的依赖函数链较长时手写热补丁很容易出错;

  • 无法修复局部函数和局部变量(只能修复全局可见的函数和变量)。


后面的文章我们会介绍如一种更加先进的应用程序热补丁技术。


本文转载自公众号 UCloud 技术(ID:ucloud_tech)。


原文链接:


https://mp.weixin.qq.com/s/BJhpWihFh3UcxMrvaZaOnw


2019-11-12 16:184563

评论

发布
暂无评论
发现更多内容

如何使用 Python 内置虚拟环境工具 venv 创建和管理虚拟环境

克莱因瓶

音乐NFT开发的测试

北京木奇移动技术有限公司

软件外包公司 音乐NFT 体育NFT

数字藏品(NFT)开发的智能合约

北京木奇移动技术有限公司

软件外包公司 音乐NFT 体育NFT

电商平台爬虫技术深度剖析:实战心得与技巧汇总

代码忍者

构建1688自动代采系统:PHP开发实战指南

代码忍者

1688代采

AI 发展是否正在放缓?AI 发展将驶向何方?

Baihai IDP

程序员 AI 白海科技 LLMs

火山引擎「车鱼视听」“汽水音乐”蔚来车端首发,带来更多车载音乐体验选择

极客天地

如何在Mac上安装Python环境的详细指南

克莱因瓶

《CPython Internals》阅读笔记:p356-p359

codists

CPython Internals

Windows 11 24H2 中文版、英文版 (x64、ARM64) 下载 (2025 年 1 月更新)

sysin

windows

Windows Server 2025 中文版、英文版下载 (2025 年 1 月更新)

sysin

windows

火语言RPA--超级鹰打码

火语言RPA

如何在一台计算机上管理多版本的 Python

克莱因瓶

深度解析:利用商品详情 API 接口实现数据获取与应用

科普小能手

数据挖掘 淘宝 电商 API 接口 跨境电商运营

如何使用 Virtualenv 创建和管理 Python 虚拟环境的详细指南

克莱因瓶

全面解析 Anaconda 的安装与使用

克莱因瓶

音乐NFT的上线流程

北京木奇移动技术有限公司

软件外包公司 音乐NFT 体育NFT

Windows 11 23H2 中文版、英文版 (x64、ARM64) 下载 (2025 年 1 月更新)

sysin

windows

人工智能丨Midscene:让UI自动化测试变得更简单

测试人

人工智能 软件测试

大模型选择指南:功能、参数和优化

JustYan

人工智能 大模型 生成式AI

Windows Server 2022 中文版、英文版下载 (2025 年 1 月更新)

sysin

windows

音乐NFT开发中的主要技术难点

北京木奇移动技术有限公司

软件外包公司 音乐NFT 体育NFT

性能测试丨Nginx 性能数据监控

测试人

软件测试

《CPython Internals》阅读笔记:p360-p377

codists

CPython Internals

如何在 Linux 系统下安装并配置 Anaconda 上的 Python 开发环境

克莱因瓶

Windows 10 version 22H2 中文版、英文版下载 (2025 年 1 月更新)

sysin

windows

1688 买家订单,订单物流,订单回传接口系列(1688 寻源通 API)

tbapi

1688API 1688订单接口 1688订单物流接口 1688寻原通

性能测试丨JVM 性能数据采集

测试人

软件测试

数字藏品NFT的开发框架

北京木奇移动技术有限公司

软件外包公司 音乐NFT 体育NFT

你的LLM评估方法过时了吗?这三个范式转变不容错过

Baihai IDP

程序员 AI LLM LLM评估范式

应用程序热补丁(一):几行代码构造免重启修复补丁_文化 & 方法_王超_InfoQ精选文章