zoukankan      html  css  js  c++  java
  • 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
    ",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 ***
    ");
    	/*printf("ptrace_readreg
    ");
    	printf("%x
    ",regs->ebx);
    	printf("%x
    ",regs->ecx);
    	printf("%x
    ",regs->edx);
    	printf("%x
    ",regs->esi);
    	printf("%x
    ",regs->edi);
    	printf("%x
    ",regs->ebp);
    	printf("%x
    ",regs->eax);
    	printf("%x
    ",regs->xds);
    	printf("%x
    ",regs->xes);
    	printf("%x
    ",regs->xfs);
    	printf("%x
    ",regs->xgs);
    	printf("%x
    ",regs->orig_eax);
    	printf("%x
    ",regs->eip);
    	printf("%x
    ",regs->xcs);
    	printf("%x
    ",regs->eflags);
    	printf("%x
    ",regs->esp);
    	printf("%x
    ",regs->xss);*/
    
    }
    
    /* 写进程寄存器 */
    void ptrace_writereg(int pid, struct user_regs_struct *regs)
    {
    	/*printf("ptrace_writereg
    ");
    	printf("%x
    ",regs->ebx);
    	printf("%x
    ",regs->ecx);
    	printf("%x
    ",regs->edx);
    	printf("%x
    ",regs->esi);
    	printf("%x
    ",regs->edi);
    	printf("%x
    ",regs->ebp);
    	printf("%x
    ",regs->eax);
    	printf("%x
    ",regs->xds);
    	printf("%x
    ",regs->xes);
    	printf("%x
    ",regs->xfs);
    	printf("%x
    ",regs->xgs);
    	printf("%x
    ",regs->orig_eax);
    	printf("%x
    ",regs->eip);
    	printf("%x
    ",regs->xcs);
    	printf("%x
    ",regs->eflags);
    	printf("%x
    ",regs->esp);
    	printf("%x
    ",regs->xss);*/
    
        if(ptrace(PTRACE_SETREGS, pid, NULL, regs))
            printf("*** ptrace_writereg error ***
    ");
    }
    
    /* 关联到进程 */
    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	 %ld
    ", 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
    ",addr);
        while (count < len) {
    		//printf("ptrace_read addr+count = %x
    ",addr + count);
            word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
    		while(word < 0)
    		{
    			if(errno == 0)
    				break;
    			//printf("ptrace_read word = %x
    ",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, &regs);
        esp = regs.esp;
        esp -= size;
        esp = esp - esp % 4;
        regs.esp = esp;
    
        ptrace_writereg(pid, &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, &pc, sizeof(pc));
    
        ptrace_readreg(pid, &regs);
        regs.eip = addr;
        ptrace_writereg(pid, &regs);
    
        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 
    ");
        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	 %p
    ", 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
    ",i);
    		printf("getnchains shdr->sh_type = %x
    ",shdr->sh_type);
    		printf("getnchains shdr->sh_name = %x
    ",shdr->sh_name);
    		printf("getnchains shdr->sh_size = %x
    ",shdr->sh_size);
    		printf("getnchains shdr->sh_entsize = %x
    ",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
    ",shdr->sh_type);
    	//printf("getnchains shdr->sh_name = %d
    ",shdr->sh_name);
    	//printf("getnchains shdr->sh_size = %d
    ",shdr->sh_size);
    	//printf("getnchains shdr->sh_entsize = %d
    ",shdr->sh_entsize);
    	//printf("getnchains nchains = %x
    ",nchains);	
    	close(fd);
    	free(ehdr);
    	free(shdr);
    	printf("getnchains exit 
    ");
    }
    */
    
    
    /*
     取得指向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	 %p
    ", 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	 %p
    ", 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
    ",tmpaddr);
            ptrace_read(pid,tmpaddr, dyn, sizeof(Elf32_Dyn));
            i++;
        }
    
        got = (Elf32_Word)dyn->d_un.d_ptr;
        got += 4;
        //printf("GOT		 %p
    ", got);
    
        ptrace_read(pid, got, &map_addr, 4);
        printf("map_addr	 %p
    ", 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
    ",lm);
    	//printf("get_sym_info lm->l_ld's offset = %x
    ",&((struct link_map *)0)->l_ld);
    	//printf("get_sym_info &lm->l_ld = %x
    ",&(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
    ",dyn->d_tag);
    		//printf("get_sym_info dyn->d_un.d_ptr = %x
    ",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
    ",dyn->d_un.d_ptr);
    			//printf("get_sym_info symtbl's entry = %x
    ",(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
    ",link_addr);
    	//printf("get_sym_info symtab = %x
    ",symtab);
    	//printf("get_sym_info relsize = %x
    ",relsize);
    	//printf("get_sym_info reldyn = %x
    ",reldyn);
    	//printf("get_sym_info totalrelsize = %x
    ",totalrelsize);
    	//printf("get_sym_info jmprel = %x
    ",jmprel);
    	//printf("get_sym_info nchains = %x
    ",nchains);
    	//printf("get_sym_info strtab = %x
    ",strtab);
    
        nrels = totalrelsize / relsize;
    	nreldyns = reldynsz/relsize;
    	
    	//printf("get_sym_info nreldyns = %d
    ",nreldyns);
    	//printf("get_sym_info nrels = %d
    ",nrels);
    
        free(dyn);
    	printf("get_sym_info exit
    ");
    }
    /*
     在指定的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	sym->st_size = %x	sym->st_value = %x
    ",sym->st_name,sym->st_size,sym->st_value);
    		//printf("find_symbol_in_linkmap Elf32_Sym's size = %d
    ",sizeof(Elf32_Sym));
    		//printf("
    find_symbol_in_linkmap sym->st_name = %x
    ",sym->st_name);        
    		if (!sym->st_name && !sym->st_size && !sym->st_value)//全为0是符号表的第一项
                continue;
    		//printf("
    find_symbol_in_linkmap strtab = %x
    ",strtab);
            str = (char *) ptrace_readstr(pid, strtab + sym->st_name);
    		//printf("
    find_symbol_in_linkmap str = %s
    ",str);
    		//printf("
    find_symbol_in_linkmap sym->st_value = %x
    ",sym->st_value);
            if (strcmp(str, sym_name) == 0) {
    			printf("
    find_symbol_in_linkmap str = %s
    ",str);
    			printf("
    find_symbol_in_linkmap sym->st_value = %x
    ",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]
    ", 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("
    find_symbol map = %x
    ",map);
    	//ptrace_read(pid,(char *)map+12,&tmp,4);
    	//lm = tmp;
    	//printf("find_symbol lm = %x
    ",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
    ",lm);
            /*str = ptrace_readstr(pid, (unsigned long)lm->l_name);
            if(str[0] == '/0')
                continue;
            printf("[%s]
    ", 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
    ",flags);
        if (modifyflag == 0)
            ret = 0;
        else
        	ret =  link_addr + rel->r_offset;
    	//printf("find_sym_in_rel link_addr = %x	 sym->st_value = %x
    ",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	 %p
    ", 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, &regs);
        regs.eax = (unsigned long) pRLibName;
        regs.ecx = 0x0;
        regs.edx = RTLD_LAZY;
        ptrace_writereg(pid, &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
    ",libname);
    	//printf("call__libc_dlopen_mode addr = %x
    ",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
    ");
    		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
    ",pid);
    	printf("main libpath : %s
    ",libpath);
    	printf("main oldfunname : %s
    ",oldfunname);
    	printf("main newfunname : %s
    ",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
    ", sym_addr);  
    	if(sym_addr == 0)
    		goto detach;
    	call_printf(pid,sym_addr,"injso successed
    ");
    	waitpid(pid,&status,0);
    	printf("status = %x
    ",status);
    	
        /*ptrace_writereg(pid, &oldregs);
    	ptrace_cont(pid);
    
    	
    
    	waitpid(pid,&status,0);
    	//printf("status = %x
    ",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
    ", 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	 %p
    ", 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	 %p
    ", 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	 %p
    ", 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
    ",sym_addr);
    	ptrace_write(pid, rel_addr, &sym_addr, sizeof(sym_addr));
    	
        puts("injectso ok");
    detach:
    	printf("prepare to detach
    ");
    	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
    ");
    //      fun2();
    }
    
    int main()
    {
            signed int i  = 0x40011673 ;
            i = i - 0x4001172d ;
            printf("i = %x
    ",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原创

      转载请注明出处

  • 相关阅读:
    vue 插件的使用 todolist案例
    vue 传值 混入mixin
    vue 生命周期函数
    vue 指令总结
    vue 其它的指令
    vue 监听数据变化的原理 表单数据的收集
    vue for循环中的key
    vue 学习
    vue 学习
    HDU 1029
  • 原文地址:https://www.cnblogs.com/leo0000/p/5632642.html
Copyright © 2011-2022 走看看