标签:
参考材料:https://en.wikipedia.org/wiki/Rpath
下面介绍GNU ld.so加载动态库的先后顺序:
LD_PRELOAD环境变量指定的路径(一般对应文件/etc/ld.so.preload);
ELF .dynamic节中DT_RPATH入口指定的路径,若DT_RUNPATH入口不存在的话;
环境变量LD_LIBRARY_PATH指定的路径,但如果可执行文件有setuid/setgid权限,则忽略这个路径;编译时指定--library-path会覆盖这个路径;
ELF .dynamic节中DT_RUNPATH入口指定的路径;
ldconfig缓存中的路径(一般对应/etc/ld.so.cache文件),若编译时使用了-z nodeflib的链接选项,则此步跳过;
/lib,然后/usr/lib路径 ,若使用了-z nodeflib链接选项,则此步亦跳过;
参考材料: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;
画了张图,帮助理解:
首先实现DT_RPATH定位功能:
#define ERREXIT(err) do {perror(err);return -1;}while(1) int elf_rpath_entry(const char *filename) { printf("+ enter elf_rpath_entry\n"); 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 = §s[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\n"); _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\n"); break; } dyn++; } close(fd); munmap(fbase, statbuf.st_size); printf("+ exit elf_rpath_entry\n"); 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\n"); 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\n"); return -1; }
对于__gmon_start__符号的查找涉及3个部分,一是节区头部表,主要用来索引字符串表与符号表;符号表中通过索引,引用字符串中实际字符串,如symp->st_name实际只是索引;
下图帮助理解上述代码过程:
标签:
原文地址:http://www.cnblogs.com/kiiim/p/5621229.html