0%

House系列学习

House of Orange

主要是对之前所学习的内容进行细节补充

概述

House of orange是一种针对低版本堆题无free函数来进行IO流攻击的手法。其大致思路为利用unsortedbin_attack劫持_IO_list_all指针,然后劫持_IO_FILE_plus的vtable来进行getshell

前置知识——Unsortedbin attack

Unsortedbin为一个双向链表,其内的堆采用FIFO进行管理这里直接分析一下__int_malloc中对unsortedbin进行操作的相关源码

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
33
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);

/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/

if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
...
}

/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
}
}

在fastbin,smallbin,largebin中都无法exact fit后,就进入了这个大循环在这个大循环中,首先循环检查unsortedbin中是否有堆块如果有的话,先设置bck = victim->bk然后检查victim大小是否合法接着检查所申请的nb(normalized byte)

  1. 是否属于smallbin的大小
  2. 当前victim是否为unsortedbin的最后一个堆
  3. 当前堆的大小是否大于(用户申请的内存+MINSIZE)
    都未通过的话,则将当前chunk从unsortedbin链表中释放操作如下:
1
2
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av)

正常的双向链表解链操作但是如果当unsortedbin中只有一个堆块,而该堆块的bk指针我们可以进行修改那么就可以利用

1
bck->fd = unsorted_chunks (av)

在任意位置写上unsorted_chunks (av)指针

前置知识——top chunk

当用户申请的堆块,在各个bin中都无法满足后,则进入use top

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use_top:
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).

We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/

在use top中,首先会判断当前top chunk的size是否大于用户申请的内存大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

不满足后,后面又涉及到另一个小trick

1
2
3
4
5
6
7
8
9
10
11
/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (have_fastchunks (av))
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}

这里会检查fastbin中是否有chunk,有的话则进行合并,然后将合并后的堆,根据其大小分到不同的bin中到最后,才会做出响应用户申请内存的请求

1
2
3
4
5
6
7
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}

这里使用sysmalloc进行申请内存

1
2
3
4
5
6
7
8
9
10
11
static void *
sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{
/* definations */
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
....
}
}

在sysmalloc中,首先会检查申请的内存是否大于mmap的最小申请阈值即

1
#define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024)

但对于house of orange来说,其申请的内存不会超过其值

upload successful
在跳过这个检查后,开始进入几个断言检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Record incoming configuration of top */

old_top = av->top;
old_size = chunksize (old_top);
old_end = (char *) (chunk_at_offset (old_top, old_size));

brk = snd_brk = (char *) (MORECORE_FAILURE);

/*
If not the first time through, we require old_size to be
at least MINSIZE and to have prev_inuse set.
*/

assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
// old_end = (char *) (chunk_at_offset (old_top, old_size));

/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));

对于第一个assert:

  1. 判断old_top的地址是否和刚生成时一样

  2. old_top->size是否等于0

  3. old_top->size是否大于0x10

  4. old_top->prev_inuse是否等于1

  5. old_top是否为页对齐的

House of Pig