公司怎么注册网站免费,天眼查询官网在线入口,建设规划许可证公示网站,网站空间租赁linux运行进程热补丁#xff08;一#xff09;之函数替换_linux 热补丁的实现-CSDN博客
一、实现目标 在Linux环境下#xff08;x86_64#xff09;对正在运行进程的函数替换#xff0c;不改变该进程的可执行文件内容#xff0c;通过使用汇编指令JMP完成运行中进程的函数…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 #includestdlib.h #includestring.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(jmpbuf1, 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 --colorauto 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 putsplt 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 putsplt 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 sleepplt 4005c7: eb e5 jmp 4005ae main0xf 4005c9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) ... ... 3. gdb attach myprint查看myprint函数的汇编代码 3.1 myprint进程while循环函数跳转到指令 main15的位置: 0x00000000004005c7 40: jmp 0x4005ae main15 1 3.2 main15的位置为执行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 sleepplt 0x00000000004005c7 40: jmp 0x4005ae main15 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 putsplt 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 putsplt 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); //打印相关地址信息 /* addr20x40058e addr0x40057d 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 */ //拷贝内存jmpbuf1地址开始的4sizeof(int)字节到jmp_relative // jmpbuf 0xffffffe9 0c00 0000 memcpy(jmpbuf1, 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)) //addr0x40057d (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 addr0x40057d // 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))); } //i0 lv[0] 894855ff 0x40057c //i1 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]); } //valjmpbuf 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; }