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.mfbz.cn/a/591695.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

phpMyAdmin增加自定义IP登录教程

phpMyAdmin增加自定义IP登录教程 1、打开phpMyAdmin目录&#xff0c; 在此目录下是否有config.sample.inc.php文件&#xff0c;如果存在&#xff0c;那么将其改名为config.inc.php&#xff08;为避免修改失误所造成的损失&#xff0c;强烈建议先备份config.sample.inc.php文件…

matlab期末知识

1.期末考什么&#xff1f; 1.1 matlab操作界面 &#xff08;1&#xff09;matlab主界面 &#xff08;2&#xff09;命令行窗口 &#xff08;3&#xff09;当前文件夹窗口 &#xff08;4&#xff09;工作区窗口 &#xff08;5&#xff09;命令历史记录窗口 1.2 matlab搜索…

U盘启动树莓派系统操作流程(3B+)

步骤 使用SD Card启动修改树莓派硬件启动方式 已烧写好的SD Card先 config.txt文件最后一行配置 program_usb_boot_mode1 program_usb_boot_timeout1 ## 超时时间加大到5s, 避免硬件USB枚举时过长导致启动超时。 SD Card接入树莓派&#xff0c; 然后上电, 使用指令 vcgencm…

「2024年」前端开发常用工具函数总结 TypeScript

前言 在前端开发中&#xff0c;工具函数是提高代码复用率、保持代码整洁和增加开发效率的关键。使用 TypeScript 编写工具函数不仅可以帮助开发者捕捉到更多的类型错误&#xff0c;还可以提供更清晰的代码注释和更智能的代码补全。下面是一些在 TypeScript 中常用的前端开发工…

搜好货API接口:快速获取商品列表的利器

搜好货商品列表API接口允许开发者根据关键字搜索并获取相关的商品列表数据。接口支持多种参数配置&#xff0c;可以根据需求灵活调整搜索条件和结果返回格式。 点击获取key和secret API接口请求说明 请求地址&#xff1a;https://api.souhaohuo.com/goods/search请求方法&…

Java——认识异常

目录 一.异常的概念与体系结构 1.异常的概念 1.1算术异常 1.2数组越界异常 1.3空指针异常 2.异常的体系结构 3.异常的分类 3.1编译时异常 3.2运行时异常 二.异常的处理 1.防御式编程 1.1LBYL 1.2EAFP&#xff08;核心&#xff09; 2.异常的抛出 3.异常的捕获 3…

主流Text2Image技术学习

DDPM原理 DDPM&#xff08;Denoising Diffusion Probabilistic Models&#xff09;是一种生成模型&#xff0c;它通过模拟数据的扩散过程来生成新的数据样本。 DDPM通过一个随时间增加噪声的扩散过程和一个逐步去除噪声的生成过程来模拟数据分布。其核心在于训练一个去噪声模…

Steam新人下载安装教程分享 迅游一键下载安装steam

Steam平台是Valve公司聘请的BitTorrent协议&#xff08;BT下载&#xff09;发明者Bram Cohen亲自开发设计。国内玩家对于Valve公司的游戏不会陌生&#xff0c;该公司发行的游戏有半条命系列、反恐精英系列、求生之路系列、传送门系列、军团要塞2、Dota2。Steam平台的客户端新增…

使用docker安装redis

使用docker安装redis ①拉取镜像 docker pull redis:6.2.6② 创建容器 docker run -d --name forum-redis --restartalways -p 6379:6379 redis:6.2.6 redis-server --requirepass "dong97"③链接测试 打开Redis Desktop Manager&#xff0c;输入host、port、pas…

开源版本管理系统的搭建一:SVN服务端安装

作者&#xff1a;私语茶馆 1.Windows搭建SVN版本管理系统 点评&#xff1a;SVN本身非常简洁易用&#xff0c;VisualSVN文档支撑非常好&#xff0c;客户端TortoiseSVN非常专业。5星好评。 1.1.SVN概要和组成 背景介绍 Svn是一个开源版本管理系统&#xff0c;由CollabNet公司…

Java Map集合(二)

1. HashMap原理 1.1 HashMap的容量 HashMap中使用数组作为存储元素的桶&#xff0c;对应的内部属性为table&#xff0c;如下图所示。HashMap的内部数组不是在创建HashMap对象时初始化&#xff0c;而是在首次存入元素时进行初始化&#xff0c;以减少对内存的占用。 从源码注释中…

【STM32+HAL】三轴按键PS2摇杆

一、准备工作&#xff1a; 有关CUBEMX的初始化配置&#xff0c;参见我的另一篇blog&#xff1a;【STM32HAL】CUBEMX初始化配置 有关定时器触发ADC模式配置&#xff0c;详见【STM32HAL】ADC采集波形实现 二、所用工具&#xff1a; 1、芯片&#xff1a; STM32F407VET6 2、CUBE…

小蓝本--因式分解(习题1)讲解

这几天要备战期中&#xff0c;下一期可能要等暑假了...... 小升初的压力真是紧扣于头啊&#xff0c;为了分到一个好班&#xff0c;拼了&#xff01; 对了&#xff0c;下一期可能在寒假更&#xff0c;见谅&#xff01; 1分解因式&#xff1a; 公因式&#xff1a; 答案&#xff…

发动机台架测试起动电源为发动机台架测试提供方便操作

发动机台架测试启动电源通常是指为发动机试验设备提供电力的装置&#xff0c;它可能包括交流电源、直流电源或专用的启动发电机。在进行发动机性能测试时&#xff0c;需要稳定的电力供应来驱动各种测试设备&#xff0c;如振动台、数据采集系统等。具体到电源类型常见的有市电、…

QT:label标签/进度条的使用

文章目录 设置不同格式的文本显示图片文本对齐/自动换行/缩进/边距LCDNumber倒计时 ProgressBar进度条 设置不同格式的文本 在文本格式中&#xff0c;存在富文本&#xff0c;makedown格式的文本&#xff0c;还有纯文本&#xff0c;下面就依据这三个进行举例 #include "w…

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

从浅入深 学习 SpringCloud 微服务架构&#xff08;七&#xff09;Hystrix&#xff08;1&#xff09; 一、Hystrix&#xff1a;基于 RestTemplate 的熔断配置 1、Hystrix 介绍&#xff1a; 1&#xff09;Hystrix 是由 Netflix 开源的一个延迟和容错库&#xff0c; 用于隔离访…

nginx--配置文件

组成 主配置文件&#xff1a;nginx.conf 子配置文件&#xff1a;include conf.d/*.conf 协议相关的配置文件&#xff1a;fastcgi uwsgi scgi等 mime.types&#xff1a;⽀持的mime类型&#xff0c;MIME(Multipurpose Internet Mail Extensions)多用途互联⽹网邮件扩展类型&…

渲染 函数

DOM树 什么是渲染函数 在多数情况下&#xff0c;Vue推荐使用模板template来创建HTML。 然而在一些应用场景中&#xff0c;需要使用JavaScript创建HTML。 这时可以用渲染函数&#xff0c;它比模板更方便。 render函数的主要神秘地方就是Vue的h函数。 h()函数 h()函数是一个用于…

学习Rust的第26天:Rust中的cp

在本文中复刻了 cp 实用程序的功能&#xff0c;我想默认使其递归&#xff0c;因为每次我想复制时都输入 -R 文件夹都会觉得有点重复&#xff0c;本文代码将与前文代码保持相似&#xff0c;我们只会更改程序的核心功能和一些变量名称以匹配用例 Pseudo Code 伪代码 function cop…

C#实战—代码实现收发文件智能化

在信息化的今天&#xff0c;收发电子文档几乎是每个朋友都要经历的事情。比如班级学委和班长需要收发作业&#xff0c;企业管理者需要收发工作文件。但是&#xff01;&#xff01;&#xff01; 每到要交结果时&#xff0c;往往会发现总会有一些人没有即使交上&#xff0c;50个…
最新文章