rop之return-to-dl-resolve

可用工具: roputils.py

适用场景:

  • 无法leak内存
  • 所有可以采用stack pivot的场景

一、(Partial RELRO 或者 no RELRO) and no PIE

1. 32位平台:

_dl_runtime_resolve(link_map *, reloc_offset)

利用roputils的dl_resolve_call(base, binsh_addr)和dl_resolve_data(base, “system”)完成system(“/bin/sh”),注意:连续写入dl_resolve_call和dl_resolve_data的数据时,要将dl_resolve_call放在dl_resolve_data的内容之前,否则sub esp会覆盖前面的数据

2. 64位平台

_dl_runtime_resolve(link_map *, reloc_index)
  1. 无RELRO的情况下,可以修改.dynmaic段中的DT_STRTAB指向.bss(注意要与.dynamic段的倒数第二项VERSYM保持至少0x180的间距),修改.dynamic段中的SYMTAB指向.bss,伪造sym,绕过sym->other检测,然后在STRTAB+sym->st_name的位置写入”system”,参考 http://skysider.com/?p=602
  2. Patial RELRO
    1. 可以leak时,需要leak link_map,令 link_map +0x1c8 = 0(对应link_map->l_info[49])(此时也可以leak 函数地址,然后确定libc版本,return-to-libc)
      if(__builtin_expect(ELFW(ST_VISIBILITY)(sym->st_other), 0) == 0)
      {
          const struct r_found_version *version = NULL;
          
          if (l->l_info[VERSYMIDX(DT_VERSYM])!=NULL)
          {
           const ElfW(Half) *vernum = 
             (const void *) D_PTR(l, l_info[VERSYMIDX(DT_VERSYM)]);
           ElfW(Half) ndx = vernum[ELFW(R_SYM)(reloc->r_info)] & 0x7ffff;
           version = &l->l_versions[ndx];
           if(version->hash) == 0
               version = NULL;
          }

      在伪造sym之后造成VERSYM取值超出范围,造成segment fault

    2. 无leak(前提需要知道libc版本,需要有一个已经resolved过的函数)
      1. 修改link_map指针(GOT[1],令其指向一个解析过的函数的GOT表项,比如.got[‘read’])
      2. 正确构造link_map中的l_info[DT_JMPREL]、l_info[DT_SYMTAB]、l_info[DT_STRTAB](可与原有的link_map值相等)
      3. 伪造index, 使其指向伪造的 reloc
      4. reloc->info>>0x20为SYMTAB的下标,使其指向伪造的Elf64_Sym项
      5. if((sym->st_other) & 3 ) != 0) // 如果已经resolve过
            value = DL_FIXUP_MAKE_VALUE(l, l->l_addr + sym->st_value)

        令sym->st_other的第2位为1,此时调用 DL_FIXUP_MAKE_VALUE宏返回解析过的函数地址,正常情况下,link_map的l_addr=0(代表ELF文件中的虚拟地址与实际加载地址之间的差值),  sym->st_value为0,(got表项中的函数解析地址与实际虚拟地址之间的差值)。而通过伪造link_map指针,使l->l_addr为解析过的函数地址,sym->st_value为system地址与该函数地址之间的差值,二者之和就是system的实际地址。解析后的system地址存放在reloc->offset + sym->st_value的地方,要确保可写

二、Full RELRO:

前提:可以leak内存

常用思路:读取.dynamic段中的d_tag=DT_DEBUG的项,其中的d_value指向一个r_debug结构体,定义如下:

struct r_debug { #64位版本
    int r_version;
    struct link_map_public *r_map;
    Elf64_Addr r_brk;
    enum {RT_CONSISTENT, RT_ADD, RT_DELETE} r_state;
    Elf64_Addr r_ldbase;

其中r_map成员指向link_map链表的头节点,头结点为当前elf文件的link_map,伪造link_map中l_info[DT_JMPREL]和l_info[DT_STRTAB],伪造.rel.plt表以及.dynstr表,使rel表项指向已有的.dynsym项,由于.dynstr表被伪造,st_name指向fake .dynstr中的字符串。

第二个问题是需要调用_dl_runtime_resolve,但是开启了 Full Relro的程序 .plt.got表中没有该函数的地址,此时可以去遍历link_map链表,通过link_map->l_name可以获取当前加载库的路径,从中可以找到未开启Full Relro的库文件(比如libc.so.6),读取link_map->l_info[DT_PLTGOT]->d_val可以找到.plt.got的地址,读取GOT[2]就可以找到_dl_runtime_resolve的地址。

以上图片来自http://www.inforsec.org/wp/?p=389

附:

/* Dynamic section tags.  */

#define DT_NULL		0
#define DT_NEEDED	1
#define DT_PLTRELSZ	2
#define DT_PLTGOT	3
#define DT_HASH		4
#define DT_STRTAB	5
#define DT_SYMTAB	6
#define DT_RELA		7
#define DT_RELASZ	8
#define DT_RELAENT	9
#define DT_STRSZ	10
#define DT_SYMENT	11
#define DT_INIT		12
#define DT_FINI		13
#define DT_SONAME	14
#define DT_RPATH	15
#define DT_SYMBOLIC	16
#define DT_REL		17
#define DT_RELSZ	18
#define DT_RELENT	19
#define DT_PLTREL	20
#define DT_DEBUG	21
#define DT_TEXTREL	22
#define DT_JMPREL	23
#define DT_BIND_NOW	24
#define DT_INIT_ARRAY	25
#define DT_FINI_ARRAY	26
#define DT_INIT_ARRAYSZ 27
#define DT_FINI_ARRAYSZ 28
#define DT_RUNPATH	29
#define DT_FLAGS	30
#define DT_ENCODING	32
#define DT_PREINIT_ARRAY   32
#define DT_PREINIT_ARRAYSZ 33

参考:

  1. http://angelboy.logdown.com/posts/283218-return-to-dl-resolve
  2. http://code.metager.de/source/xref/gnu/src/include/elf/common.h
  3. http://www.inforsec.org/wp/?p=389