• 后门技术(HOOK篇)之DT_RPATH


    0x01 GNU ld.so动态库搜索路径

    参考材料:https://en.wikipedia.org/wiki/Rpath

    下面介绍GNU ld.so加载动态库的先后顺序:

    1. LD_PRELOAD环境变量指定的路径(一般对应文件/etc/ld.so.preload);

    2. ELF .dynamic节中DT_RPATH入口指定的路径,若DT_RUNPATH入口不存在的话;

    3. 环境变量LD_LIBRARY_PATH指定的路径,但如果可执行文件有setuid/setgid权限,则忽略这个路径;编译时指定--library-path会覆盖这个路径;

    4. ELF .dynamic节中DT_RUNPATH入口指定的路径;

    5. ldconfig缓存中的路径(一般对应/etc/ld.so.cache文件),若编译时使用了-z nodeflib的链接选项,则此步跳过;

    6. /lib,然后/usr/lib路径 ,若使用了-z nodeflib链接选项,则此步亦跳过;

    0x02 原理分析

    参考材料:http://linux.chinaunix.net/techdoc/system/2009/04/30/1109602.shtml

    作者:alert7

    从上面分析的搜索路径来看,DT_RPTAH先于/lib和/usr/lib,因此通过修改ELF,在.dynamic中加入DT_RPATH的入口,就可以让可执行文件优先加载我们的动态库,实现劫持的目的;

    加入自定义的DT_RPTAH有两种方式,修改原有的DT_RPATH入口,插入新的DT_RPATH入口;一般ELF文件.dynamic中,都没有这一入口,因此选择新插入;

    这里遇到2个问题,一是定位.dynamic位置,并插入新的entry;二是在ELF中插入我们HOOK用动态库路径;

    现在解决第一个问题。

    32位系统下,.dynamic入口由下面数据结构表示:

    glibc-2.18/elf/elf.h

    /* Dynamic section entry.  */
    typedef struct
    {
      Elf32_Sword d_tag;  /* Dynamic entry type */
      union
        {
          Elf32_Word d_val;  /* Integer value */
          Elf32_Addr d_ptr;  /* Address value */
        } d_un;
    } Elf32_Dyn;

    其中d_tag表示入口类型:

    /* Legal values for d_tag (dynamic entry type).  */
    
    #define DT_NULL      0      /* Marks end of dynamic section */
    #define DT_NEEDED   1      /* Name of needed library */
    #define DT_STRTAB   5      /* Address of string table */
    #define DT_SYMTAB   6      /* Address of symbol table */
    #define DT_RPATH   15      /* Library search path (deprecated) */
    ...

    在.dynamic中,有许多未使用的入口,我们只需找到一处,写入即可;而ELF中,根据偏移定位某个节表比较容易的;

    接下来解决第二个问题,将动态库路径加入ELF中;考虑到加入新的内容,ELF头等位置的偏移都要重新修正,因此最好的办法是修改一处已有字符串,我们选择修改__gmon_start__,因为它在所有程序中都有;

    剩下的任务就是1.定位__gmon_start__并修改,2.返回其在字符串表中的index;

    画了张图,帮助理解:

    0x03 代码实现

    首先实现DT_RPATH定位功能:

    #define ERREXIT(err) do {perror(err);return -1;}while(1)
    
    int elf_rpath_entry(const char *filename)
    {
    	printf("+ enter elf_rpath_entry
    ");
    	int fd = open(filename, O_RDONLY);
    	if (fd < 0) ERREXIT("open");
    
    	struct stat statbuf;
    	fstat(fd, &statbuf);
    
    	char *fbase = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
    	if (fbase == NULL) ERREXIT("mmap");
    
    	Elf32_Ehdr *ehdr = (Elf32_Ehdr *)fbase;
    	Elf32_Shdr *sects = (Elf32_Shdr *)(fbase + ehdr->e_shoff);
    
    	int shsize = ehdr->e_shentsize;
    	int shnum = ehdr->e_shnum;
    	int shstrndx = ehdr->e_shstrndx;
    
    	Elf32_Shdr *shstrsect = &sects[shstrndx]; 
    	char *shstrtab = fbase + shstrsect->sh_offset;
    
    	int i;
    	int _sh_size, _sh_entsize;
    	int _sh_offset;
    	for(i = 0; i < shnum; i++) {
    		if(!strcmp(shstrtab + sects[i].sh_name, ".dynamic")) {
    			printf("+ found the .dynamic section
    ");
    			_sh_size = sects[i].sh_size;
    			_sh_entsize = sects[i].sh_entsize;	
    			_sh_offset = sects[i].sh_offset;
    			break;
    		}
    	}
    
    	Elf32_Dyn *dyn = (Elf32_Dyn*)(fbase + _sh_offset);
    
    	for (i = 0; i < _sh_size; i+=_sh_entsize) {
    		if (dyn->d_tag == DT_RPATH) {
    			printf("+ got DT_RPATH entry
    ");
    			break;	
    		}
    		dyn++;
    	}
    
    	close(fd);
    	munmap(fbase, statbuf.st_size);
    
    	printf("+ exit elf_rpath_entry
    ");
    	return 0;
    }

    接下来,查找并修改__gmon_start__字符串,并返回其索引:

    int modify_symbols(const char *fbase)
    {
            Elf32_Ehdr *ehdr = (Elf32_Ehdr*)fbase;
            Elf32_Shdr *shdr = (Elf32_Shdr *)(fbase + ehdr->e_shoff);
    
            Elf32_Shdr *shdrp = shdr;
            Elf32_Shdr *strsym = NULL;
    
            int i;
            int find = 0;
            for(i = 0; i < ehdr->e_shnum; i++) {
                    if(shdrp->sh_type == SHT_DYNSYM) {
                            find=1;
                            break;
                    }
                    shdrp++;
            }   
            if(!find) {
                    printf("+ not find SHT_DYNSYM
    ");
                    return -1; 
            }   
            strsym = &shdr[shdrp->sh_link];
    
            char *str = (char*)(fbase + strsym->sh_offset);
    
            Elf32_Sym *symp;
            symp = (Elf32_Sym*)(fbase + shdrp->sh_offset);
    
            for(i = 0; i < shdrp->sh_size; i += shdrp->sh_entsize) {
                    if(!strcmp(&str[symp->st_name], "__gmon_start__")) {
                            /* modify here */
                            return symp->st_name;
                    }
                    symp++;
            }   
            printf("+ not find match symbol
    ");
    
            return -1; 
    }

    对于__gmon_start__符号的查找涉及3个部分,一是节区头部表,主要用来索引字符串表与符号表;符号表中通过索引,引用字符串中实际字符串,如symp->st_name实际只是索引;

    下图帮助理解上述代码过程:

  • 相关阅读:
    在Win10中通过命令行打开UWP应用
    前端学习Docker
    Puppeteer的使用
    taro教程
    22种开源Vue模板和主题框架「干货」
    36种免费React模板和主题「干货」
    移动端1px显示异常解决方案
    前端性能优化(二)
    Vue.set()和this.$set()源码解析
    2018年8月7日 乐视2 X621 刷机包下载链接
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9864099.html
Copyright © 2020-2023  润新知