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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值