在程序运行时实现函数替换

声明:以下的代码成果,是参考了网上的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, &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 \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, &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\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);*/

    /* 发现_dl_open,并调用它 */
    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);
	/* 找到我们的新函数newread的地址 */
    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;

    /* 找到read的RELOCATION地址 */
    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)));
}

  运行结果:

[email protected]: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 <[email protected]>
 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 <[email protected]>
 80484ab:       eb e9                   jmp    8048496 <main+0x2e>
 80484ad:       90                      nop
 80484ae:       90                      nop
 80484af:       90                      nop

  可以看到在地址0x8048496处的机器码是跳转到fun1函数的,那么这个ffffffb9就是call的操作数,操作数地址0x8048497,也就是说把这个地址中的数值改掉就可以了,有关这个call或者jmp的地址计算可以查看我的另外一篇博文。

  有关这个如何跳转的方法,已经在主函数的代码中给出了,但是被我注释掉了,大家感兴趣的话,可以自己试试。

  效果:

[email protected]: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";*/

  

  linux共享库注射地址:http://www.docin.com/p-634172083.html

  

时间: 2024-09-28 02:44:33

在程序运行时实现函数替换的相关文章

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

转自:http://www.cnblogs.com/leo0000/p/5632642.html 声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享. 但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的.也正因为这些错误,加深了我的学习深度. 最近因为在学习一些调试的技术,但是很少有提到如何在函数运行时实现函数替换的. 为什么会想到这一点?因为在学习调试时,难免会看到一些内核方面的调试技术,内核中的调试

VC项目程序运行时设置指定目录读取Dll

方法一: 选择当前工程,右击"Properties" -> "Configuration Properties" -> "Debugging",在"Working Directory"设置dll的路径就可以了 方法二:设置项目的环境变量 方法三: CString strDllPath = GetExePath() + _T("System"); SetDllDirectory(strDllPat

程序运行时三种内存分配策略

按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求. 栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未

Linux下程序运行时内存状态及相应查看工具

最近在解决一个编译问题时,一直在考虑一个问题,那就是Linux下可执行程序运行时内存是什么状态,是按照什么方式分配内存并运行的.查看了一下资料,就此总结一下,众所周知,linux下内存管理是通过虚存管理的,在分配内存是并非在物理内存开辟了一段空间,而是在使用时才分配的,而且是通过段页式管理.以上比较废话,开始看看程序运行时内存会是什么状态. 在linux下内存分配是以页为单位的,而页是通过段管理,各个段之间是独立的,方便管理.linux程序运行时,可以分为以下几个内存段: 一.BSS段 (bss

程序运行时的内存分配情况

以下内容来自<C++编程实战宝典> 变量和函数占用的内存是系统在程序运行时为程序分配的,但并不是所有的变量和函数都被分配在同一块内存区域中.对于一个C++程序来说,系统一般采用3种方式为程序分配内存,下面将分别介绍这3种方式. (1)从静态存储区域分配 这部分内存在程序编译的时候就已经分配好,并且这块内存在程序的整个运行期间都存在.例如在函数外定义的全局变量,以及在创建时使用static修饰符的变量.在该区域存储的内容一般是全局变量,其中存储在数据段中的全局变量通常已经被初始化. (2)在栈上

c/c++编译时,指定程序运行时查找的动态链接库路径

http://blog.csdn.net/tsxw24/article/details/10220735 c/c++编译时,指定程序运行时查找的动态链接库路径 分类: c/c++ linux 2013-08-23 14:04 1117人阅读 评论(0) 收藏 举报 [plain] view plaincopy $ g++ -Wl,-rpath,/usr/local/lib/ -oevh libevent_http.cpp -levent -Wl,-rpath,  用于指定程序运行时查找动态链接库

Android程序运行时权限与文件系统权限的区别

apk程序是运行在虚拟机上的,对应的是Android独特的权限机制,只有体现到文件系统上时才使用linux的权限设置. (1)Android中的apk必须签名 (2)基于UserID的进程级别的安全机制  (3)默认apk生成的数据对外是不可见的  (4)AndroidManifest.xml中的显式权限声明  Android程序运行时权限与文件系统权限的区别

Java程序运行时的几个区域

Java运行时涉及到的区域 几个基本概念: 1.Java对象     2.Java方法    3.一个编译好的类,以class文件的形式出现 4.Java的本地方法   5.线程私有和线程共有 一.方法区(永久代) 和 堆(heap) 这两个区域是线程共有的,供所有线程使用.所以,对存放在这两个地方的资源进行操作时,如果是程序是多线程的,那么要考虑同步. 方法区存放的是类的类型信息.类的类型信息有,类的静态变量,其它从class文件中读取到的信息. 当用户访问一个类的静态方法或者类的静态变量,或

获取java程序运行时内存信息

由于最近想自己动手测试一下String和StringBuffer的效率问题,需要获取程序运行时的内存占中信息,于是上网查了一下,根据查到的资料写了个程序,发现结果有问题,才发现查到的资料是错误的.所以在这里跟大家分享一下获取内存占用的正确方法 错误的方法 //程序开始时:(先调用一下垃圾回收,但是不一定立即执行) Runtime.getRuntime().gc(); long initm=Runtime.getRuntime().freeMemory(); //程序结束时: Runtime.ge