1. 程式人生 > >後門技術(HOOK篇)之DT_RPATH

後門技術(HOOK篇)之DT_RPATH

dex 文件 定位 symbols 存在 cat 未使用 con alert

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\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 = &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\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