linux下实现在程序运行时的函数替换(热补丁)
声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享。
但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的。也正因为这些错误,加深了我的学习深度。
最近因为在学习一些调试的技术,但是很少有提到如何在函数运行时实现函数替换的。
为什么会想到这一点?因为在学习调试时,难免会看到一些内核方面的调试技术,内核中的调试有一个kprobe,很强大,可以实现运行时的函数替换。其原理就是hook,钩子,但是学习了这个kprobe之后会发现,kprobe内部有检测所要钩的函数是不是属于内核空间,必须是内核函数才能实现替换。而实际上,我的工作大部分还是在应用层的,所以想要实现应用程序的热补丁技术。
一些基础的知识这边的就不展开了,需要的基础有,elf文件格式,ptrace,waitpid,应用程序间通信时的信号,汇编。
- 1、elf文件加载过程
elf简单地说是由以下四部分组成的,elf文件头,program header和section header,内容。其中program header是运行时使用的,而section header并不会被加载进程序运行空间,但他们可以在编译时被指定该段的加载地址等信息,当然一般这个链接脚本.lds是由gcc默认的。
第一步,加载elf文件头,检验文件类型版本等,重要的是找到program header的地址和header的个数,如果连接器脚本是默认的,那么elf文件头会被加载在0x804800地址处。
第二步,加载program header,接着扫描program header,找到一个类型为PT_INTERP的program header,这个header里面放着的是有关解释器的地址,这时候将解释器程序的elf文件头加载进来。一般是这样:
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
第三步,扫描program header,如果类型为PT_LOAD,则将该段加载进来。
第四步,判断是否需要解释器程序,如果需要,把解释器程序加载进来,并把程序入口设置为解释器程序的地址。否则是应用程序本身的入口。反汇编为_start标号。
第五步,设置命令行传入的参数等应用程序需要的信息。
第六步,解释器程序开始运行,加载程序需要的库,填写重定向符号表中的地址信息。
- 2.elf文件动态链接过程
上一步,解释器程序根据program header已经将应用程序的段都加载进内存了,接下来再扫描program header,找到类型为PT_DYNAMIC,这里面包含了很多由section header描述的内容,包括重定向表,符号表,字符串表等等。解释器需要这个段描述的一些信息。
DT_NEEDED描述了所需要的动态库名称,DT_REL描述了重定位表地址,DT_JMPREL描述了重定位表地址(这个表是懒惰链接使用的),DT_PLTGOT全局偏移表地址。
此时解释器程序就可以根据所需要的动态库,将其加载进内存。每一个被加载进来的库的相关信息会被记录在link_map结构中,这个结构是一个链表,保存了所有的动态信息。
其中,全局偏移表got,got[0]保存了PT_DYNAMIC的起始地址,got[1]保存link_map的地址,而link_map中就可以找到PT_DYNAMIC的起始地址,和下一个或者上一个共享文件或者可执行文件的link_map地址。
DT_REL这个重定向表中的符号必须在此时就被解析完成。
而DT_JMPREL这个重定向表中的符号可以在运行时再解析。
所有的库和符号全部解析完成之后,解释器程序就会把控制权交给可执行文件的_start。程序开始执行。
- 3.替换函数和被替换函数
被替换程序源码。
#include <stdio.h> #include <time.h> int main() { while(1){ sleep(10); printf("%d : original\n",time(0)); } }
替换新库代码。
#include <stdio.h> int newmyprint() { write(1,"hahahahahahaha",14); return 0; }
够简单明了吧,如果替换成功,目标程序将会一直输出“哈哈哈哈哈哈”。
- 4.功能函数
ptrace相关代码:
/* 读进程寄存器 */ void ptrace_readreg(int pid, struct user_regs_struct *regs) { if(ptrace(PTRACE_GETREGS, pid, NULL, regs)) printf("*** ptrace_readreg error ***\n"); /*printf("ptrace_readreg\n"); printf("%x\n",regs->ebx); printf("%x\n",regs->ecx); printf("%x\n",regs->edx); printf("%x\n",regs->esi); printf("%x\n",regs->edi); printf("%x\n",regs->ebp); printf("%x\n",regs->eax); printf("%x\n",regs->xds); printf("%x\n",regs->xes); printf("%x\n",regs->xfs); printf("%x\n",regs->xgs); printf("%x\n",regs->orig_eax); printf("%x\n",regs->eip); printf("%x\n",regs->xcs); printf("%x\n",regs->eflags); printf("%x\n",regs->esp); printf("%x\n",regs->xss);*/ } /* 写进程寄存器 */ void ptrace_writereg(int pid, struct user_regs_struct *regs) { /*printf("ptrace_writereg\n"); printf("%x\n",regs->ebx); printf("%x\n",regs->ecx); printf("%x\n",regs->edx); printf("%x\n",regs->esi); printf("%x\n",regs->edi); printf("%x\n",regs->ebp); printf("%x\n",regs->eax); printf("%x\n",regs->xds); printf("%x\n",regs->xes); printf("%x\n",regs->xfs); printf("%x\n",regs->xgs); printf("%x\n",regs->orig_eax); printf("%x\n",regs->eip); printf("%x\n",regs->xcs); printf("%x\n",regs->eflags); printf("%x\n",regs->esp); printf("%x\n",regs->xss);*/ if(ptrace(PTRACE_SETREGS, pid, NULL, regs)) printf("*** ptrace_writereg error ***\n"); } /* 关联到进程 */ void ptrace_attach(int pid) { if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { perror("ptrace_attach"); exit(-1); } waitpid(pid, NULL, /*WUNTRACED*/0); ptrace_readreg(pid, &oldregs); } /* 进程继续 */ void ptrace_cont(int pid) { int stat; if(ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) { perror("ptrace_cont"); exit(-1); } /*while(!WIFSTOPPED(stat)) waitpid(pid, &stat, WNOHANG);*/ } /* 脱离进程 */ void ptrace_detach(int pid) { ptrace_writereg(pid, &oldregs); if(ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) { perror("ptrace_detach"); exit(-1); } } /* 写指定进程地址 */ void ptrace_write(int pid, unsigned long addr, void *vptr, int len) { int count; long word; count = 0; while(count < len) { memcpy(&word, vptr + count, sizeof(word)); word = ptrace(PTRACE_POKETEXT, pid, addr + count, word); count += 4; if(errno != 0) printf("ptrace_write failed\t %ld\n", addr + count); } } /* 读指定进程 */ int ptrace_read(int pid, unsigned long addr, void *vptr, int len) { int i,count; long word; unsigned long *ptr = (unsigned long *)vptr; i = count = 0; //printf("ptrace_read addr = %x\n",addr); while (count < len) { //printf("ptrace_read addr+count = %x\n",addr + count); word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL); while(word < 0) { if(errno == 0) break; //printf("ptrace_read word = %x\n",word); perror("ptrace_read failed"); return 2; } count += 4; ptr[i++] = word; } return 0; } /* 在进程指定地址读一个字符串 */ char * ptrace_readstr(int pid, unsigned long addr) { char *str = (char *) malloc(64); int i,count; long word; char *pa; i = count = 0; pa = (char *)&word; while(i <= 60) { word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL); count += 4; if (pa[0] == 0) { str[i] = 0; break; } else str[i++] = pa[0]; if (pa[1] == 0) { str[i] = 0; break; } else str[i++] = pa[1]; if (pa[2] ==0) { str[i] = 0; break; } else str[i++] = pa[2]; if (pa[3] ==0) { str[i] = 0; break; } else str[i++] = pa[3]; } return str; } /* 将指定数据压入进程堆栈并返回堆栈指针 */ void * ptrace_push(int pid, void *paddr, int size) { unsigned long esp; struct user_regs_struct regs; ptrace_readreg(pid, ®s); esp = regs.esp; esp -= size; esp = esp - esp % 4; regs.esp = esp; ptrace_writereg(pid, ®s); ptrace_write(pid, esp, paddr, size); return (void *)esp; } /* 在进程内调用指定地址的函数 */ void ptrace_call(int pid, unsigned long addr) { void *pc; struct user_regs_struct regs; int stat; void *pra; pc = (void *) 0x41414140; pra = ptrace_push(pid, &pc, sizeof(pc)); ptrace_readreg(pid, ®s); regs.eip = addr; ptrace_writereg(pid, ®s); ptrace_cont(pid); //while(WIFSIGNALED(stat)) // waitpid(pid, &stat, WNOHANG); }
这里面的东西我就不展开了,对ptrace的学习,请自行man。
/* 因为应用程序可能不存在hash表,所以通过读取源文件的section header获取符号表的入口数, 其实是被误导了,但也学习了hash表的作用,用来快速查找符号表中的信息和字符串表中的信息 */ /*int getnchains(int pid,unsigned long base_addr) { printf("getnchains enter \n"); Elf32_Ehdr *ehdr = (Elf32_Ehdr *) malloc(sizeof(Elf32_Ehdr)); Elf32_Shdr *shdr = (Elf32_Shdr *)malloc(sizeof(Elf32_Shdr)); unsigned long shdr_addr; int i = 0; int fd; char filename[1024] = {0}; ptrace_read(pid, base_addr, ehdr, sizeof(Elf32_Ehdr)); shdr_addr = base_addr + ehdr->e_shoff; //printf("getnchains ehdr->e_shoff\t %p\n", ehdr->e_shoff); snprintf(filename, sizeof(filename), "/proc/%d/exe", pid); fd = open(filename, O_RDONLY); if (lseek(fd, ehdr->e_shoff, SEEK_SET) < 0) exit(-1); /*while(i<ehdr->e_shnum) { read(fd, shdr, ehdr->e_shentsize); printf("getnchains i = %d\n",i); printf("getnchains shdr->sh_type = %x\n",shdr->sh_type); printf("getnchains shdr->sh_name = %x\n",shdr->sh_name); printf("getnchains shdr->sh_size = %x\n",shdr->sh_size); printf("getnchains shdr->sh_entsize = %x\n",shdr->sh_entsize); i++; } while(shdr->sh_type != SHT_SYMTAB) read(fd, shdr, ehdr->e_shentsize); nchains = shdr->sh_size/shdr->sh_entsize; //printf("getnchains shdr->sh_type = %d\n",shdr->sh_type); //printf("getnchains shdr->sh_name = %d\n",shdr->sh_name); //printf("getnchains shdr->sh_size = %d\n",shdr->sh_size); //printf("getnchains shdr->sh_entsize = %d\n",shdr->sh_entsize); //printf("getnchains nchains = %x\n",nchains); close(fd); free(ehdr); free(shdr); printf("getnchains exit \n"); } */ /* 取得指向link_map链表首项的指针 */ struct link_map * get_linkmap(int pid) { Elf32_Ehdr *ehdr = (Elf32_Ehdr *) malloc(sizeof(Elf32_Ehdr)); Elf32_Phdr *phdr = (Elf32_Phdr *) malloc(sizeof(Elf32_Phdr)); Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn)); Elf32_Word got; struct link_map *map = (struct link_map *)malloc(sizeof(struct link_map)); int i = 1; unsigned long tmpaddr; ptrace_read(pid, IMAGE_ADDR, ehdr, sizeof(Elf32_Ehdr)); phdr_addr = IMAGE_ADDR + ehdr->e_phoff; printf("phdr_addr\t %p\n", phdr_addr); ptrace_read(pid, phdr_addr, phdr, sizeof(Elf32_Phdr)); while(phdr->p_type != PT_DYNAMIC) ptrace_read(pid, phdr_addr += sizeof(Elf32_Phdr), phdr,sizeof(Elf32_Phdr)); dyn_addr = phdr->p_vaddr; printf("dyn_addr\t %p\n", dyn_addr); ptrace_read(pid, dyn_addr, dyn, sizeof(Elf32_Dyn)); while(dyn->d_tag != DT_PLTGOT) { tmpaddr = dyn_addr + i * sizeof(Elf32_Dyn); //printf("get_linkmap tmpaddr = %x\n",tmpaddr); ptrace_read(pid,tmpaddr, dyn, sizeof(Elf32_Dyn)); i++; } got = (Elf32_Word)dyn->d_un.d_ptr; got += 4; //printf("GOT\t\t %p\n", got); ptrace_read(pid, got, &map_addr, 4); printf("map_addr\t %p\n", map_addr); map = map_addr; //ptrace_read(pid, map_addr, map, sizeof(struct link_map)); free(ehdr); free(phdr); free(dyn); return map; } /* 取得给定link_map指向的SYMTAB、STRTAB、HASH、JMPREL、PLTRELSZ、RELAENT、RELENT信息 这些地址信息将被保存到全局变量中,以方便使用 */ void get_sym_info(int pid, struct link_map *lm) { Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn)); unsigned long dyn_addr; //printf("get_sym_info lm = %x\n",lm); //printf("get_sym_info lm->l_ld's offset = %x\n",&((struct link_map *)0)->l_ld); //printf("get_sym_info &lm->l_ld = %x\n",&(lm->l_ld)); //dyn_addr = (unsigned long)&(lm->l_ld); //进入被跟踪进程获取动态节的地址 ptrace_read(pid,&(lm->l_ld) , &dyn_addr, sizeof(dyn_addr)); ptrace_read(pid,&(lm->l_addr) , &link_addr, sizeof(dyn_addr)); ptrace_read(pid, dyn_addr, dyn, sizeof(Elf32_Dyn)); //if(link_addr == 0) // getnchains(pid,IMAGE_ADDR); /*else getnchains(pid,link_addr);*/ while(dyn->d_tag != DT_NULL){ //printf("get_sym_info dyn->d_tag = %x\n",dyn->d_tag); //printf("get_sym_info dyn->d_un.d_ptr = %x\n",dyn->d_un.d_ptr); switch(dyn->d_tag) { case DT_SYMTAB: symtab = dyn->d_un.d_ptr; break; case DT_STRTAB: strtab = dyn->d_un.d_ptr; break; /*case DT_HASH://可能不存在哈希表,此时nchains是错误的,这个值可以通过符号表得到 //printf("get_sym_info hash table's addr = %x\n",dyn->d_un.d_ptr); //printf("get_sym_info symtbl's entry = %x\n",(dyn->d_un.d_ptr) + 4); ptrace_read(pid, (dyn->d_un.d_ptr) + 4,&nchains, sizeof(nchains)); break;*/ case DT_JMPREL: jmprel = dyn->d_un.d_ptr; break; case DT_PLTRELSZ: totalrelsize = dyn->d_un.d_val; break; case DT_RELAENT: relsize = dyn->d_un.d_val; break; case DT_RELENT: relsize = dyn->d_un.d_val; break; case DT_REL: reldyn = dyn->d_un.d_ptr; break; case DT_RELSZ: reldynsz = dyn->d_un.d_val; break; } ptrace_read(pid, dyn_addr += sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn)); } //printf("get_sym_info link_addr = %x\n",link_addr); //printf("get_sym_info symtab = %x\n",symtab); //printf("get_sym_info relsize = %x\n",relsize); //printf("get_sym_info reldyn = %x\n",reldyn); //printf("get_sym_info totalrelsize = %x\n",totalrelsize); //printf("get_sym_info jmprel = %x\n",jmprel); //printf("get_sym_info nchains = %x\n",nchains); //printf("get_sym_info strtab = %x\n",strtab); nrels = totalrelsize / relsize; nreldyns = reldynsz/relsize; //printf("get_sym_info nreldyns = %d\n",nreldyns); //printf("get_sym_info nrels = %d\n",nrels); free(dyn); printf("get_sym_info exit\n"); } /* 在指定的link_map指向的符号表查找符号,它仅仅是被上面的find_symbol使用 */ unsigned long find_symbol_in_linkmap(int pid, struct link_map *lm, char *sym_name) { Elf32_Sym *sym = (Elf32_Sym *) malloc(sizeof(Elf32_Sym)); int i = 0; char *str; unsigned long ret; int flags = 0; get_sym_info(pid, lm); do{ if(ptrace_read(pid, symtab + i * sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym))) return 0; i++; //printf("find_symbol_in_linkmap sym->st_name = %x\tsym->st_size = %x\tsym->st_value = %x\n",sym->st_name,sym->st_size,sym->st_value); //printf("find_symbol_in_linkmap Elf32_Sym's size = %d\n",sizeof(Elf32_Sym)); //printf("\nfind_symbol_in_linkmap sym->st_name = %x\n",sym->st_name); if (!sym->st_name && !sym->st_size && !sym->st_value)//全为0是符号表的第一项 continue; //printf("\nfind_symbol_in_linkmap strtab = %x\n",strtab); str = (char *) ptrace_readstr(pid, strtab + sym->st_name); //printf("\nfind_symbol_in_linkmap str = %s\n",str); //printf("\nfind_symbol_in_linkmap sym->st_value = %x\n",sym->st_value); if (strcmp(str, sym_name) == 0) { printf("\nfind_symbol_in_linkmap str = %s\n",str); printf("\nfind_symbol_in_linkmap sym->st_value = %x\n",sym->st_value); free(str); if(sym->st_value == 0)//值为0代表这个符号本身就是重定向的内容 continue; flags = 1; //str = ptrace_readstr(pid, (unsigned long)lm->l_name); //printf("find_symbol_in_linkmap lib name [%s]\n", str); //free(str); break; } free(str); }while(1); if (flags != 1) ret = 0; else ret = link_addr + sym->st_value; free(sym); return ret; } /* 解析指定符号 */ unsigned long find_symbol(int pid, struct link_map *map, char *sym_name) { struct link_map *lm = map; unsigned long sym_addr; char *str; unsigned long tmp; //sym_addr = find_symbol_in_linkmap(pid, map, sym_name); //return 0; //if (sym_addr) // return sym_addr; //printf("\nfind_symbol map = %x\n",map); //ptrace_read(pid,(char *)map+12,&tmp,4); //lm = tmp; //printf("find_symbol lm = %x\n",lm); //ptrace_read(pid, (unsigned long)map->l_next, lm, sizeof(struct link_map)); sym_addr = find_symbol_in_linkmap(pid, lm, sym_name); while(!sym_addr ) { ptrace_read(pid, (char *)lm+12, &tmp, 4);//获取下一个库的link_map地址 if(tmp == 0) return 0; lm = tmp; //printf("find_symbol lm = %x\n",lm); /*str = ptrace_readstr(pid, (unsigned long)lm->l_name); if(str[0] == '/0') continue; printf("[%s]\n", str); free(str);*/ if ((sym_addr = find_symbol_in_linkmap(pid, lm, sym_name))) break; } return sym_addr; } /* 查找符号的重定位地址 */ unsigned long find_sym_in_rel(int pid, char *sym_name) { Elf32_Rel *rel = (Elf32_Rel *) malloc(sizeof(Elf32_Rel)); Elf32_Sym *sym = (Elf32_Sym *) malloc(sizeof(Elf32_Sym)); int i; char *str; unsigned long ret; struct link_map *lm; lm = map_addr; //get_dyn_info(pid); do{ get_sym_info(pid,lm); ptrace_read(pid, (char *)lm+12, &lm, 4); //首先查找过程连接的重定位表 for(i = 0; i< nrels ;i++) { ptrace_read(pid, (unsigned long)(jmprel + i * sizeof(Elf32_Rel)), rel, sizeof(Elf32_Rel)); if(ELF32_R_SYM(rel->r_info)) { ptrace_read(pid, symtab + ELF32_R_SYM(rel->r_info) * sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym)); str = ptrace_readstr(pid, strtab + sym->st_name); if (strcmp(str, sym_name) == 0) { if(sym->st_value != 0){ free(str); continue; } modifyflag = 1; free(str); break; } free(str); } } if(modifyflag == 1) break; //没找到的话,再找在链接时就重定位的重定位表 for(i = 0; i< nreldyns;i++) { ptrace_read(pid, (unsigned long)(reldyn+ i * sizeof(Elf32_Rel)), rel, sizeof(Elf32_Rel)); if(ELF32_R_SYM(rel->r_info)) { ptrace_read(pid, symtab + ELF32_R_SYM(rel->r_info) * sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym)); str = ptrace_readstr(pid, strtab + sym->st_name); if (strcmp(str, sym_name) == 0) { if(sym->st_value != 0){ free(str); continue; } modifyflag = 2; free(str); break; } free(str); } } if(modifyflag == 2) break; }while(lm); //printf("find_sym_in_rel flags = %d\n",flags); if (modifyflag == 0) ret = 0; else ret = link_addr + rel->r_offset; //printf("find_sym_in_rel link_addr = %x\t sym->st_value = %x\n",link_addr , sym->st_value); free(rel); free(sym); return ret; } /* 在进程自身的映象中(即不包括动态共享库,无须遍历link_map链表)获得各种动态信息 */ /*void get_dyn_info(int pid) { Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn)); int i = 0; ptrace_read(pid, dyn_addr + i * sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn)); i++; while(dyn->d_tag){ switch(dyn->d_tag) { case DT_SYMTAB: //puts("DT_SYMTAB"); symtab = dyn->d_un.d_ptr; break; case DT_STRTAB: strtab = dyn->d_un.d_ptr; //puts("DT_STRTAB"); break; case DT_JMPREL: jmprel = dyn->d_un.d_ptr; //puts("DT_JMPREL"); //printf("jmprel\t %p\n", jmprel); break; case DT_PLTRELSZ: totalrelsize = dyn->d_un.d_val; //puts("DT_PLTRELSZ"); break; case DT_RELAENT: relsize = dyn->d_un.d_val; //puts("DT_RELAENT"); break; case DT_RELENT: relsize = dyn->d_un.d_val; //puts("DT_RELENT"); break; } ptrace_read(pid, dyn_addr + i * sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn)); i++; } nrels = totalrelsize / relsize; free(dyn); }*/ /*void call_dl_open(int pid, unsigned long addr, char *libname) { void *pRLibName; struct user_regs_struct regs; /* 先找个空间存放要装载的共享库名,我们可以简单的把它放入堆栈 pRLibName = ptrace_push(pid, libname, strlen(libname) + 1); /* 设置参数到寄存器 ptrace_readreg(pid, ®s); regs.eax = (unsigned long) pRLibName; regs.ecx = 0x0; regs.edx = RTLD_LAZY; ptrace_writereg(pid, ®s); /* 调用_dl_open ptrace_call(pid, addr); puts("call _dl_open ok"); }*/ /*#define RTLD_LAZY 0x00001 #define RTLD_NOW 0x00002 #define RTLD_BINDING_MASK 0x3 #define RTLD_NOLOAD 0x00004 #define RTLD_DEEPBIND 0x00008 #define RTLD_GLOBAL 0x00100 #define RTLD_LOCAL 0 #define RTLD_NODELETE 0x01000 */ void call__libc_dlopen_mode(int pid, unsigned long addr, char *libname) { void *plibnameaddr; //printf("call__libc_dlopen_mode libname = %s\n",libname); //printf("call__libc_dlopen_mode addr = %x\n",addr); //将需要加载的共享库地址压栈 plibnameaddr = ptrace_push(pid, libname, strlen(libname) + 1); ptrace_push(pid,&mode,sizeof(int)); ptrace_push(pid,&plibnameaddr,sizeof(plibnameaddr)); /* 调用__libc_dlopen_mode */ ptrace_call(pid, addr); } void call_printf(int pid, unsigned long addr, char *string) { void *paddr; paddr = ptrace_push(pid, string, strlen(string) + 1); ptrace_push(pid,&paddr,sizeof(paddr)); ptrace_call(pid, addr); }
作者所做的修改,读者可以对比文章最后的连接中的代码。
这边对于程序的具体解释,就不具体展开了。
需要注意的是,原来是采用_dl_open的方式加载库函数,但是ld库并没有这个符号导出。而libc库中导出了一个可以加载库的__libc_dlopen_mode函数。
- 5.主函数
先说一下流程,
a.获取被跟踪进程的link_map地址
b.根据link_map给出的信息,搜索符号表,遍历每一个link_map中的符号表,直到找到想要找的符号。这里是printf或者__libc_dlopen_mode函数
c.将库路径包括库名称传递给调用__libc_dlopen_mode的函数,该函数即call__libc_dlopen_mode会把__libc_dlopen_mode函数需要的参数,路径和加载方式压栈,在让被跟踪进
程开始运行之前,压入一个非法地址,当__libc_dlopen_mode返回时返回到一个非法地址时,就会发生中断,此时跟踪进程可以waitpid跟踪到。好,设置寄存器,并让被跟踪进程开
始运行。打开库之后,被跟踪进程因中断而被跟踪进程再次获得控制权。
d.再一次根据之前保存的link_map信息,当然完全可以直接用上一次搜索结果结束之后的link_map往后找,因为新库一定在最后,但是本文还是从头开始找,找到新库中的
newmyprint地址。
e.还是根据link_map信息查找printf的重定向地址,在rel.dyn节中,有关这个rel.dyn和rel.plt等节之间的关系,可以看我的其他博文。
f.将newmyprint的地址填入printf的重定向地址。
g.将被跟踪进程原先的寄存器设置回去,释放控制。
h.被跟踪进程开始输出“哈哈哈哈哈”。
上源码:
int main(int argc, char *argv[]) { int pid; struct link_map *map; char sym_name[256]; unsigned long sym_addr; unsigned long new_addr,old_addr,rel_addr; int status = 0; char libpath[1024]; char oldfunname[128]; char newfunname[128]; //mode = atoi(argv[2]); if(argc < 5){ printf("usage : ./injso pid libpath oldfunname newfunname\n"); exit(-1); } /* 从命令行取得目标进程PID*/ pid = atoi(argv[1]); /* 从命令行取得新库名称*/ memset(libpath,0,sizeof(libpath)); memcpy(libpath,argv[2],strlen(argv[2])); /* 从命令行取得旧函数的名称*/ memset(oldfunname,0,sizeof(oldfunname)); memcpy(oldfunname,argv[3],strlen(argv[3])); /* 从命令行取得新函数的名称*/ memset(newfunname,0,sizeof(newfunname)); memcpy(newfunname,argv[4],strlen(argv[4])); printf("main pid = %d\n",pid); printf("main libpath : %s\n",libpath); printf("main oldfunname : %s\n",oldfunname); printf("main newfunname : %s\n",newfunname); /* 关联到目标进程*/ ptrace_attach(pid); /* 得到指向link_map链表的指针 */ map = get_linkmap(pid); /* get_linkmap */ sym_addr = find_symbol(pid, map, "printf"); printf("found printf at addr %p\n", sym_addr); if(sym_addr == 0) goto detach; call_printf(pid,sym_addr,"injso successed\n"); waitpid(pid,&status,0); printf("status = %x\n",status); /*ptrace_writereg(pid, &oldregs); ptrace_cont(pid); waitpid(pid,&status,0); //printf("status = %x\n",status); //ptrace_readreg(pid, &oldregs); //oldregs.eip = 0x8048414; //ptrace_writereg(pid, &oldregs); ptrace_cont(int pid)(pid); ptrace_detach(pid); exit(0);*/ /* 发现__libc_dlopen_mode,并调用它 */ sym_addr = find_symbol(pid, map, "__libc_dlopen_mode"); /* call _dl_open */ printf("found __libc_dlopen_mode at addr %p\n", sym_addr); if(sym_addr == 0) goto detach; call__libc_dlopen_mode(pid, sym_addr,libpath); /* 注意装载的库地址 */ //while(1); waitpid(pid,&status,0); /* 找到新函数的地址 */ strcpy(sym_name, newfunname); /* intercept */ sym_addr = find_symbol(pid, map, sym_name); printf("%s addr\t %p\n", sym_name, sym_addr); if(sym_addr == 0) goto detach; /* 找到旧函数在重定向表的地址 */ strcpy(sym_name, oldfunname); rel_addr = find_sym_in_rel(pid, sym_name); printf("%s rel addr\t %p\n", sym_name, rel_addr); if(rel_addr == 0) goto detach; /* 找到用于保存read地址的指针 */ //strcpy(sym_name, "oldread"); //old_addr = find_symbol(pid, map, sym_name); //printf("%s addr\t %p\n", sym_name, old_addr); /* 函数重定向 */ puts("intercept..."); /* intercept */ //ptrace_read(pid, rel_addr, &new_addr, sizeof(new_addr)); //ptrace_write(pid, old_addr, &new_addr, sizeof(new_addr)); //rel_addr = 0x8048497;如果是静态地址,也就是未导出该符号地址,那么只能通过反汇编先找到该函数被调用的地方,将这个地方的跳转地址修改 if(modifyflag == 2) sym_addr = sym_addr - rel_addr - 4; printf("main modify sym_addr = %x\n",sym_addr); ptrace_write(pid, rel_addr, &sym_addr, sizeof(sym_addr)); puts("injectso ok"); detach: printf("prepare to detach\n"); ptrace_detach(pid); return 0; }
这里面有一个很重要的地方,如果不先在目标进程中调用printf就不能够调用__lib_dlopen_mode成功,这个原因很奇怪,根据当时的core文件来看崩溃在了下面的这个函数,原因是_dl_open_hook这个全局变量为0,但实际上运行过printf之后,这个_dl_open_hook还是0。这个有待后续检验。
void * __libc_dlsym (void *map, const char *name) { struct do_dlsym_args args; args.map = map; args.name = name; #ifdef SHARED if (__builtin_expect (_dl_open_hook != NULL, 0)) return _dl_open_hook->dlsym (map, name); #endif return (dlerror_run (do_dlsym, &args) ? NULL : (void *) (DL_SYMBOL_ADDRESS (args.loadbase, args.ref))); }
运行结果:
root@leo-desktop:injso# ./test
1467364356 : original
injso successed
hahahahahahahahahahahahahaha
- 6.如何替换未导出符号的地址
被替换函数源码:
#include <stdio.h> //int fun2(); int fun1() { printf("fun1\n"); // fun2(); } int main() { signed int i = 0x40011673 ; i = i - 0x4001172d ; printf("i = %x\n",i); while(1){ i = fun1(); sleep(10); } return 1; }
这个怎么来替换fun1函数的地址呢?
首先反汇编得到main的机器码,如下,
08048468 <main>: 8048468: 55 push %ebp 8048469: 89 e5 mov %esp,%ebp 804846b: 83 e4 f0 and $0xfffffff0,%esp 804846e: 83 ec 20 sub $0x20,%esp 8048471: c7 44 24 1c 73 16 01 movl $0x40011673,0x1c(%esp) 8048478: 40 8048479: 81 6c 24 1c 2d 17 01 subl $0x4001172d,0x1c(%esp) 8048480: 40 8048481: b8 75 85 04 08 mov $0x8048575,%eax 8048486: 8b 54 24 1c mov 0x1c(%esp),%edx 804848a: 89 54 24 04 mov %edx,0x4(%esp) 804848e: 89 04 24 mov %eax,(%esp) 8048491: e8 ce fe ff ff call 8048364 <printf@plt> 8048496: e8 b9 ff ff ff call 8048454 <fun1> 804849b: 89 44 24 1c mov %eax,0x1c(%esp) 804849f: c7 04 24 0a 00 00 00 movl $0xa,(%esp) 80484a6: e8 c9 fe ff ff call 8048374 <sleep@plt> 80484ab: eb e9 jmp 8048496 <main+0x2e> 80484ad: 90 nop 80484ae: 90 nop 80484af: 90 nop
可以看到在地址0x8048496处的机器码是跳转到fun1函数的,那么这个ffffffb9就是call的操作数,操作数地址0x8048497,也就是说把这个地址中的数值改掉就可以了,有关这个call或者jmp的地址计算可以查看我的另外一篇博文。
有关这个如何跳转的方法,已经在主函数的代码中给出了,但是被我注释掉了,大家感兴趣的话,可以自己试试。
效果:
root@leo-desktop:lib2lib# ./a.out
i = ffffff46
fun1
injso successed
hahahahahahaha^C
这里面的无关代码,大家仔细看,是为了证明call的函数地址计算方式的。
- 7.总结
那么讲到现在的话,已经实现了不管函数符号是否导出都可以实现运行时替换的代码。
这里面主要的技术是,elf文件格式,运行时加载的过程,跳转地址的计算,运行时链接的过程,也就是plt表(当然这个也可以从我的另一篇博文中看到)。
比较遗憾的是有关那个奔溃,有网友如果找到了原因,请回复下,3q。当然我也会自己再研究下。
最后补上全局变量和头文件:
#include <stdio.h> #include <string.h> #include <elf.h> #include <sys/types.h> #include <stdio.h> #include <sys/ptrace.h> #include <sys/wait.h> #include <sys/errno.h> #include <sys/user.h> #include <link.h> #include <sys/stat.h> #include <fcntl.h> #include <bits/dlfcn.h> #define IMAGE_ADDR 0x08048000 int mode = 2; struct user_regs_struct oldregs; Elf32_Addr phdr_addr; Elf32_Addr dyn_addr; Elf32_Addr map_addr; Elf32_Addr symtab; Elf32_Addr strtab; Elf32_Addr jmprel; Elf32_Addr reldyn; Elf32_Word reldynsz; Elf32_Word totalrelsize; Elf32_Word relsize; unsigned long link_addr; int nrels; int nreldyns; //int nchains; int modifyflag = 0; /*char libpath[128] = "/mnt/hgfs/svnroot/test/injectsov2/prj_linux/so.so";*/
- 8.修正
针对在调用__libc_dlopen_mode函数之前需要调用printf的问题,终于让我在晚上解决了。
首先,我尝试了调用其他函数而不是printf函数,发现效果一样,包括第一次是调用__libc_dlopen_mode,第二次对该函数的调用都可以成功。
其次,那么现在问题就集中在了这两个__libc_dlopen_mode调用之间的差别在哪里,程序段肯定是一致的,栈也是一致的,而堆空间未使用,还有一个重要的因素,那就是寄存器。
最后,发现在调用__libc_dlopen_mode前,有四个寄存器不同,分别是eax,orig_eax,eflags和esp。我一开始认为,通用寄存器eax和orig_eax不会对程序的执行造成影响。但是通过实验,仅调一次__libc_dlopen_mode,部分寄存器赋正确执行时的值,发现对eax和orig_eax被赋于正确执行时的值时,程序可以正常运行,而且不仅仅必须是一种值,比如eax可以是0,1,0xffffffff,很多值都可以,但是被赋予0xfffffdfc和0xfffffdff等值时会失败,试验过并不是因为d这一位决定的,0xfffffdf0或者d00是可以运行成功的。
(gdb) disassemble __libc_dlopen_mode Dump of assembler code for function __libc_dlopen_mode: 0x00232640 <+0>: push %ebp 0x00232641 <+1>: mov %esp,%ebp 0x00232643 <+3>: sub $0x1c,%esp 0x00232646 <+6>: mov %ebx,-0x8(%ebp) 0x00232649 <+9>: mov 0x8(%ebp),%eax 0x0023264c <+12>: call 0x144a0f 0x00232651 <+17>: add $0x519a3,%ebx 0x00232657 <+23>: mov 0xc(%ebp),%edx 0x0023265a <+26>: mov %esi,-0x4(%ebp) 0x0023265d <+29>: mov %eax,-0x14(%ebp) 0x00232660 <+32>: mov %edx,-0x10(%ebp) 0x00232663 <+35>: mov 0x354c(%ebx),%esi 0x00232669 <+41>: test %esi,%esi
在实验中,还发现对eax赋于不正确的值时,当时忘了记了,还让程序跑飞了。崩了,但是新库已经加载上了。所以这个函数替换还是有一定的风险,或者说libc库本身存在一定的bug。
所以现在问题找到了,在于eax和orig_eax上,但是对__libc_dlopen_mode反汇编发现,eax在函数开头就被赋予了通过栈传递的参数2的值,所以eax不应该影响程序的运行,但实际上影响了,这一点让我觉得很奇怪,如果有任何网友对这个原因知晓的话,麻烦回复,万分感谢。
linux共享库注射地址:http://www.docin.com/p-634172083.html
__simple原创
转载请注明出处