linux下實現在程式運行時的函數替換(熱補丁)

来源:http://www.cnblogs.com/leo0000/archive/2016/07/01/5632642.html
-Advertisement-
Play Games

聲明:以下的代碼成果,是參考了網上的injso技術,文章最後會給出地址。 但是injso文章中的代碼存在一些問題,所以後面出現的代碼是經過我個人修改和檢測的。 最近因為在學習一些調試的技術,但是很少有提到如何在函數運行時實現函數替換的。 為什麼會想到這一點?因為在學習調試時,難免會看到一些內核方面的 ...


聲明:以下的代碼成果,是參考了網上的injso技術,文章最後會給出地址。

   但是injso文章中的代碼存在一些問題,所以後面出現的代碼是經過我個人修改和檢測的。

 

  最近因為在學習一些調試的技術,但是很少有提到如何在函數運行時實現函數替換的。

  為什麼會想到這一點?因為在學習調試時,難免會看到一些內核方面的調試技術,內核中的調試有一個kprobe,很強大,可以實現運行時的函數替換。其原理就是hook,鉤子,但是學習了這個kprobe之後會發現,kprobe內部有檢測所要鉤的函數是不是屬於內核空間,必須是內核函數才能實現替換。而實際上,我的工作大部分還是在應用層的,所以想要實現應用程式的熱補丁技術。

  一些基礎的知識這邊的就不展開了,需要的基礎有,elf文件格式,ptrace,waitpid,應用程式間通信時的信號,彙編。

  • 1、elf文件載入過程

  elf簡單地說是由以下四部分組成的,elf文件頭,program header和section header,內容。其中program header是運行時使用的,而section header並不會被載入進程式運行空間,但他們可以在編譯時被指定該段的載入地址等信息,當然一般這個鏈接腳本.lds是由gcc預設的。

  第一步,載入elf文件頭,檢驗文件類型版本等,重要的是找到program header的地址和header的個數,如果連接器腳本是預設的,那麼elf文件頭會被載入在0x804800地址處。

  第二步,載入program header,接著掃描program header,找到一個類型為PT_INTERP的program header,這個header裡面放著的是有關解釋器的地址,這時候將解釋器程式的elf文件頭載入進來。一般是這樣:

      INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
        [Requesting program interpreter: /lib/ld-linux.so.2]

  第三步,掃描program header,如果類型為PT_LOAD,則將該段載入進來。

  第四步,判斷是否需要解釋器程式,如果需要,把解釋器程式載入進來,並把程式入口設置為解釋器程式的地址。否則是應用程式本身的入口。反彙編為_start標號。

  第五步,設置命令行傳入的參數等應用程式需要的信息。

  第六步,解釋器程式開始運行,載入程式需要的庫,填寫重定向符號表中的地址信息。

  • 2.elf文件動態鏈接過程

  上一步,解釋器程式根據program header已經將應用程式的段都載入進記憶體了,接下來再掃描program header,找到類型為PT_DYNAMIC,這裡麵包含了很多由section header描述的內容,包括重定向表,符號表,字元串表等等。解釋器需要這個段描述的一些信息。

  DT_NEEDED描述了所需要的動態庫名稱,DT_REL描述了重定位表地址,DT_JMPREL描述了重定位表地址(這個表是懶惰鏈接使用的),DT_PLTGOT全局偏移表地址。

  此時解釋器程式就可以根據所需要的動態庫,將其載入進記憶體。每一個被載入進來的庫的相關信息會被記錄在link_map結構中,這個結構是一個鏈表,保存了所有的動態信息。

  其中,全局偏移表got,got[0]保存了PT_DYNAMIC的起始地址,got[1]保存link_map的地址,而link_map中就可以找到PT_DYNAMIC的起始地址,和下一個或者上一個共用文件或者可執行文件的link_map地址。

  DT_REL這個重定向表中的符號必須在此時就被解析完成。

  而DT_JMPREL這個重定向表中的符號可以在運行時再解析。

  所有的庫和符號全部解析完成之後,解釋器程式就會把控制權交給可執行文件的_start。程式開始執行。

  • 3.替換函數和被替換函數

  被替換程式源碼。 

#include <stdio.h>
#include <time.h>
int main()
{
        while(1){
                sleep(10);
                printf("%d : original\n",time(0));
        }
}

  替換新庫代碼。

#include <stdio.h>

int newmyprint()
{
	write(1,"hahahahahahaha",14);
	return 0;
}

  夠簡單明瞭吧,如果替換成功,目標程式將會一直輸出“哈哈哈哈哈哈”。

  • 4.功能函數  

  ptrace相關代碼:

/* 讀進程寄存器 */
void ptrace_readreg(int pid, struct user_regs_struct *regs)
{
    if(ptrace(PTRACE_GETREGS, pid, NULL, regs))
        printf("*** ptrace_readreg error ***\n");
	/*printf("ptrace_readreg\n");
	printf("%x\n",regs->ebx);
	printf("%x\n",regs->ecx);
	printf("%x\n",regs->edx);
	printf("%x\n",regs->esi);
	printf("%x\n",regs->edi);
	printf("%x\n",regs->ebp);
	printf("%x\n",regs->eax);
	printf("%x\n",regs->xds);
	printf("%x\n",regs->xes);
	printf("%x\n",regs->xfs);
	printf("%x\n",regs->xgs);
	printf("%x\n",regs->orig_eax);
	printf("%x\n",regs->eip);
	printf("%x\n",regs->xcs);
	printf("%x\n",regs->eflags);
	printf("%x\n",regs->esp);
	printf("%x\n",regs->xss);*/

}

/* 寫進程寄存器 */
void ptrace_writereg(int pid, struct user_regs_struct *regs)
{
	/*printf("ptrace_writereg\n");
	printf("%x\n",regs->ebx);
	printf("%x\n",regs->ecx);
	printf("%x\n",regs->edx);
	printf("%x\n",regs->esi);
	printf("%x\n",regs->edi);
	printf("%x\n",regs->ebp);
	printf("%x\n",regs->eax);
	printf("%x\n",regs->xds);
	printf("%x\n",regs->xes);
	printf("%x\n",regs->xfs);
	printf("%x\n",regs->xgs);
	printf("%x\n",regs->orig_eax);
	printf("%x\n",regs->eip);
	printf("%x\n",regs->xcs);
	printf("%x\n",regs->eflags);
	printf("%x\n",regs->esp);
	printf("%x\n",regs->xss);*/

    if(ptrace(PTRACE_SETREGS, pid, NULL, regs))
        printf("*** ptrace_writereg error ***\n");
}

/* 關聯到進程 */
void ptrace_attach(int pid)
{
    if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
        perror("ptrace_attach");
        exit(-1);
    }

    waitpid(pid, NULL, /*WUNTRACED*/0);   
   
    ptrace_readreg(pid, &oldregs);
}

/* 進程繼續 */
void ptrace_cont(int pid)
{
    int stat;

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

/* 脫離進程 */
void ptrace_detach(int pid)
{
    ptrace_writereg(pid, &oldregs);

    if(ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) {
        perror("ptrace_detach");
        exit(-1);
    }
}

/* 寫指定進程地址 */
void ptrace_write(int pid, unsigned long addr, void *vptr, int len)
{
    int count;
    long word;

    count = 0;

    while(count < len) {
        memcpy(&word, vptr + count, sizeof(word));
        word = ptrace(PTRACE_POKETEXT, pid, addr + count, word);
        count += 4;

        if(errno != 0)
            printf("ptrace_write failed\t %ld\n", addr + count);
    }
}

/* 讀指定進程 */
int ptrace_read(int pid, unsigned long addr, void *vptr, int len)
{
    int i,count;
    long word;
    unsigned long *ptr = (unsigned long *)vptr;

    i = count = 0;
	//printf("ptrace_read addr = %x\n",addr);
    while (count < len) {
		//printf("ptrace_read addr+count = %x\n",addr + count);
        word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
		while(word < 0)
		{
			if(errno == 0)
				break;
			//printf("ptrace_read word = %x\n",word);
			perror("ptrace_read failed");
			return 2;
		}
        count += 4;
        ptr[i++] = word;
    }
	return 0;
}

/*
 在進程指定地址讀一個字元串
 */
char * ptrace_readstr(int pid, unsigned long addr)
{
    char *str = (char *) malloc(64);
    int i,count;
    long word;
    char *pa;

    i = count = 0;
    pa = (char *)&word;

    while(i <= 60) {
        word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
        count += 4;

        if (pa[0] == 0) {
            str[i] = 0;
        break;
        }
        else
            str[i++] = pa[0];

        if (pa[1] == 0) {
            str[i] = 0;
            break;
        }
        else
            str[i++] = pa[1];

        if (pa[2] ==0) {
            str[i] = 0;
            break;
        }
        else
            str[i++] = pa[2];

        if (pa[3] ==0) {
            str[i] = 0;
            break;
        }
        else
            str[i++] = pa[3];
    }
   
    return str;
}




/*
 將指定數據壓入進程堆棧並返回堆棧指針
 */
void * ptrace_push(int pid, void *paddr, int size)
{
    unsigned long esp;
    struct user_regs_struct regs;

    ptrace_readreg(pid, &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);*/
	
    /* 發現__libc_dlopen_mode,並調用它 */
    sym_addr = find_symbol(pid, map, "__libc_dlopen_mode");        /* call _dl_open */
    printf("found __libc_dlopen_mode at addr %p\n", sym_addr);  
	if(sym_addr == 0)
		goto detach;
    call__libc_dlopen_mode(pid, sym_addr,libpath);    /* 註意裝載的庫地址 */   
	//while(1);
	waitpid(pid,&status,0);
	/* 找到新函數的地址 */
    strcpy(sym_name, newfunname);                /* intercept */
    sym_addr = find_symbol(pid, map, sym_name);
    printf("%s addr\t %p\n", sym_name, sym_addr);
	if(sym_addr == 0)
		goto detach;

    /* 找到舊函數在重定向表的地址 */
    strcpy(sym_name, oldfunname);               
    rel_addr = find_sym_in_rel(pid, sym_name);
    printf("%s rel addr\t %p\n", sym_name, rel_addr);
	if(rel_addr == 0)
		goto detach;

    /* 找到用於保存read地址的指針 */
    //strcpy(sym_name, "oldread");               
    //old_addr = find_symbol(pid, map, sym_name);
    //printf("%s addr\t %p\n", sym_name, old_addr);

    /* 函數重定向 */
    puts("intercept...");                    /* intercept */
    //ptrace_read(pid, rel_addr, &new_addr, sizeof(new_addr));
    //ptrace_write(pid, old_addr, &new_addr, sizeof(new_addr));
    //rel_addr = 0x8048497;如果是靜態地址,也就是未導出該符號地址,那麼只能通過反彙編先找到該函數被調用的地方,將這個地方的跳轉地址修改
    
	if(modifyflag == 2)
		sym_addr = sym_addr - rel_addr - 4;
	printf("main modify sym_addr = %x\n",sym_addr);
	ptrace_write(pid, rel_addr, &sym_addr, sizeof(sym_addr));
	
    puts("injectso ok");
detach:
	printf("prepare to detach\n");
	ptrace_detach(pid);
	
	return 0;
  
}

  這裡面有一個很重要的地方,如果不先在目標進程中調用printf就不能夠調用__lib_dlopen_mode成功,這個原因很奇怪,根據當時的core文件來看崩潰在了下麵的這個函數,原因是_dl_open_hook這個全局變數為0,但實際上運行過printf之後,這個_dl_open_hook還是0。這個有待後續檢驗。

void *
__libc_dlsym (void *map, const char *name)
{
  struct do_dlsym_args args;
  args.map = map;
  args.name = name;

#ifdef SHARED
  if (__builtin_expect (_dl_open_hook != NULL, 0))
    return _dl_open_hook->dlsym (map, name);
#endif
  return (dlerror_run (do_dlsym, &args) ? NULL
	  : (void *) (DL_SYMBOL_ADDRESS (args.loadbase, args.ref)));
}

  運行結果:

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

  • 6.如何替換未導出符號的地址

  被替換函數源碼:

#include <stdio.h>


//int fun2();

int fun1()
{
        printf("fun1\n");
//      fun2();
}

int main()
{
        signed int i  = 0x40011673 ;
        i = i - 0x4001172d ;
        printf("i = %x\n",i);
        while(1){
                i = fun1();
                sleep(10);
        }
        return 1;
}

  這個怎麼來替換fun1函數的地址呢?

  首先反彙編得到main的機器碼,如下,

08048468 <main>:
 8048468:       55                      push   %ebp
 8048469:       89 e5                   mov    %esp,%ebp
 804846b:       83 e4 f0                and    $0xfffffff0,%esp
 804846e:       83 ec 20                sub    $0x20,%esp
 8048471:       c7 44 24 1c 73 16 01    movl   $0x40011673,0x1c(%esp)
 8048478:       40 
 8048479:       81 6c 24 1c 2d 17 01    subl   $0x4001172d,0x1c(%esp)
 8048480:       40 
 8048481:       b8 75 85 04 08          mov    $0x8048575,%eax
 8048486:       8b 54 24 1c             mov    0x1c(%esp),%edx
 804848a:       89 54 24 04             mov    %edx,0x4(%esp)
 804848e:       89 04 24                mov    %eax,(%esp)
 8048491:       e8 ce fe ff ff          call   8048364 <printf@plt>
 8048496:       e8 b9 ff ff ff          call   8048454 <fun1>
 804849b:       89 44 24 1c             mov    %eax,0x1c(%esp)
 804849f:       c7 04 24 0a 00 00 00    movl   $0xa,(%esp)
 80484a6:       e8 c9 fe ff ff          call   8048374 <sleep@plt>
 80484ab:       eb e9                   jmp    8048496 <main+0x2e>
 80484ad:       90                      nop
 80484ae:       90                      nop
 80484af:       90                      nop

  可以看到在地址0x8048496處的機器碼是跳轉到fun1函數的,那麼這個ffffffb9就是call的操作數,操作數地址0x8048497,也就是說把這個地址中的數值改掉就可以了,有關這個call或者jmp的地址計算可以查看我的另外一篇博文。

  有關這個如何跳轉的方法,已經在主函數的代碼中給出了,但是被我註釋掉了,大家感興趣的話,可以自己試試。

  效果:

root@leo-desktop:lib2lib# ./a.out
i = ffffff46
fun1
injso successed
hahahahahahaha^C

  這裡面的無關代碼,大家仔細看,是為了證明call的函數地址計算方式的。

  • 7.總結

  那麼講到現在的話,已經實現了不管函數符號是否導出都可以實現運行時替換的代碼。

  這裡面主要的技術是,elf文件格式,運行時載入的過程,跳轉地址的計算,運行時鏈接的過程,也就是plt表(當然這個也可以從我的另一篇博文中看到)。

  比較遺憾的是有關那個奔潰,有網友如果找到了原因,請回覆下,3q。當然我也會自己再研究下。

 

   最後補上全局變數和頭文件:

#include <stdio.h>
#include <string.h>
#include <elf.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <sys/user.h>
#include <link.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <bits/dlfcn.h>

#define IMAGE_ADDR 0x08048000

int mode = 2;

struct user_regs_struct oldregs;
Elf32_Addr phdr_addr;
Elf32_Addr dyn_addr;
Elf32_Addr map_addr;
Elf32_Addr symtab;
Elf32_Addr strtab;
Elf32_Addr jmprel;
Elf32_Addr reldyn;
Elf32_Word reldynsz;
Elf32_Word totalrelsize;
Elf32_Word relsize;
unsigned long link_addr;
int nrels;
int nreldyns;
//int nchains;
int modifyflag = 0;
/*char libpath[128] = "/mnt/hgfs/svnroot/test/injectsov2/prj_linux/so.so";*/

  

 

  • 8.修正

  針對在調用__libc_dlopen_mode函數之前需要調用printf的問題,終於讓我在晚上解決了。
  首先,我嘗試了調用其他函數而不是printf函數,發現效果一樣,包括第一次是調用__libc_dlopen_mode,第二次對該函數的調用都可以成功。
  其次,那麼現在問題就集中在了這兩個__libc_dlopen_mode調用之間的差別在哪裡,程式段肯定是一致的,棧也是一致的,而堆空間未使用,還有一個重要的因素,那就是寄存器。
  最後,發現在調用__libc_dlopen_mode前,有四個寄存器不同,分別是eax,orig_eax,eflags和esp。我一開始認為,通用寄存器eax和orig_eax不會對程式的執行造成影響。但是通過實驗,僅調一次__libc_dlopen_mode,部分寄存器賦正確執行時的值,發現對eax和orig_eax被賦於正確執行時的值時,程式可以正常運行,而且不僅僅必須是一種值,比如eax可以是0,1,0xffffffff,很多值都可以,但是被賦予0xfffffdfc和0xfffffdff等值時會失敗,試驗過並不是因為d這一位決定的,0xfffffdf0或者d00是可以運行成功的。

(gdb) disassemble __libc_dlopen_mode
Dump of assembler code for function __libc_dlopen_mode:
   0x00232640 <+0>:	push   %ebp
   0x00232641 <+1>:	mov    %esp,%ebp
   0x00232643 <+3>:	sub    $0x1c,%esp
   0x00232646 <+6>:	mov    %ebx,-0x8(%ebp)
   0x00232649 <+9>:	mov    0x8(%ebp),%eax
   0x0023264c <+12>:	call   0x144a0f
   0x00232651 <+17>:	add    $0x519a3,%ebx
   0x00232657 <+23>:	mov    0xc(%ebp),%edx
   0x0023265a <+26>:	mov    %esi,-0x4(%ebp)
   0x0023265d <+29>:	mov    %eax,-0x14(%ebp)
   0x00232660 <+32>:	mov    %edx,-0x10(%ebp)
   0x00232663 <+35>:	mov    0x354c(%ebx),%esi
   0x00232669 <+41>:	test   %esi,%esi

  在實驗中,還發現對eax賦於不正確的值時,當時忘了記了,還讓程式跑飛了。崩了,但是新庫已經載入上了。所以這個函數替換還是有一定的風險,或者說libc庫本身存在一定的bug。
  所以現在問題找到了,在於eax和orig_eax上,但是對__libc_dlopen_mode反彙編發現,eax在函數開頭就被賦予了通過棧傳遞的參數2的值,所以eax不應該影響程式的運行,但實際上影響了,這一點讓我覺得很奇怪,如果有任何網友對這個原因知曉的話,麻煩回覆,萬分感謝。

 

  linux共用庫註射地址:http://www.docin.com/p-634172083.html

 

 

  

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本人使用CentOs6.5 最近在學習linux操作系統,單在使用shell連接前都要使用ifconfig eth0 設置一個臨時IP讓我不勝其煩。決定學習設置一個固定IP 步驟: 1、登錄電腦後使用 setup 命令 進入下圖界面 2、選擇網路配置 3、選擇設備配置 4、進入 eth0 5、陪配 ...
  • 需要使用Linux的遠程桌面服務,xmanager之前用過,感覺一般,這次嘗試下vnc 我的操作系統是centos7 一服務端 安裝VNCServer #yum -y install vnc *vnc-server* 設置密碼 # vncserverYou will require a passwo ...
  • 一、線上安裝 : 即可安裝 如果在安裝完後無法用Tab鍵補全命令,可以執行: APT(Advanced Packaging Tool), 包括apt get, apt cache, apt cdrom等工具,APT可以自動下載,配置,安裝二進位或者源代碼格式的軟體包,因此簡化了Unix系統上管理軟體 ...
  • 在使用fdisk創建分區時,我們會使用partprobe命令可以使kernel重新讀取分區信息,從而避免重啟系統,但是有時候會遇到下麵錯誤信息“Warning: Unable to open /dev/sr0 read-write (Read-only file system). /dev/sr0 ... ...
  • 經過了前三篇的鋪墊,我們終於來到了最重要的部分~~如果沒看過前幾篇的小伙伴們,可以出門右轉~~用十幾分鐘回顧一下~~然後在看這篇會感覺不一樣的~~~~ 下麵讓我們來正式開始吧 我們進入大白菜的桌面是醬紫的~ 首先看見大白菜一鍵裝機雙擊進去 第一步:選擇ISO系統包 第二步:選擇安裝到的硬碟(一般就是 ...
  • 在Linux中,有時使用umount命令去卸載LV或文件時,可能出現umount: xxx: device is busy的情況,如下案例所示 [root@DB-Server u06]# vgdisplay -v VolGroup03 Using volume group(s) on command... ...
  • 1. 安裝svn yum intall subversion 2. 查看安裝位置 rpm -ql subversion 3. 檢驗svn是否安裝成功,查看幫助 svn --help , 看到下圖表示成功。 4. 創建svn版本庫目錄 mkdir –p /var/svn/svnrepos 5. 創建版 ...
  • # 快捷鍵 //未完待續 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...