当前位置:   article > 正文

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

get_sym_info

声明:以下的代码成果,是参考了网上的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.替换函数和被替换函数

  被替换程序源码。 

  1. #include <stdio.h>
  2. #include <time.h>
  3. int main()
  4. {
  5. while(1){
  6. sleep(10);
  7. printf("%d : original\n",time(0));
  8. }
  9. }

  替换新库代码。

  1. #include <stdio.h>
  2. int newmyprint()
  3. {
  4. write(1,"hahahahahahaha",14);
  5. return 0;
  6. }

  够简单明了吧,如果替换成功,目标程序将会一直输出“哈哈哈哈哈哈”。

  • 4.功能函数  

  ptrace相关代码:

  1. /* 读进程寄存器 */
  2. void ptrace_readreg(int pid, struct user_regs_struct *regs)
  3. {
  4. if(ptrace(PTRACE_GETREGS, pid, NULL, regs))
  5. printf("*** ptrace_readreg error ***\n");
  6. /*printf("ptrace_readreg\n");
  7. printf("%x\n",regs->ebx);
  8. printf("%x\n",regs->ecx);
  9. printf("%x\n",regs->edx);
  10. printf("%x\n",regs->esi);
  11. printf("%x\n",regs->edi);
  12. printf("%x\n",regs->ebp);
  13. printf("%x\n",regs->eax);
  14. printf("%x\n",regs->xds);
  15. printf("%x\n",regs->xes);
  16. printf("%x\n",regs->xfs);
  17. printf("%x\n",regs->xgs);
  18. printf("%x\n",regs->orig_eax);
  19. printf("%x\n",regs->eip);
  20. printf("%x\n",regs->xcs);
  21. printf("%x\n",regs->eflags);
  22. printf("%x\n",regs->esp);
  23. printf("%x\n",regs->xss);*/
  24. }
  25. /* 写进程寄存器 */
  26. void ptrace_writereg(int pid, struct user_regs_struct *regs)
  27. {
  28. /*printf("ptrace_writereg\n");
  29. printf("%x\n",regs->ebx);
  30. printf("%x\n",regs->ecx);
  31. printf("%x\n",regs->edx);
  32. printf("%x\n",regs->esi);
  33. printf("%x\n",regs->edi);
  34. printf("%x\n",regs->ebp);
  35. printf("%x\n",regs->eax);
  36. printf("%x\n",regs->xds);
  37. printf("%x\n",regs->xes);
  38. printf("%x\n",regs->xfs);
  39. printf("%x\n",regs->xgs);
  40. printf("%x\n",regs->orig_eax);
  41. printf("%x\n",regs->eip);
  42. printf("%x\n",regs->xcs);
  43. printf("%x\n",regs->eflags);
  44. printf("%x\n",regs->esp);
  45. printf("%x\n",regs->xss);*/
  46. if(ptrace(PTRACE_SETREGS, pid, NULL, regs))
  47. printf("*** ptrace_writereg error ***\n");
  48. }
  49. /* 关联到进程 */
  50. void ptrace_attach(int pid)
  51. {
  52. if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
  53. perror("ptrace_attach");
  54. exit(-1);
  55. }
  56. waitpid(pid, NULL, /*WUNTRACED*/0);
  57. ptrace_readreg(pid, &oldregs);
  58. }
  59. /* 进程继续 */
  60. void ptrace_cont(int pid)
  61. {
  62. int stat;
  63. if(ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
  64. perror("ptrace_cont");
  65. exit(-1);
  66. }
  67. /*while(!WIFSTOPPED(stat))
  68. waitpid(pid, &stat, WNOHANG);*/
  69. }
  70. /* 脱离进程 */
  71. void ptrace_detach(int pid)
  72. {
  73. ptrace_writereg(pid, &oldregs);
  74. if(ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) {
  75. perror("ptrace_detach");
  76. exit(-1);
  77. }
  78. }
  79. /* 写指定进程地址 */
  80. void ptrace_write(int pid, unsigned long addr, void *vptr, int len)
  81. {
  82. int count;
  83. long word;
  84. count = 0;
  85. while(count < len) {
  86. memcpy(&word, vptr + count, sizeof(word));
  87. word = ptrace(PTRACE_POKETEXT, pid, addr + count, word);
  88. count += 4;
  89. if(errno != 0)
  90. printf("ptrace_write failed\t %ld\n", addr + count);
  91. }
  92. }
  93. /* 读指定进程 */
  94. int ptrace_read(int pid, unsigned long addr, void *vptr, int len)
  95. {
  96. int i,count;
  97. long word;
  98. unsigned long *ptr = (unsigned long *)vptr;
  99. i = count = 0;
  100. //printf("ptrace_read addr = %x\n",addr);
  101. while (count < len) {
  102. //printf("ptrace_read addr+count = %x\n",addr + count);
  103. word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
  104. while(word < 0)
  105. {
  106. if(errno == 0)
  107. break;
  108. //printf("ptrace_read word = %x\n",word);
  109. perror("ptrace_read failed");
  110. return 2;
  111. }
  112. count += 4;
  113. ptr[i++] = word;
  114. }
  115. return 0;
  116. }
  117. /*
  118. 在进程指定地址读一个字符串
  119. */
  120. char * ptrace_readstr(int pid, unsigned long addr)
  121. {
  122. char *str = (char *) malloc(64);
  123. int i,count;
  124. long word;
  125. char *pa;
  126. i = count = 0;
  127. pa = (char *)&word;
  128. while(i <= 60) {
  129. word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
  130. count += 4;
  131. if (pa[0] == 0) {
  132. str[i] = 0;
  133. break;
  134. }
  135. else
  136. str[i++] = pa[0];
  137. if (pa[1] == 0) {
  138. str[i] = 0;
  139. break;
  140. }
  141. else
  142. str[i++] = pa[1];
  143. if (pa[2] ==0) {
  144. str[i] = 0;
  145. break;
  146. }
  147. else
  148. str[i++] = pa[2];
  149. if (pa[3] ==0) {
  150. str[i] = 0;
  151. break;
  152. }
  153. else
  154. str[i++] = pa[3];
  155. }
  156. return str;
  157. }
  158. /*
  159. 将指定数据压入进程堆栈并返回堆栈指针
  160. */
  161. void * ptrace_push(int pid, void *paddr, int size)
  162. {
  163. unsigned long esp;
  164. struct user_regs_struct regs;
  165. ptrace_readreg(pid, &regs);
  166. esp = regs.esp;
  167. esp -= size;
  168. esp = esp - esp % 4;
  169. regs.esp = esp;
  170. ptrace_writereg(pid, &regs);
  171. ptrace_write(pid, esp, paddr, size);
  172. return (void *)esp;
  173. }
  174. /*
  175. 在进程内调用指定地址的函数
  176. */
  177. void ptrace_call(int pid, unsigned long addr)
  178. {
  179. void *pc;
  180. struct user_regs_struct regs;
  181. int stat;
  182. void *pra;
  183. pc = (void *) 0x41414140;
  184. pra = ptrace_push(pid, &pc, sizeof(pc));
  185. ptrace_readreg(pid, &regs);
  186. regs.eip = addr;
  187. ptrace_writereg(pid, &regs);
  188. ptrace_cont(pid);
  189. //while(WIFSIGNALED(stat))
  190. // waitpid(pid, &stat, WNOHANG);
  191. }

  这里面的东西我就不展开了,对ptrace的学习,请自行man。

  

  1. /*
  2. 因为应用程序可能不存在hash表,所以通过读取源文件的section header获取符号表的入口数,
  3. 其实是被误导了,但也学习了hash表的作用,用来快速查找符号表中的信息和字符串表中的信息
  4. */
  5. /*int getnchains(int pid,unsigned long base_addr)
  6. {
  7. printf("getnchains enter \n");
  8. Elf32_Ehdr *ehdr = (Elf32_Ehdr *) malloc(sizeof(Elf32_Ehdr));
  9. Elf32_Shdr *shdr = (Elf32_Shdr *)malloc(sizeof(Elf32_Shdr));
  10. unsigned long shdr_addr;
  11. int i = 0;
  12. int fd;
  13. char filename[1024] = {0};
  14. ptrace_read(pid, base_addr, ehdr, sizeof(Elf32_Ehdr));
  15. shdr_addr = base_addr + ehdr->e_shoff;
  16. //printf("getnchains ehdr->e_shoff\t %p\n", ehdr->e_shoff);
  17. snprintf(filename, sizeof(filename), "/proc/%d/exe", pid);
  18. fd = open(filename, O_RDONLY);
  19. if (lseek(fd, ehdr->e_shoff, SEEK_SET) < 0)
  20. exit(-1);
  21. /*while(i<ehdr->e_shnum)
  22. {
  23. read(fd, shdr, ehdr->e_shentsize);
  24. printf("getnchains i = %d\n",i);
  25. printf("getnchains shdr->sh_type = %x\n",shdr->sh_type);
  26. printf("getnchains shdr->sh_name = %x\n",shdr->sh_name);
  27. printf("getnchains shdr->sh_size = %x\n",shdr->sh_size);
  28. printf("getnchains shdr->sh_entsize = %x\n",shdr->sh_entsize);
  29. i++;
  30. }
  31. while(shdr->sh_type != SHT_SYMTAB)
  32. read(fd, shdr, ehdr->e_shentsize);
  33. nchains = shdr->sh_size/shdr->sh_entsize;
  34. //printf("getnchains shdr->sh_type = %d\n",shdr->sh_type);
  35. //printf("getnchains shdr->sh_name = %d\n",shdr->sh_name);
  36. //printf("getnchains shdr->sh_size = %d\n",shdr->sh_size);
  37. //printf("getnchains shdr->sh_entsize = %d\n",shdr->sh_entsize);
  38. //printf("getnchains nchains = %x\n",nchains);
  39. close(fd);
  40. free(ehdr);
  41. free(shdr);
  42. printf("getnchains exit \n");
  43. }
  44. */
  45. /*
  46. 取得指向link_map链表首项的指针
  47. */
  48. struct link_map * get_linkmap(int pid)
  49. {
  50. Elf32_Ehdr *ehdr = (Elf32_Ehdr *) malloc(sizeof(Elf32_Ehdr));
  51. Elf32_Phdr *phdr = (Elf32_Phdr *) malloc(sizeof(Elf32_Phdr));
  52. Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn));
  53. Elf32_Word got;
  54. struct link_map *map = (struct link_map *)malloc(sizeof(struct link_map));
  55. int i = 1;
  56. unsigned long tmpaddr;
  57. ptrace_read(pid, IMAGE_ADDR, ehdr, sizeof(Elf32_Ehdr));
  58. phdr_addr = IMAGE_ADDR + ehdr->e_phoff;
  59. printf("phdr_addr\t %p\n", phdr_addr);
  60. ptrace_read(pid, phdr_addr, phdr, sizeof(Elf32_Phdr));
  61. while(phdr->p_type != PT_DYNAMIC)
  62. ptrace_read(pid, phdr_addr += sizeof(Elf32_Phdr), phdr,sizeof(Elf32_Phdr));
  63. dyn_addr = phdr->p_vaddr;
  64. printf("dyn_addr\t %p\n", dyn_addr);
  65. ptrace_read(pid, dyn_addr, dyn, sizeof(Elf32_Dyn));
  66. while(dyn->d_tag != DT_PLTGOT) {
  67. tmpaddr = dyn_addr + i * sizeof(Elf32_Dyn);
  68. //printf("get_linkmap tmpaddr = %x\n",tmpaddr);
  69. ptrace_read(pid,tmpaddr, dyn, sizeof(Elf32_Dyn));
  70. i++;
  71. }
  72. got = (Elf32_Word)dyn->d_un.d_ptr;
  73. got += 4;
  74. //printf("GOT\t\t %p\n", got);
  75. ptrace_read(pid, got, &map_addr, 4);
  76. printf("map_addr\t %p\n", map_addr);
  77. map = map_addr;
  78. //ptrace_read(pid, map_addr, map, sizeof(struct link_map));
  79. free(ehdr);
  80. free(phdr);
  81. free(dyn);
  82. return map;
  83. }
  84. /*
  85. 取得给定link_map指向的SYMTAB、STRTAB、HASH、JMPREL、PLTRELSZ、RELAENT、RELENT信息
  86. 这些地址信息将被保存到全局变量中,以方便使用
  87. */
  88. void get_sym_info(int pid, struct link_map *lm)
  89. {
  90. Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn));
  91. unsigned long dyn_addr;
  92. //printf("get_sym_info lm = %x\n",lm);
  93. //printf("get_sym_info lm->l_ld's offset = %x\n",&((struct link_map *)0)->l_ld);
  94. //printf("get_sym_info &lm->l_ld = %x\n",&(lm->l_ld));
  95. //dyn_addr = (unsigned long)&(lm->l_ld);
  96. //进入被跟踪进程获取动态节的地址
  97. ptrace_read(pid,&(lm->l_ld) , &dyn_addr, sizeof(dyn_addr));
  98. ptrace_read(pid,&(lm->l_addr) , &link_addr, sizeof(dyn_addr));
  99. ptrace_read(pid, dyn_addr, dyn, sizeof(Elf32_Dyn));
  100. //if(link_addr == 0)
  101. // getnchains(pid,IMAGE_ADDR);
  102. /*else
  103. getnchains(pid,link_addr);*/
  104. while(dyn->d_tag != DT_NULL){
  105. //printf("get_sym_info dyn->d_tag = %x\n",dyn->d_tag);
  106. //printf("get_sym_info dyn->d_un.d_ptr = %x\n",dyn->d_un.d_ptr);
  107. switch(dyn->d_tag)
  108. {
  109. case DT_SYMTAB:
  110. symtab = dyn->d_un.d_ptr;
  111. break;
  112. case DT_STRTAB:
  113. strtab = dyn->d_un.d_ptr;
  114. break;
  115. /*case DT_HASH://可能不存在哈希表,此时nchains是错误的,这个值可以通过符号表得到
  116. //printf("get_sym_info hash table's addr = %x\n",dyn->d_un.d_ptr);
  117. //printf("get_sym_info symtbl's entry = %x\n",(dyn->d_un.d_ptr) + 4);
  118. ptrace_read(pid, (dyn->d_un.d_ptr) + 4,&nchains, sizeof(nchains));
  119. break;*/
  120. case DT_JMPREL:
  121. jmprel = dyn->d_un.d_ptr;
  122. break;
  123. case DT_PLTRELSZ:
  124. totalrelsize = dyn->d_un.d_val;
  125. break;
  126. case DT_RELAENT:
  127. relsize = dyn->d_un.d_val;
  128. break;
  129. case DT_RELENT:
  130. relsize = dyn->d_un.d_val;
  131. break;
  132. case DT_REL:
  133. reldyn = dyn->d_un.d_ptr;
  134. break;
  135. case DT_RELSZ:
  136. reldynsz = dyn->d_un.d_val;
  137. break;
  138. }
  139. ptrace_read(pid, dyn_addr += sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
  140. }
  141. //printf("get_sym_info link_addr = %x\n",link_addr);
  142. //printf("get_sym_info symtab = %x\n",symtab);
  143. //printf("get_sym_info relsize = %x\n",relsize);
  144. //printf("get_sym_info reldyn = %x\n",reldyn);
  145. //printf("get_sym_info totalrelsize = %x\n",totalrelsize);
  146. //printf("get_sym_info jmprel = %x\n",jmprel);
  147. //printf("get_sym_info nchains = %x\n",nchains);
  148. //printf("get_sym_info strtab = %x\n",strtab);
  149. nrels = totalrelsize / relsize;
  150. nreldyns = reldynsz/relsize;
  151. //printf("get_sym_info nreldyns = %d\n",nreldyns);
  152. //printf("get_sym_info nrels = %d\n",nrels);
  153. free(dyn);
  154. printf("get_sym_info exit\n");
  155. }
  156. /*
  157. 在指定的link_map指向的符号表查找符号,它仅仅是被上面的find_symbol使用
  158. */
  159. unsigned long find_symbol_in_linkmap(int pid, struct link_map *lm, char *sym_name)
  160. {
  161. Elf32_Sym *sym = (Elf32_Sym *) malloc(sizeof(Elf32_Sym));
  162. int i = 0;
  163. char *str;
  164. unsigned long ret;
  165. int flags = 0;
  166. get_sym_info(pid, lm);
  167. do{
  168. if(ptrace_read(pid, symtab + i * sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym)))
  169. return 0;
  170. i++;
  171. //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);
  172. //printf("find_symbol_in_linkmap Elf32_Sym's size = %d\n",sizeof(Elf32_Sym));
  173. //printf("\nfind_symbol_in_linkmap sym->st_name = %x\n",sym->st_name);
  174. if (!sym->st_name && !sym->st_size && !sym->st_value)//全为0是符号表的第一项
  175. continue;
  176. //printf("\nfind_symbol_in_linkmap strtab = %x\n",strtab);
  177. str = (char *) ptrace_readstr(pid, strtab + sym->st_name);
  178. //printf("\nfind_symbol_in_linkmap str = %s\n",str);
  179. //printf("\nfind_symbol_in_linkmap sym->st_value = %x\n",sym->st_value);
  180. if (strcmp(str, sym_name) == 0) {
  181. printf("\nfind_symbol_in_linkmap str = %s\n",str);
  182. printf("\nfind_symbol_in_linkmap sym->st_value = %x\n",sym->st_value);
  183. free(str);
  184. if(sym->st_value == 0)//值为0代表这个符号本身就是重定向的内容
  185. continue;
  186. flags = 1;
  187. //str = ptrace_readstr(pid, (unsigned long)lm->l_name);
  188. //printf("find_symbol_in_linkmap lib name [%s]\n", str);
  189. //free(str);
  190. break;
  191. }
  192. free(str);
  193. }while(1);
  194. if (flags != 1)
  195. ret = 0;
  196. else
  197. ret = link_addr + sym->st_value;
  198. free(sym);
  199. return ret;
  200. }
  201. /*
  202. 解析指定符号
  203. */
  204. unsigned long find_symbol(int pid, struct link_map *map, char *sym_name)
  205. {
  206. struct link_map *lm = map;
  207. unsigned long sym_addr;
  208. char *str;
  209. unsigned long tmp;
  210. //sym_addr = find_symbol_in_linkmap(pid, map, sym_name);
  211. //return 0;
  212. //if (sym_addr)
  213. // return sym_addr;
  214. //printf("\nfind_symbol map = %x\n",map);
  215. //ptrace_read(pid,(char *)map+12,&tmp,4);
  216. //lm = tmp;
  217. //printf("find_symbol lm = %x\n",lm);
  218. //ptrace_read(pid, (unsigned long)map->l_next, lm, sizeof(struct link_map));
  219. sym_addr = find_symbol_in_linkmap(pid, lm, sym_name);
  220. while(!sym_addr ) {
  221. ptrace_read(pid, (char *)lm+12, &tmp, 4);//获取下一个库的link_map地址
  222. if(tmp == 0)
  223. return 0;
  224. lm = tmp;
  225. //printf("find_symbol lm = %x\n",lm);
  226. /*str = ptrace_readstr(pid, (unsigned long)lm->l_name);
  227. if(str[0] == '/0')
  228. continue;
  229. printf("[%s]\n", str);
  230. free(str);*/
  231. if ((sym_addr = find_symbol_in_linkmap(pid, lm, sym_name)))
  232. break;
  233. }
  234. return sym_addr;
  235. }
  236. /* 查找符号的重定位地址 */
  237. unsigned long find_sym_in_rel(int pid, char *sym_name)
  238. {
  239. Elf32_Rel *rel = (Elf32_Rel *) malloc(sizeof(Elf32_Rel));
  240. Elf32_Sym *sym = (Elf32_Sym *) malloc(sizeof(Elf32_Sym));
  241. int i;
  242. char *str;
  243. unsigned long ret;
  244. struct link_map *lm;
  245. lm = map_addr;
  246. //get_dyn_info(pid);
  247. do{
  248. get_sym_info(pid,lm);
  249. ptrace_read(pid, (char *)lm+12, &lm, 4);
  250. //首先查找过程连接的重定位表
  251. for(i = 0; i< nrels ;i++) {
  252. ptrace_read(pid, (unsigned long)(jmprel + i * sizeof(Elf32_Rel)),
  253. rel, sizeof(Elf32_Rel));
  254. if(ELF32_R_SYM(rel->r_info)) {
  255. ptrace_read(pid, symtab + ELF32_R_SYM(rel->r_info) *
  256. sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym));
  257. str = ptrace_readstr(pid, strtab + sym->st_name);
  258. if (strcmp(str, sym_name) == 0) {
  259. if(sym->st_value != 0){
  260. free(str);
  261. continue;
  262. }
  263. modifyflag = 1;
  264. free(str);
  265. break;
  266. }
  267. free(str);
  268. }
  269. }
  270. if(modifyflag == 1)
  271. break;
  272. //没找到的话,再找在链接时就重定位的重定位表
  273. for(i = 0; i< nreldyns;i++) {
  274. ptrace_read(pid, (unsigned long)(reldyn+ i * sizeof(Elf32_Rel)),
  275. rel, sizeof(Elf32_Rel));
  276. if(ELF32_R_SYM(rel->r_info)) {
  277. ptrace_read(pid, symtab + ELF32_R_SYM(rel->r_info) *
  278. sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym));
  279. str = ptrace_readstr(pid, strtab + sym->st_name);
  280. if (strcmp(str, sym_name) == 0) {
  281. if(sym->st_value != 0){
  282. free(str);
  283. continue;
  284. }
  285. modifyflag = 2;
  286. free(str);
  287. break;
  288. }
  289. free(str);
  290. }
  291. }
  292. if(modifyflag == 2)
  293. break;
  294. }while(lm);
  295. //printf("find_sym_in_rel flags = %d\n",flags);
  296. if (modifyflag == 0)
  297. ret = 0;
  298. else
  299. ret = link_addr + rel->r_offset;
  300. //printf("find_sym_in_rel link_addr = %x\t sym->st_value = %x\n",link_addr , sym->st_value);
  301. free(rel);
  302. free(sym);
  303. return ret;
  304. }
  305. /*
  306. 在进程自身的映象中(即不包括动态共享库,无须遍历link_map链表)获得各种动态信息
  307. */
  308. /*void get_dyn_info(int pid)
  309. {
  310. Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn));
  311. int i = 0;
  312. ptrace_read(pid, dyn_addr + i * sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
  313. i++;
  314. while(dyn->d_tag){
  315. switch(dyn->d_tag)
  316. {
  317. case DT_SYMTAB:
  318. //puts("DT_SYMTAB");
  319. symtab = dyn->d_un.d_ptr;
  320. break;
  321. case DT_STRTAB:
  322. strtab = dyn->d_un.d_ptr;
  323. //puts("DT_STRTAB");
  324. break;
  325. case DT_JMPREL:
  326. jmprel = dyn->d_un.d_ptr;
  327. //puts("DT_JMPREL");
  328. //printf("jmprel\t %p\n", jmprel);
  329. break;
  330. case DT_PLTRELSZ:
  331. totalrelsize = dyn->d_un.d_val;
  332. //puts("DT_PLTRELSZ");
  333. break;
  334. case DT_RELAENT:
  335. relsize = dyn->d_un.d_val;
  336. //puts("DT_RELAENT");
  337. break;
  338. case DT_RELENT:
  339. relsize = dyn->d_un.d_val;
  340. //puts("DT_RELENT");
  341. break;
  342. }
  343. ptrace_read(pid, dyn_addr + i * sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
  344. i++;
  345. }
  346. nrels = totalrelsize / relsize;
  347. free(dyn);
  348. }*/
  349. /*void call_dl_open(int pid, unsigned long addr, char *libname)
  350. {
  351. void *pRLibName;
  352. struct user_regs_struct regs;
  353. /*
  354. 先找个空间存放要装载的共享库名,我们可以简单的把它放入堆栈
  355. pRLibName = ptrace_push(pid, libname, strlen(libname) + 1);
  356. /* 设置参数到寄存器
  357. ptrace_readreg(pid, &regs);
  358. regs.eax = (unsigned long) pRLibName;
  359. regs.ecx = 0x0;
  360. regs.edx = RTLD_LAZY;
  361. ptrace_writereg(pid, &regs);
  362. /* 调用_dl_open
  363. ptrace_call(pid, addr);
  364. puts("call _dl_open ok");
  365. }*/
  366. /*#define RTLD_LAZY 0x00001
  367. #define RTLD_NOW 0x00002
  368. #define RTLD_BINDING_MASK 0x3
  369. #define RTLD_NOLOAD 0x00004
  370. #define RTLD_DEEPBIND 0x00008
  371. #define RTLD_GLOBAL 0x00100
  372. #define RTLD_LOCAL 0
  373. #define RTLD_NODELETE 0x01000 */
  374. void call__libc_dlopen_mode(int pid, unsigned long addr, char *libname)
  375. {
  376. void *plibnameaddr;
  377. //printf("call__libc_dlopen_mode libname = %s\n",libname);
  378. //printf("call__libc_dlopen_mode addr = %x\n",addr);
  379. //将需要加载的共享库地址压栈
  380. plibnameaddr = ptrace_push(pid, libname, strlen(libname) + 1);
  381. ptrace_push(pid,&mode,sizeof(int));
  382. ptrace_push(pid,&plibnameaddr,sizeof(plibnameaddr));
  383. /* 调用__libc_dlopen_mode */
  384. ptrace_call(pid, addr);
  385. }
  386. void call_printf(int pid, unsigned long addr, char *string)
  387. {
  388. void *paddr;
  389. paddr = ptrace_push(pid, string, strlen(string) + 1);
  390. ptrace_push(pid,&paddr,sizeof(paddr));
  391. ptrace_call(pid, addr);
  392. }

  作者所做的修改,读者可以对比文章最后的连接中的代码。

  这边对于程序的具体解释,就不具体展开了。

  需要注意的是,原来是采用_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.被跟踪进程开始输出“哈哈哈哈哈”。

  上源码:

  

  1. int main(int argc, char *argv[])
  2. {
  3. int pid;
  4. struct link_map *map;
  5. char sym_name[256];
  6. unsigned long sym_addr;
  7. unsigned long new_addr,old_addr,rel_addr;
  8. int status = 0;
  9. char libpath[1024];
  10. char oldfunname[128];
  11. char newfunname[128];
  12. //mode = atoi(argv[2]);
  13. if(argc < 5){
  14. printf("usage : ./injso pid libpath oldfunname newfunname\n");
  15. exit(-1);
  16. }
  17. /* 从命令行取得目标进程PID*/
  18. pid = atoi(argv[1]);
  19. /* 从命令行取得新库名称*/
  20. memset(libpath,0,sizeof(libpath));
  21. memcpy(libpath,argv[2],strlen(argv[2]));
  22. /* 从命令行取得旧函数的名称*/
  23. memset(oldfunname,0,sizeof(oldfunname));
  24. memcpy(oldfunname,argv[3],strlen(argv[3]));
  25. /* 从命令行取得新函数的名称*/
  26. memset(newfunname,0,sizeof(newfunname));
  27. memcpy(newfunname,argv[4],strlen(argv[4]));
  28. printf("main pid = %d\n",pid);
  29. printf("main libpath : %s\n",libpath);
  30. printf("main oldfunname : %s\n",oldfunname);
  31. printf("main newfunname : %s\n",newfunname);
  32. /* 关联到目标进程*/
  33. ptrace_attach(pid);
  34. /* 得到指向link_map链表的指针 */
  35. map = get_linkmap(pid); /* get_linkmap */
  36. sym_addr = find_symbol(pid, map, "printf");
  37. printf("found printf at addr %p\n", sym_addr);
  38. if(sym_addr == 0)
  39. goto detach;
  40. call_printf(pid,sym_addr,"injso successed\n");
  41. waitpid(pid,&status,0);
  42. printf("status = %x\n",status);
  43. /*ptrace_writereg(pid, &oldregs);
  44. ptrace_cont(pid);
  45. waitpid(pid,&status,0);
  46. //printf("status = %x\n",status);
  47. //ptrace_readreg(pid, &oldregs);
  48. //oldregs.eip = 0x8048414;
  49. //ptrace_writereg(pid, &oldregs);
  50. ptrace_cont(int pid)(pid);
  51. ptrace_detach(pid);
  52. exit(0);*/
  53. /* 发现__libc_dlopen_mode,并调用它 */
  54. sym_addr = find_symbol(pid, map, "__libc_dlopen_mode"); /* call _dl_open */
  55. printf("found __libc_dlopen_mode at addr %p\n", sym_addr);
  56. if(sym_addr == 0)
  57. goto detach;
  58. call__libc_dlopen_mode(pid, sym_addr,libpath); /* 注意装载的库地址 */
  59. //while(1);
  60. waitpid(pid,&status,0);
  61. /* 找到新函数的地址 */
  62. strcpy(sym_name, newfunname); /* intercept */
  63. sym_addr = find_symbol(pid, map, sym_name);
  64. printf("%s addr\t %p\n", sym_name, sym_addr);
  65. if(sym_addr == 0)
  66. goto detach;
  67. /* 找到旧函数在重定向表的地址 */
  68. strcpy(sym_name, oldfunname);
  69. rel_addr = find_sym_in_rel(pid, sym_name);
  70. printf("%s rel addr\t %p\n", sym_name, rel_addr);
  71. if(rel_addr == 0)
  72. goto detach;
  73. /* 找到用于保存read地址的指针 */
  74. //strcpy(sym_name, "oldread");
  75. //old_addr = find_symbol(pid, map, sym_name);
  76. //printf("%s addr\t %p\n", sym_name, old_addr);
  77. /* 函数重定向 */
  78. puts("intercept..."); /* intercept */
  79. //ptrace_read(pid, rel_addr, &new_addr, sizeof(new_addr));
  80. //ptrace_write(pid, old_addr, &new_addr, sizeof(new_addr));
  81. //rel_addr = 0x8048497;如果是静态地址,也就是未导出该符号地址,那么只能通过反汇编先找到该函数被调用的地方,将这个地方的跳转地址修改
  82. if(modifyflag == 2)
  83. sym_addr = sym_addr - rel_addr - 4;
  84. printf("main modify sym_addr = %x\n",sym_addr);
  85. ptrace_write(pid, rel_addr, &sym_addr, sizeof(sym_addr));
  86. puts("injectso ok");
  87. detach:
  88. printf("prepare to detach\n");
  89. ptrace_detach(pid);
  90. return 0;
  91. }

  这里面有一个很重要的地方,如果不先在目标进程中调用printf就不能够调用__lib_dlopen_mode成功,这个原因很奇怪,根据当时的core文件来看崩溃在了下面的这个函数,原因是_dl_open_hook这个全局变量为0,但实际上运行过printf之后,这个_dl_open_hook还是0。这个有待后续检验。

  1. void *
  2. __libc_dlsym (void *map, const char *name)
  3. {
  4. struct do_dlsym_args args;
  5. args.map = map;
  6. args.name = name;
  7. #ifdef SHARED
  8. if (__builtin_expect (_dl_open_hook != NULL, 0))
  9. return _dl_open_hook->dlsym (map, name);
  10. #endif
  11. return (dlerror_run (do_dlsym, &args) ? NULL
  12. : (void *) (DL_SYMBOL_ADDRESS (args.loadbase, args.ref)));
  13. }

  运行结果:

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

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

  被替换函数源码:

  1. #include <stdio.h>
  2. //int fun2();
  3. int fun1()
  4. {
  5. printf("fun1\n");
  6. // fun2();
  7. }
  8. int main()
  9. {
  10. signed int i = 0x40011673 ;
  11. i = i - 0x4001172d ;
  12. printf("i = %x\n",i);
  13. while(1){
  14. i = fun1();
  15. sleep(10);
  16. }
  17. return 1;
  18. }

  这个怎么来替换fun1函数的地址呢?

  首先反汇编得到main的机器码,如下,

  1. 08048468 <main>:
  2. 8048468: 55 push %ebp
  3. 8048469: 89 e5 mov %esp,%ebp
  4. 804846b: 83 e4 f0 and $0xfffffff0,%esp
  5. 804846e: 83 ec 20 sub $0x20,%esp
  6. 8048471: c7 44 24 1c 73 16 01 movl $0x40011673,0x1c(%esp)
  7. 8048478: 40
  8. 8048479: 81 6c 24 1c 2d 17 01 subl $0x4001172d,0x1c(%esp)
  9. 8048480: 40
  10. 8048481: b8 75 85 04 08 mov $0x8048575,%eax
  11. 8048486: 8b 54 24 1c mov 0x1c(%esp),%edx
  12. 804848a: 89 54 24 04 mov %edx,0x4(%esp)
  13. 804848e: 89 04 24 mov %eax,(%esp)
  14. 8048491: e8 ce fe ff ff call 8048364 <printf@plt>
  15. 8048496: e8 b9 ff ff ff call 8048454 <fun1>
  16. 804849b: 89 44 24 1c mov %eax,0x1c(%esp)
  17. 804849f: c7 04 24 0a 00 00 00 movl $0xa,(%esp)
  18. 80484a6: e8 c9 fe ff ff call 8048374 <sleep@plt>
  19. 80484ab: eb e9 jmp 8048496 <main+0x2e>
  20. 80484ad: 90 nop
  21. 80484ae: 90 nop
  22. 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。当然我也会自己再研究下。

 

   最后补上全局变量和头文件:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <elf.h>
  4. #include <sys/types.h>
  5. #include <stdio.h>
  6. #include <sys/ptrace.h>
  7. #include <sys/wait.h>
  8. #include <sys/errno.h>
  9. #include <sys/user.h>
  10. #include <link.h>
  11. #include <sys/stat.h>
  12. #include <fcntl.h>
  13. #include <bits/dlfcn.h>
  14. #define IMAGE_ADDR 0x08048000
  15. int mode = 2;
  16. struct user_regs_struct oldregs;
  17. Elf32_Addr phdr_addr;
  18. Elf32_Addr dyn_addr;
  19. Elf32_Addr map_addr;
  20. Elf32_Addr symtab;
  21. Elf32_Addr strtab;
  22. Elf32_Addr jmprel;
  23. Elf32_Addr reldyn;
  24. Elf32_Word reldynsz;
  25. Elf32_Word totalrelsize;
  26. Elf32_Word relsize;
  27. unsigned long link_addr;
  28. int nrels;
  29. int nreldyns;
  30. //int nchains;
  31. int modifyflag = 0;
  32. /*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是可以运行成功的。

  1. (gdb) disassemble __libc_dlopen_mode
  2. Dump of assembler code for function __libc_dlopen_mode:
  3. 0x00232640 <+0>: push %ebp
  4. 0x00232641 <+1>: mov %esp,%ebp
  5. 0x00232643 <+3>: sub $0x1c,%esp
  6. 0x00232646 <+6>: mov %ebx,-0x8(%ebp)
  7. 0x00232649 <+9>: mov 0x8(%ebp),%eax
  8. 0x0023264c <+12>: call 0x144a0f
  9. 0x00232651 <+17>: add $0x519a3,%ebx
  10. 0x00232657 <+23>: mov 0xc(%ebp),%edx
  11. 0x0023265a <+26>: mov %esi,-0x4(%ebp)
  12. 0x0023265d <+29>: mov %eax,-0x14(%ebp)
  13. 0x00232660 <+32>: mov %edx,-0x10(%ebp)
  14. 0x00232663 <+35>: mov 0x354c(%ebx),%esi
  15. 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原创

  转载请注明出处

 

转载于:https://www.cnblogs.com/leo0000/p/5632642.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/160458
推荐阅读
相关标签
  

闽ICP备14008679号