原帖地址:

声明:以下的代码成果,是参考了网上的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-&gt;ebx);printf("%x\n",regs-&gt;ecx);printf("%x\n",regs-&gt;edx);printf("%x\n",regs-&gt;esi);printf("%x\n",regs-&gt;edi);printf("%x\n",regs-&gt;ebp);printf("%x\n",regs-&gt;eax);printf("%x\n",regs-&gt;xds);printf("%x\n",regs-&gt;xes);printf("%x\n",regs-&gt;xfs);printf("%x\n",regs-&gt;xgs);printf("%x\n",regs-&gt;orig_eax);printf("%x\n",regs-&gt;eip);printf("%x\n",regs-&gt;xcs);printf("%x\n",regs-&gt;eflags);printf("%x\n",regs-&gt;esp);printf("%x\n",regs-&gt;xss);*/}

/* 写进程寄存器 */

void ptrace_writereg(int pid, struct user_regs_struct *regs)
{/*printf("ptrace_writereg\n");printf("%x\n",regs-&gt;ebx);printf("%x\n",regs-&gt;ecx);printf("%x\n",regs-&gt;edx);printf("%x\n",regs-&gt;esi);printf("%x\n",regs-&gt;edi);printf("%x\n",regs-&gt;ebp);printf("%x\n",regs-&gt;eax);printf("%x\n",regs-&gt;xds);printf("%x\n",regs-&gt;xes);printf("%x\n",regs-&gt;xfs);printf("%x\n",regs-&gt;xgs);printf("%x\n",regs-&gt;orig_eax);printf("%x\n",regs-&gt;eip);printf("%x\n",regs-&gt;xcs);printf("%x\n",regs-&gt;eflags);printf("%x\n",regs-&gt;esp);printf("%x\n",regs-&gt;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) &lt; 0) {perror("ptrace_attach");exit(-1);}waitpid(pid, NULL, /*WUNTRACED*/0);   ptrace_readreg(pid, &amp;oldregs);
}

/* 进程继续 */

void ptrace_cont(int pid)
{int stat;if(ptrace(PTRACE_CONT, pid, NULL, NULL) &lt; 0) {perror("ptrace_cont");exit(-1);}/* while(!WIFSTOPPED(stat))waitpid(pid, &amp;stat, WNOHANG); */
}

/* 脱离进程 */

void ptrace_detach(int pid)
{ptrace_writereg(pid, &amp;oldregs);if(ptrace(PTRACE_DETACH, pid, NULL, NULL) &lt; 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 &lt; len) {memcpy(&amp;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 &lt; len) {//printf("ptrace_read addr+count = %x\n",addr + count);word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);while(word &lt; 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 *)&amp;word;while(i &lt;= 60) {word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);count += 4;if (pa[0] == 0) {str[i] = 0;break;}elsestr[i++] = pa[0];if (pa[1] == 0) {str[i] = 0;break;}elsestr[i++] = pa[1];if (pa[2] ==0) {str[i] = 0;break;}elsestr[i++] = pa[2];if (pa[3] ==0) {str[i] = 0;break;}elsestr[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, &amp;regs);esp = regs.esp;esp -= size;esp = esp - esp % 4;regs.esp = esp;ptrace_writereg(pid, &amp;regs);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, &amp;pc, sizeof(pc));ptrace_readreg(pid, &amp;regs);regs.eip = addr;ptrace_writereg(pid, &amp;regs);ptrace_cont(pid);//while(WIFSIGNALED(stat))// waitpid(pid, &amp;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-&gt;e_shoff;//printf("getnchains ehdr-&gt;e_shoff\t %p\n", ehdr-&gt;e_shoff);snprintf(filename, sizeof(filename), "/proc/%d/exe", pid);fd = open(filename, O_RDONLY);if (lseek(fd, ehdr-&gt;e_shoff, SEEK_SET) &lt; 0) exit(-1);/*while(i&lt;ehdr-&gt;e_shnum){read(fd, shdr, ehdr-&gt;e_shentsize);printf("getnchains i = %d\n",i);printf("getnchains shdr-&gt;sh_type = %x\n",shdr-&gt;sh_type);printf("getnchains shdr-&gt;sh_name = %x\n",shdr-&gt;sh_name);printf("getnchains shdr-&gt;sh_size = %x\n",shdr-&gt;sh_size);printf("getnchains shdr-&gt;sh_entsize = %x\n",shdr-&gt;sh_entsize);i++;}while(shdr-&gt;sh_type != SHT_SYMTAB)read(fd, shdr, ehdr-&gt;e_shentsize);nchains = shdr-&gt;sh_size/shdr-&gt;sh_entsize;//printf("getnchains shdr-&gt;sh_type = %d\n",shdr-&gt;sh_type);//printf("getnchains shdr-&gt;sh_name = %d\n",shdr-&gt;sh_name);//printf("getnchains shdr-&gt;sh_size = %d\n",shdr-&gt;sh_size);//printf("getnchains shdr-&gt;sh_entsize = %d\n",shdr-&gt;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-&gt;e_phoff;printf("phdr_addr\t %p\n", phdr_addr);ptrace_read(pid, phdr_addr, phdr, sizeof(Elf32_Phdr));while(phdr-&gt;p_type != PT_DYNAMIC)ptrace_read(pid, phdr_addr += sizeof(Elf32_Phdr), phdr,sizeof(Elf32_Phdr));dyn_addr = phdr-&gt;p_vaddr;printf("dyn_addr\t %p\n", dyn_addr);ptrace_read(pid, dyn_addr, dyn, sizeof(Elf32_Dyn));while(dyn-&gt;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-&gt;d_un.d_ptr;got += 4;//printf("GOT\t\t %p\n", got);ptrace_read(pid, got, &amp;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-&gt;l_ld's offset = %x\n",&amp;((struct link_map *)0)-&gt;l_ld);//printf("get_sym_info &amp;lm-&gt;l_ld = %x\n",&amp;(lm-&gt;l_ld));//dyn_addr = (unsigned long)&amp;(lm-&gt;l_ld);//进入被跟踪进程获取动态节的地址   ptrace_read(pid,&amp;(lm-&gt;l_ld) , &amp;dyn_addr, sizeof(dyn_addr));ptrace_read(pid,&amp;(lm-&gt;l_addr) , &amp;link_addr, sizeof(dyn_addr));ptrace_read(pid, dyn_addr, dyn, sizeof(Elf32_Dyn));//if(link_addr == 0)//  getnchains(pid,IMAGE_ADDR);/*elsegetnchains(pid,link_addr);*/while(dyn-&gt;d_tag != DT_NULL){//printf("get_sym_info dyn-&gt;d_tag = %x\n",dyn-&gt;d_tag);//printf("get_sym_info dyn-&gt;d_un.d_ptr = %x\n",dyn-&gt;d_un.d_ptr);switch(dyn-&gt;d_tag){case DT_SYMTAB:symtab = dyn-&gt;d_un.d_ptr;break;case DT_STRTAB:strtab = dyn-&gt;d_un.d_ptr;break;/*case DT_HASH://可能不存在哈希表,此时nchains是错误的,这个值可以通过符号表得到//printf("get_sym_info hash table's addr = %x\n",dyn-&gt;d_un.d_ptr);//printf("get_sym_info symtbl's entry = %x\n",(dyn-&gt;d_un.d_ptr) + 4);ptrace_read(pid, (dyn-&gt;d_un.d_ptr) + 4,&amp;nchains, sizeof(nchains));break;*/case DT_JMPREL:jmprel = dyn-&gt;d_un.d_ptr;break;case DT_PLTRELSZ:totalrelsize = dyn-&gt;d_un.d_val;break;case DT_RELAENT:relsize = dyn-&gt;d_un.d_val;break;case DT_RELENT:relsize = dyn-&gt;d_un.d_val;break;case DT_REL:reldyn = dyn-&gt;d_un.d_ptr;      break;case DT_RELSZ:reldynsz = dyn-&gt;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-&gt;st_name = %x\tsym-&gt;st_size = %x\tsym-&gt;st_value = %x\n",sym-&gt;st_name,sym-&gt;st_size,sym-&gt;st_value);//printf("find_symbol_in_linkmap Elf32_Sym's size = %d\n",sizeof(Elf32_Sym));//printf("\nfind_symbol_in_linkmap sym-&gt;st_name = %x\n",sym-&gt;st_name);        if (!sym-&gt;st_name &amp;&amp; !sym-&gt;st_size &amp;&amp; !sym-&gt;st_value)//全为0是符号表的第一项continue;//printf("\nfind_symbol_in_linkmap strtab = %x\n",strtab);str = (char *) ptrace_readstr(pid, strtab + sym-&gt;st_name);//printf("\nfind_symbol_in_linkmap str = %s\n",str);//printf("\nfind_symbol_in_linkmap sym-&gt;st_value = %x\n",sym-&gt;st_value);if (strcmp(str, sym_name) == 0) {printf("\nfind_symbol_in_linkmap str = %s\n",str);printf("\nfind_symbol_in_linkmap sym-&gt;st_value = %x\n",sym-&gt;st_value);free(str);if(sym-&gt;st_value == 0)//值为0代表这个符号本身就是重定向的内容continue;flags = 1;//str = ptrace_readstr(pid, (unsigned long)lm-&gt;l_name);//printf("find_symbol_in_linkmap lib name [%s]\n", str);//free(str);break;}free(str);}while(1);if (flags != 1)ret = 0;elseret =  link_addr + sym-&gt;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,&amp;tmp,4);//lm = tmp;//printf("find_symbol lm = %x\n",lm);//ptrace_read(pid, (unsigned long)map-&gt;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, &amp;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-&gt;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, &amp;lm, 4);//首先查找过程连接的重定位表for(i = 0; i&lt; nrels ;i++) {ptrace_read(pid, (unsigned long)(jmprel + i * sizeof(Elf32_Rel)),rel, sizeof(Elf32_Rel));if(ELF32_R_SYM(rel-&gt;r_info)) {ptrace_read(pid, symtab + ELF32_R_SYM(rel-&gt;r_info) *sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym));str = ptrace_readstr(pid, strtab + sym-&gt;st_name);if (strcmp(str, sym_name) == 0) {if(sym-&gt;st_value != 0){free(str);continue;}modifyflag = 1;free(str);break;}free(str);}}if(modifyflag == 1)break;//没找到的话,再找在链接时就重定位的重定位表for(i = 0; i&lt; nreldyns;i++) {ptrace_read(pid, (unsigned long)(reldyn+ i * sizeof(Elf32_Rel)),rel, sizeof(Elf32_Rel));if(ELF32_R_SYM(rel-&gt;r_info)) {ptrace_read(pid, symtab + ELF32_R_SYM(rel-&gt;r_info) *sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym));str = ptrace_readstr(pid, strtab + sym-&gt;st_name);if (strcmp(str, sym_name) == 0) {if(sym-&gt;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;elseret =  link_addr + rel-&gt;r_offset;//printf("find_sym_in_rel link_addr = %x\t sym-&gt;st_value = %x\n",link_addr , sym-&gt;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-&gt;d_tag){switch(dyn-&gt;d_tag){case DT_SYMTAB://puts("DT_SYMTAB");symtab = dyn-&gt;d_un.d_ptr;break;case DT_STRTAB:strtab = dyn-&gt;d_un.d_ptr;//puts("DT_STRTAB");break;case DT_JMPREL:jmprel = dyn-&gt;d_un.d_ptr;//puts("DT_JMPREL");//printf("jmprel\t %p\n", jmprel);break;case DT_PLTRELSZ:totalrelsize = dyn-&gt;d_un.d_val;//puts("DT_PLTRELSZ");break;case DT_RELAENT:relsize = dyn-&gt;d_un.d_val;//puts("DT_RELAENT");break;case DT_RELENT:relsize = dyn-&gt;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, &amp;regs);regs.eax = (unsigned long) pRLibName;regs.ecx = 0x0;regs.edx = RTLD_LAZY;ptrace_writereg(pid, &amp;regs);/* 调用_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,&amp;mode,sizeof(int));ptrace_push(pid,&amp;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,&amp;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 &lt; 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,&amp;status,0);printf("status = %x\n",status);/*ptrace_writereg(pid, &amp;oldregs);ptrace_cont(pid);waitpid(pid,&amp;status,0);//printf("status = %x\n",status);//ptrace_readreg(pid, &amp;oldregs);//oldregs.eip = 0x8048414;//ptrace_writereg(pid, &amp;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,&amp;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, &amp;new_addr, sizeof(new_addr));//ptrace_write(pid, old_addr, &amp;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, &amp;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 SHAREDif (__builtin_expect (_dl_open_hook != NULL, 0))return _dl_open_hook-&gt;dlsym (map, name);
#endifreturn (dlerror_run (do_dlsym, &amp;args) ? NULL: (void *) (DL_SYMBOL_ADDRESS (args.loadbase, args.ref)));
}

  运行结果:

root@leo-desktop:injso# ./test
1467364356 : original
injso successed
hahahahahahahahahahahahahaha

  • 6.如何替换未导出符号的地址

  被替换函数源码:

#include &lt;stdio.h&gt;//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   %ebp8048469:       89 e5                   mov    %esp,%ebp804846b:       83 e4 f0                and    $0xfffffff0,%esp804846e:       83 ec 20                sub    $0x20,%esp8048471:       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,%eax8048486:       8b 54 24 1c             mov    0x1c(%esp),%edx804848a:       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                      nop80484ae:       90                      nop80484af:       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 &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;elf.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;stdio.h&gt;
#include &lt;sys/ptrace.h&gt;
#include &lt;sys/wait.h&gt;
#include &lt;sys/errno.h&gt;
#include &lt;sys/user.h&gt;
#include &lt;link.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;bits/dlfcn.h&gt;#define IMAGE_ADDR 0x08048000int 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   %ebp0x00232641 <+1>:  mov    %esp,%ebp0x00232643 <+3>: sub    $0x1c,%esp0x00232646 <+6>:    mov    %ebx,-0x8(%ebp)0x00232649 <+9>:   mov    0x8(%ebp),%eax0x0023264c <+12>:   call   0x144a0f0x00232651 <+17>: add    $0x519a3,%ebx0x00232657 <+23>:    mov    0xc(%ebp),%edx0x0023265a <+26>:   mov    %esi,-0x4(%ebp)0x0023265d <+29>:  mov    %eax,-0x14(%ebp)0x00232660 <+32>: mov    %edx,-0x10(%ebp)0x00232663 <+35>: mov    0x354c(%ebx),%esi0x00232669 <+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原创

linux下实现在程序运行时的函数替换(热补丁)相关推荐

  1. linux直接运行程序加载动态库失败,扣丁学堂Linux培训详解程序运行时加载动态库失败解决方法...

    今天扣丁学堂Linux培训老师给大家介绍一下关于Linux程序运行时加载动态库失败的解决方法,希望对同学们学习有所帮助,下面我们一起来看一下吧. Linux下不能加载动态库问题 当出现下边异常情况 . ...

  2. linux 查看进程变量,Linux下查看进程(程序)启动时的环境变量

    Linux下查看进程(程序)启动时的环境变量 Linux的pargs ==================================== 今天又遇到一个老问题: 同事遇到了sqlplus &qu ...

  3. linux环境下查看进程,Linux下查看进程(程序)启动时的环境变量

    背景: 因最近试安装Linux下的jira,有一个中文插件安装后,一旦设置开机启动后,它是英文,而在终端再重新启动一次后呢,似乎插件生效,它又恢复为正常中文界面,我首先想这这涉及到一个环境变量的问题, ...

  4. Java占Linux超过xms,linux下分析java程序占用CPU、内存过高

    一.CPU过高分析 1)使用TOP命令查看CPU.内存使用状态可以发现CPU占用主要分为两部分,一部分为系统内核空间占用CPU百分比,一部分为用户空间占用CPU百分比.其中CPU状态中标示id的为空闲 ...

  5. Linux系统程序运行时加载动态库路径顺序

    程序运行时加载动态库路径顺序(Linux) 在linux系统中,如果程序需要加载动态库,它会按照一定的顺序(优先级)去查找: 链接时路径(Link-time path)和运行时路径(Run-time ...

  6. 【java】 linux下利用nohup后台运行jar文件包程序

    Linux 运行jar包命令如下: 方式一: java -jar XXX.jar 特点:当前ssh窗口被锁定,可按CTRL + C打断程序运行,或直接关闭窗口,程序退出 那如何让窗口不锁定? 方式二 ...

  7. 在linux 下编译c程序时“ error:dereferencing pointer to incomplete type”的问题

    在linux 下编译c程序时经常会遇到" error:dereferencing pointer to incomplete type"的问题,该问题的原因是:结构体定义不规范造成 ...

  8. java 程序运行时注入方法_Spring入门(九):运行时值注入

    Spring提供了2种方式在运行时注入值: 属性占位符(Property placeholder) Spring表达式语言(SpEL) 1. 属性占位符 1.1 注入外部的值 1.1.1 使用Envi ...

  9. Linux下C/C++程序编译链接加载过程中的常见问题及解决方法

    Linux下C/C++程序编译链接加载过程中的常见问题及解决方法 1 头文件包含的问题 报错信息 该错误通常发生在编译时,常见报错信息如下: run.cpp:2:10: fatal error: dl ...

  10. Linux 下几款程序内存泄漏检查工具

    Linux 下几款程序内存泄漏检查工具 chenyoubing | 发布于 2016-07-23 10:08:09 | 阅读量 93 | 无 写这篇博客的原因呢是因为自己在编写基于Nginx磁盘缓存管 ...

最新文章

  1. linux 内核 内存申请函数 kmalloc、kzalloc、vmalloc 区别
  2. 01-缓存一致性---基础知识
  3. 设计模式_4_适配器模式(AdapterPattern, 多个功能的结合)
  4. linux 解压缩一个文件夹下所有的压缩文件
  5. jquery zTree异步搜索的例子--搜全部节点
  6. w3c subscribe
  7. java实现调用百度图像识别API,批量识别车辆车型、颜色等信息
  8. 基于片内Flash的提示音播放程序
  9. Intel Media SDK概述
  10. 学会自己测天气系列八卦基础 01
  11. 同宇新材冲刺深交所:年营收9.47亿 张驰与苏世国为实控人
  12. 系统命名法(IUPAC命名法)
  13. excel取整数的函数_函数010 EXCEL如何随机打乱数据,不重复随机数来帮忙!
  14. react 项目添加百度统计
  15. VBO,VAO,,EBO-penGL进阶(二十) - 绘制一个长方形和一个三角形
  16. 长期持有银行股,吃分红,打新股是什么体验?
  17. 无需SDK的统计工具,让哥赚了个iphone6
  18. windows下配置adb环境
  19. 跑分cpu_【新机】A14芯片最新跑分成绩曝光:3GHz主频,CPU/GPU提升20%丨特斯拉又双叒降价了...
  20. Android中NFC读写

热门文章

  1. (并查集)~APTX4869(fzu 2233)
  2. React中state与props介绍与比较
  3. SQL(1)—增删改查
  4. JQuery CSS 基本选择器 详解
  5. 网站运营模式之行业网站分析
  6. Maven常用命令 - 构建反应堆中指定模块
  7. l2tp pptp相关的一些记录
  8. Eclipse问题提示
  9. Apache Shiro学习笔记(七)IniWebEnvironment
  10. [翻译] FeSpinner