PWN-堆基础之House of Orange
House of Orange
1.IO_FILE
在看House of Orange的例子之前,我们首先了解一个Linux中的利用机制_IO_FILE
可以使用ptype /o struct _IO_FILE查看这个结构的定义,在pwndbg中可以直接写为dt FILE
_IO_FILE这个结构体中有一个_chain可以看到它的类型是一个struct _IO_FILE *,也就是说是一个指向其他_IO_FILE结构体的指针;在使用fopen之类的函数打开一个文件之后,实际上系统会在堆上创建一个这样的_IO_FILE结构体,并将其加入到一个单链表中,单链表的头部为_IO_list_all,这个单链表可以理解为是一个专门存储_IO_FILE的fastbin。另外vtable为虚函数表指针,实际上是为了与C++的streambuf类兼容才会出现的,但是这里兼容性上的问题,让GLIBC的file stream有可能存在虚函数表劫持的漏洞
另一方面,我们知道Linux的文件系统中一切皆为文件
每一个进程创建时都会有三个标准I/O File Stream:
stdinstdoutstderr
即使程序没有输出、没有输入,它也会存在这三个标准的I/O
还是以前面gdb打开的/bin/sh为例,查看它的_IO_list_all可以看到上面的三项内容
分别就是这三个标准的I/O
这个程序甚至还没有运行起来,所以即使一个程序没有用到文件,我们仍然是有可能利用到I/O File Stream的
2.House of orange
原理
House of orange的核心思想主要是这样几部分:
- 不使用free函数而得到一个free chunk
- 伪造vtable
具体而言,获取free chunk的实现方法是修改Top Chunk。在House of Force中使用的方法是修改Top Chunk为一个特别大的值,之后我们申请一个特别大的chunk,循环一遍内存之后就可以访问到原本Top Chunk上方的内容
那如果将Top Chunk修改为一个很小的值呢?
假如我们把Top Chunk修改为一个很小的数,这时再申请一个更大的chunk
内存认为的Top Chunk是无法满足申请空间的需求的,因此堆管理器后续会再使用brk申请一块新的区域。
正常来说堆管理器会直接将通过brk分配的新内存直接并入到Top Chunk中(即让Top Chunk变大)
但是由于我们改小了Top Chunk,堆管理器认为Top Chunk与堆的尾部并不相邻
因此会将原本的Top Chunk Free掉,这样一来,我们就没有通过Free函数得到了一个Free chunk
3.利用
首先,申请一块小的chunk,并修改top_chunk大小字段为一个很小的值。
1 | small_malloc() |
运行后可以看到Top Chunk被我们改写为了0x101
这时我们再申请一个 large chunk,会发现报错了。
报错的原因是:Top Chunk的prev_inuser位一定需要为1,并且Top Chunk是否在一个页的边界结尾,即是否按页对齐了。这里显然没有按页对齐。由于在Top Chunk之前我们申请了一个0x20大小的chunk(top chunk_addr为0x7020),因此这个大小我们需要伪造为0x1000-0x20。
其实就是原本old top chunk的地址加上其size之后的地址要与页对齐 也就是address&0xfff=0x000
即
1 | small_malloc() |
这样,程序就能顺利通过并且不借助free函数得到了一块free chunk。
有了该free chunk我们就可以进行unsorted bin attack了,因此将bk值修改为_IO_list_all-0x10,这样unlink后_IO_list_all指针就会指向main_arena了。
但是之后还是会报错,这里错误是因为main_arena被当作File Stream对待
我们希望触发的是_IO_OVERFLOW(fp,EOF)这一行
在这之前第一句进行了两个检查
1 | fp->mode <=0 && fp-> _IO_write_ptr > fp-> _IO_write_base |
这里被解析时_mode被认为是一个负数,并且这里write_base == wirte_ptr 所以不满足执行_IO_OVERFLOW条件,程序认为这个File Stream不需要被关闭,于是直接去看下一个FileStream了
下一个File Stream是去找了_chain这个对象指向的位置,我们可以看到这里仍然在main_arena中
这里是main_arena+168,值为0x7ffff7dd4bc8的位置
对应main_arean结构图发现该位置正好是small bins 0x60的位置,所以在第一次_IO_flush_all_lockp执行失败后会顺着_chain找到这个0x60的smallbins进一步处理
那我们如果将一个伪造的FileStream数据填到这里就可以执行对应的内容了
如果我们将unsortedbin的大小伪造为0x60,在申请一个0x20大小的chunk时,这个unsortedbin就会被sort到这个0x60的smallbins中
也就是说我们直接在这个bins中伪造数据,就会被解析为FileStream了
1 | small_malloc() |
那么伪造File Stream的数据都需要填哪些呢?
在wirte_end及之后的内容直到mode都是对我们没用的数据,直接先填0
这部分数据长度从0x30到0xc0,用p64(0)*18来填充,其余部分我们可以构造为:
1 | payload = b"Y"*0x10 |
1 | payload = b'a'*0x10 |
以上内容就构造完毕了_IO_FILE结构,但是关键的vtable指针还没有修改
可以发现,overflow位于vtable的偏移为24位置处,因此我们需要向该位置填入system的地址。那么计算一下vtable的值,从heap开始的位置,首先加上是0x20大小的一个small chunk,加上0xd8大小的整个伪造的_IO_FILE结构减去重合的8字节,最后是减去overflow函数的地址偏移24即0x18
1 | vtable = heap + 0x20 + 0xd8 - 0x8 - 0x18 |
最后就是overflow的值了
这里有两个选择,一是可以直接填写一个one_gadget,另一个简单方法就是使用system(“/bin/sh”),实际上调用的参数是fp,也就是最开始的flags,因此可以修改flags为”/bin/sh”。
因此,完整exp:
1 | #----- 修改Top Chunk得到一个Free chunk |












