linux下C热补丁

linux运行进程热补丁(一)之函数替换_linux 热补丁的实现-CSDN博客

一、实现目标
在Linux环境下(x86_64)对正在运行进程的函数替换,不改变该进程的可执行文件内容,通过使用汇编指令JMP完成运行中进程的函数替换。

二、代码示例
1.easy的target进程代码
#cat myprint.c
//编译myprint.c变为可执行文件
#gcc -o myprint myprint.c

#include <stdio.h>
void myprint()
{
   printf("the old func\n");
   return;
}

void new_myprint()
{
   printf("the new func\n");
   return;
}

int main(int argc,char *argv[])
{
   while(1)
   {
       myprint();
       sleep(1);
   }
   return 0;

}

执行效果:

2.热补丁代码( jmp)
#cat jmp.c
//编译myprint.c变为可执行文件
#gcc -o jmp jmp.c​

#include <unistd.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>

#define DEBUG(fmt,...) do {printf(fmt, __VA_ARGS__);} while(0)
#define INFO(fmt,...) do {printf(fmt, __VA_ARGS__);} while(0)
#define NOTICE(fmt,...) do {printf(fmt, __VA_ARGS__);} while(0)
#define ERROR(fmt,...) do {printf(fmt, __VA_ARGS__);} while(0)


int set_data(pid_t pid,int addr,void *val,int vlen)
{
    int i;
    int addr0 = addr & ~3;
    int len = (((addr +vlen) - addr0) +3 )/4;
    int *lv = malloc(len * sizeof(int));
    DEBUG("peek: %d, addr0 = %x, addr-addr0 = %d",len,addr0,addr-addr0);
    for (i = 0; i < len; i++) {
         if (i % 4 == 0){
          DEBUG("\n %p ",(void *)(addr0 + i * sizeof(int)));
         }

         lv[i] = ptrace(PTRACE_PEEKDATA, pid, addr0 + i * sizeof(int), NULL);

         if (lv[i] == -1 && errno != 0) {
            perror("ptrace peeku");
            return -1;
         }
         DEBUG("%08x ",lv[i]);
      }
    memcpy((char *) lv + (addr - addr0) , val, vlen);
    DEBUG("\n poke: %d", len);
    for (i = 0; i < len; i++) {
         if (i%4 == 0) {
            DEBUG("\n %p ",(void *) (addr0 + i * sizeof(int)));
          }

         if (ptrace(PTRACE_POKEDATA, pid, addr0 + i * sizeof(int) , lv[i]) < 0) {
            perror("ptrace peeku");
            return -1;
         }
          DEBUG("%08x ",lv[i]);
     }
    DEBUG("%s","\n") ; /* XXX */
    return 0;
}

//"usage: /jmp  "target_pid" "source_addr" "target_addr" "0"
int main(int argc, char*argv[])
{
    unsigned int addr;
    unsigned int addr2;
    int jmp_relative;
    char jmpbuf[5];
    pid_t target_pid;
    int status =  -1;

    if (argc < 4)
    {
    printf("usage: /jmp  \"target_pid\" \"source_addr\" \"target_addr\" \n") ;
    return 0;
    }
    target_pid = atoi(argv[1]);
    addr = atoi(argv[2]); //source
    addr2 = atoi(argv[3]);//target
    jmp_relative = addr2 - (addr + 5);

    printf("addr2=%p addr=%p \n",addr2, addr);
    printf("jmp relative %ld (0x%08lx)\n", jmp_relative, jmp_relative) ;

    jmpbuf[0] = 0xe9; /* jmp */
    memcpy(jmpbuf+1, &jmp_relative, sizeof(int));

    status = 1;

    printf("jmpbuf = 0x%02x%02x%02x%02x%02x \n",jmpbuf[0], jmpbuf[1] , jmpbuf[2] , jmpbuf[3] , jmpbuf[4]);

    if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) < 0){
       perror("ptrace attach") ;
       exit(-2) ;
    }

    DEBUG("attached %d\n", target_pid) ;
    wait(NULL);
    if (set_data(target_pid, addr, jmpbuf, sizeof(jmpbuf)) < 0){
       DEBUG("E: jmp %p %p failed. \n", (void *)addr, (void *) addr2);
       ptrace(PTRACE_DETACH, target_pid, NULL, NULL);
       DEBUG("detached %d \n",target_pid);

       exit(-3);
    }

    ptrace(PTRACE_DETACH, target_pid, NULL, NULL);
    DEBUG("detached %d \n",target_pid);
    exit(status);
}
JMP注入热补丁后执行效果(函数替换):

二、热补丁代码分析
1.需要确定target进程的pid信息
# ps -ef | grep print
root     10417 30217  0 17:49 pts/0    00:00:00 ./myprint
root     14651 14521  0 18:26 pts/1    00:00:00 grep --color=auto print

2.使用Linux工具objdump,确定 myprint和new_myprint函数地址
实际使用中,新函数往往是动态库,只能通过dlopen、dlsym等函数查询。

# objdump -S myprint
//地址十六进制转十进制
0x40057d <myprint> --> 4195709 (source_addr)
0x40058e <new_myprint> --> 4195726 (target_addr)

# objdump -S myprint
myprint:     file format elf64-x86-64
...
000000000040057d <myprint>:
  40057d:       55                      push   %rbp
  40057e:       48 89 e5                mov    %rsp,%rbp
  400581:       bf 60 06 40 00          mov    $0x400660,%edi
  400586:       e8 c5 fe ff ff          callq  400450 <puts@plt>
  40058b:       90                      nop
  40058c:       5d                      pop    %rbp
  40058d:       c3                      retq

000000000040058e <new_myprint>:
  40058e:       55                      push   %rbp
  40058f:       48 89 e5                mov    %rsp,%rbp
  400592:       bf 6d 06 40 00          mov    $0x40066d,%edi
  400597:       e8 b4 fe ff ff          callq  400450 <puts@plt>
  40059c:       90                      nop
  40059d:       5d                      pop    %rbp
  40059e:       c3                      retq

000000000040059f <main>:
  40059f:       55                      push   %rbp
  4005a0:       48 89 e5                mov    %rsp,%rbp
  4005a3:       48 83 ec 10             sub    $0x10,%rsp
  4005a7:       89 7d fc                mov    %edi,-0x4(%rbp)
  4005aa:       48 89 75 f0             mov    %rsi,-0x10(%rbp)
  4005ae:       b8 00 00 00 00          mov    $0x0,%eax
  4005b3:       e8 c5 ff ff ff          callq  40057d <myprint>
  4005b8:       bf 01 00 00 00          mov    $0x1,%edi
  4005bd:       b8 00 00 00 00          mov    $0x0,%eax
  4005c2:       e8 b9 fe ff ff          callq  400480 <sleep@plt>
  4005c7:       eb e5                   jmp    4005ae <main+0xf>
  4005c9:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)
... ...

3. gdb attach myprint,查看myprint函数的汇编代码
3.1 myprint进程while循环函数跳转到指令 main+15的位置:
 0x00000000004005c7 <+40>:    jmp    0x4005ae <main+15>
1
3.2 main+15的位置为执行myprint函数入口:
0x00000000004005ae <+15>:    mov    $0x0,%eax
0x00000000004005b3 <+20>:    callq  0x40057d <myprint>

3.3 myprint地址–>0x40057d (需要将push %rbp变成了jmpq 0x40058e,完成函数替换 )
 0x000000000040057d <+0>:     push   %rbp

3.4 new_myprint地址–>0x40058e
 (gdb) disassemble new_myprint
 Dump of assembler code for function new_myprint:
 0x000000000040058e <+0>:     push   %rbp

# gdb attach 10417
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.

(gdb) disassemble main
Dump of assembler code for function main:
   0x000000000040059f <+0>:     push   %rbp
   0x00000000004005a0 <+1>:     mov    %rsp,%rbp
   0x00000000004005a3 <+4>:     sub    $0x10,%rsp
   0x00000000004005a7 <+8>:     mov    %edi,-0x4(%rbp)
   0x00000000004005aa <+11>:    mov    %rsi,-0x10(%rbp)
   0x00000000004005ae <+15>:    mov    $0x0,%eax
   0x00000000004005b3 <+20>:    callq  0x40057d <myprint>
   0x00000000004005b8 <+25>:    mov    $0x1,%edi
   0x00000000004005bd <+30>:    mov    $0x0,%eax
   0x00000000004005c2 <+35>:    callq  0x400480 <sleep@plt>
   0x00000000004005c7 <+40>:    jmp    0x4005ae <main+15>
End of assembler dump.
(gdb) disassemble myprint
Dump of assembler code for function myprint:
   0x000000000040057d <+0>:     push   %rbp
   0x000000000040057e <+1>:     mov    %rsp,%rbp
   0x0000000000400581 <+4>:     mov    $0x400660,%edi
   0x0000000000400586 <+9>:     callq  0x400450 <puts@plt>
   0x000000000040058b <+14>:    nop
   0x000000000040058c <+15>:    pop    %rbp
   0x000000000040058d <+16>:    retq
End of assembler dump.
(gdb) disassemble new_myprint
Dump of assembler code for function new_myprint:
   0x000000000040058e <+0>:     push   %rbp
   0x000000000040058f <+1>:     mov    %rsp,%rbp
   0x0000000000400592 <+4>:     mov    $0x40066d,%edi
   0x0000000000400597 <+9>:     callq  0x400450 <puts@plt>
   0x000000000040059c <+14>:    nop
   0x000000000040059d <+15>:    pop    %rbp
   0x000000000040059e <+16>:    retq
End of assembler dump.

4. main函数分析
int main(int argc, char*argv[])
{
    //初始化地址和跳转相关变量
    unsigned int addr;
    unsigned int addr2;
    int jmp_relative;
    char jmpbuf[5];
    pid_t target_pid;
    int status =  -1;

    if (argc < 4)
    {
    printf("usage: /jmp  \"target_pid\" \"source_addr\" \"target_addr\" \n") ;
    return 0;
    }
    
    target_pid = atoi(argv[1]);
    addr = atoi(argv[2]); //source
    addr2 = atoi(argv[3]);//target
    //jmp的偏移量:目标地址addr2与下一条指令地址(addr + 5 跳转指令长度)的差值。
    //4195709(addr地址即myprint) + 5 + jmp_relative = 4195726 (addr2地址即new_myprint)
    jmp_relative = addr2 - ( addr + 5);
    
    //打印相关地址信息
    /*
    addr2=0x40058e addr=0x40057d
    jmp relative 12 (0x0000000c)
    */
    printf("addr2=%p addr=%p \n",addr2, addr);
    printf("jmp relative %ld (0x%08lx)\n", jmp_relative, jmp_relative) ;
    
    //构造JMP指令,jmp指令直接修改指令寄存器IP的值
    //近跳转(Near Jmp,可跳至同一段范围内的地址),对应机器码:E9
    //E9指令计算方法:跳转地址 = 基地址+偏移量+跳转指令长度
    //0x40057d + JMP指令长度 + jmp_relative(12) = 目标地址(0x40058e) 
    // 4195709 + 5 + 12 = 4195726
    jmpbuf[0] = 0xe9; /* jmp */
    //拷贝内存jmpbuf+1地址开始的4(sizeof(int))字节到jmp_relative
    //  jmpbuf = 0xffffffe9 0c00 0000
    memcpy(jmpbuf+1, &jmp_relative, sizeof(int));

    status = 1;
    //jmpbuf = 0xffffff e9 0c 00 00 00
    printf("jmpbuf = 0x%02x%02x%02x%02x%02x \n",jmpbuf[0], jmpbuf[1] , jmpbuf[2] , jmpbuf[3] , jmpbuf[4]);
   //调用PTRACE_ATTACH
    if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) < 0){
       perror("ptrace attach") ;
       exit(-2) ;
    }

    DEBUG("attached %d\n", target_pid) ;
    // wait(NULL)将阻止父进程,直到ptrace子进程完成
    wait(NULL);
    //调用函数替换set_data
    if (set_data(target_pid, addr, jmpbuf, sizeof(jmpbuf)) < 0){
       DEBUG("E: jmp %p %p failed. \n", (void *)addr, (void *) addr2);
       ptrace(PTRACE_DETACH, target_pid, NULL, NULL);
       DEBUG("detached %d \n",target_pid);

       exit(-3);
    }
  //调用PTRACE_DETACH
    ptrace(PTRACE_DETACH, target_pid, NULL, NULL);
    DEBUG("detached %d \n",target_pid);
    exit(status);
}

指令替换完成后结果:


5. set_data(pid_t pid,int addr,void *val,int vlen)函数分析
//main函数的传参:set_data(target_pid, addr, jmpbuf, sizeof(jmpbuf))
//addr=0x40057d (source 4195709) jmpbuf = 0xffffffe90c000000  sizeof(jmpbuf) = 5
int set_data(pid_t pid,int addr,void *val,int vlen)
{
    int i;
    //~:按位取反,如~3,由于3用2进制是11,所以~3即是说最低两位变成0,其它的都变成1,起到了掩码的作用。
    //addr0 = 40057c  addr=0x40057d
    // 3 --> 0x0000 0011  ~3 --> 1111 1111 1111 1100   
    //  0100 0000 0000 0101 0111 1101  
    // &
    //  1111 1111 1111 1111 1111 1100
    //  0100 0000 0011 0101 0111 1100       0x40357C (4195708)
    int addr0 = addr & ~3;
    // 4195709 + 5 - 4195708 + 3 /4  = 2
    int len = (((addr +vlen) - addr0) +3 )/4;
    // *lv分配2个int空间,8字节 00000000 00000000  
    int *lv = malloc(len * sizeof(int));
    DEBUG("peek: %d, addr0 = %x, addr-addr0 = %d",len,addr0,addr-addr0);
    for (i = 0; i < len; i++) {
         if (i % 4 == 0){
          DEBUG("\n %p ",(void *)(addr0 + i * sizeof(int)));
         }
         //i=0  lv[0] = 894855ff     0x40057c
         //i=1  lv[1] = 0660bfe5     0x40057c + 1(int)
         lv[i] = ptrace(PTRACE_PEEKDATA, pid, addr0 + i * sizeof(int), NULL);

         if (lv[i] == -1 && errno != 0) {
            perror("ptrace peeku");
            return -1;
         }
         DEBUG("%08x ",lv[i]);
      }
    //val=jmpbuf = 0xffffffe9 0c00 0000
    //lv + (addr - addr0) --> 从lv +1 开始覆盖
   //lv[0] = 000c e9ff     
   //lv[1] = 0660 0000     
    memcpy((char *) lv + (addr - addr0) , val, vlen);
    DEBUG("\n poke: %d", len);
    for (i = 0; i < len; i++) {
         if (i%4 == 0) {
            DEBUG("\n %p ",(void *) (addr0 + i * sizeof(int)));
          }
         //             0x40357d  0x40357C     
               //lv[0] = 000c    e9        ff     
               //lv[1] = 0660 0000   
         if (ptrace(PTRACE_POKEDATA, pid, addr0 + i * sizeof(int) , lv[i]) < 0) {
            perror("ptrace peeku");
            return -1;
         }
          DEBUG("%08x ",lv[i]);
     }
    DEBUG("%s","\n") ; /* XXX */
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/831674.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【科研技术】华为为什么不给微信特权?

::: block-1 “时问桫椤”是一个致力于为本科生到研究生教育阶段提供帮助的不太正式的公众号。我们旨在在大家感到困惑、痛苦或面临困难时伸出援手。通过总结广大研究生的经验&#xff0c;帮助大家尽早适应研究生生活&#xff0c;尽快了解科研的本质。祝一切顺利&#xff01;—…

# 从浅入深 学习 SpringCloud 微服务架构(七)Hystrix(4)

从浅入深 学习 SpringCloud 微服务架构&#xff08;七&#xff09;Hystrix&#xff08;4&#xff09; 一、hystrix&#xff1a;使用 turbine 聚合所有的 hytrix 的监控数据测试。创建父工程 spring_cloud_hystrix_demo&#xff0c;导入相关依赖坐标。并在父工程 spring_cloud_…

C语言/数据结构——每日一题(移除链表元素)

一.前言 今天在leetcode刷到了一道关于单链表的题。想着和大家分享一下。废话不多说&#xff0c;让我们开始今天的知识分享吧。 二.正文 1.1题目要求 1.2思路剖析 我们可以创建一个新的单链表&#xff0c;然后通过对原单链表的遍历&#xff0c;将数据不等于val的节点移到新…

图床搭建GitHub+PicGo+jsdelivr(CDN)+Typora(内附加速工具)

目录 安装PicGo GitHub配置与加速器 配置PicGo 使用typroa 安装PicGo PicGo是一个用于上传图片的客户端&#xff0c;支持拖拽上传、剪贴板上传&#xff0c;功能十分方便。 下载地址&#xff1a; https://github.com/Molunerfinn/PicGo/releases 个人网盘自取版本2.4.0…

了解并学会使用反射

目录 一、反射的应用场景&#xff08;简单了解&#xff09; 二、反射的定义 三、关于反射的四个重要的类 四、反射的使用 1.Class获取一个class对象的方式 方式一&#xff1a;forName&#xff08;&#xff09;&#xff1a; 方式二&#xff1a;封装类.Class&#xff1a; …

【stomp 实战】Spring websocket 用户订阅和会话的管理源码分析

通过Spring websocket 用户校验和业务会话绑定我们学会了如何将业务会话绑定到spring websocket会话上。通过这一节&#xff0c;我们来分析一下会话和订阅的实现 用户会话的数据结构 SessionInfo 用户会话 用户会话定义如下&#xff1a; private static final class Sessio…

怎么让电脑耳机和音响都有声音

电脑耳机音响不能同时用没声音怎么办 一般来说&#xff0c;重新开机后问题能够得到解决。右击“我的电脑”---“属性”---“硬件”---“设备管理器”&#xff0c;打开“声音、视频和游戏控制器”有无问题&#xff0c;即看前面有没有出现黄色的“”。 如果您的 电脑 耳机能正常…

VMware虚拟机中ubuntu使用记录(4)—— 如何在VMware虚拟机中调用本机电脑的摄像头

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、虚拟机调用本机摄像头(1) 启动VMware USB 服务(2) 连接本机摄像头(3) 测试摄像头的连接 前言 通过配置虚拟机调用本机摄像头&#xff0c;用户可以在虚拟机…

Redis---------实现商品秒杀业务,包括唯一ID,超卖问题,分布式锁

订单ID必须是唯一 唯一ID构成&#xff1a; 代码生成唯一ID&#xff1a; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.tim…

【论文阅读】Learning Texture Transformer Network for Image Super-Resolution

Learning Texture Transformer Network for Image Super-Resolution 论文地址Abstract1. 简介2.相关工作2.1单图像超分辨率2.2 Reference-based Image Super-Resolution 3. 方法3.1. Texture TransformerLearnable Texture Extractor 可学习的纹理提取器。Relevance Embedding.…

Qt QImageWriter类介绍

1.简介 QImageWriter 用于写入图像文件的类。它提供了将 QImage 对象保存到不同图像格式文件的功能&#xff0c;包括但不限于 PNG、JPEG、BMP 等。QImageWriter 可以将图像写入文件&#xff0c;也可以写入任何 QIODevice&#xff0c;如 QByteArray&#xff0c;这使得它非常灵活…

python中type,object,class 三者关系

type,object,class 三者关系 在python中&#xff0c;所有类的创建关系遵循&#xff1a; type -> int -> 1 type -> class -> obj例如&#xff1a; a 1 b "abc" print(type(1)) # <class int> 返回对象的类型 print(type(int)) …

基于OpenCv的图像金字塔

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

【讲解如何OpenCV入门】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

需求规格说明书编制书(word原件)

1 范围 1.1 系统概述 1.2 文档概述 1.3 术语及缩略语 2 引用文档 3 需求 3.1 要求的状态和方式 3.2 系统能力需求 3.3 系统外部接口需求 3.3.1 管理接口 3.3.2 业务接口 3.4 系统内部接口需求 3.5 系统内部数据需求 3.6 适应性需求 3.7 安全性需求 3.8 保密性需…

GiantPandaCV | FasterTransformer Decoding 源码分析(二)-Decoder框架介绍

本文来源公众号“GiantPandaCV”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;FasterTransformer Decoding 源码分析(二)-Decoder框架介绍 作者丨进击的Killua 来源丨https://zhuanlan.zhihu.com/p/669303360 编辑丨GiantPand…

【Python编程实践1/3】模块

目录 目标 模块 import ​编辑 代码小结 题目 from...import 随机模块 代码小结 randint函数 骰子大战 choice函数 总结 目标 拧一颗螺丝&#xff0c;只会用到螺丝刀&#xff1b;但是修一台汽车&#xff0c;需要一整套汽修的工具。函数就像螺丝刀&#xff0c;可以帮…

python项目==一个web项目,配置模板指定文件清洗规则,调用模板规则清洗文件

代码地址 一个小工具。 一个web项目&#xff0c;配置模板指定文件清洗规则&#xff0c;调用模板规则清洗文件 https://github.com/hebian1994/csv-transfer-all 技术栈&#xff1a; SQLite python flask vue3 elementplus 功能介绍&#xff1a; A WEB tool for cleaning…

JavaScript:Web APIs(三)

本篇文章的内容包括&#xff1a; 一&#xff0c;事件流 二&#xff0c;移除事件监听 三&#xff0c;其他事件 四&#xff0c;元素尺寸与位置 一&#xff0c;事件流 事件流是什么呢&#xff1f; 事件流是指事件执行过程中的流动路径。 我们发现&#xff0c;一个完整的事件执行…

Delta lake with Java--利用spark sql操作数据1

今天要解决的问题是如何使用spark sql 建表&#xff0c;插入数据以及查询数据 1、建立一个类叫 DeltaLakeWithSparkSql1&#xff0c;具体代码如下&#xff0c;例子参考Delta Lake Up & Running第3章内容 import org.apache.spark.sql.SaveMode; import org.apache.spark.…