heapoverflow之unsortedbin_attack

做了一个pwn题一口气更新了好多的文章。。pwnhub实在是tql

unsorted bin attack

这种攻击方式是一个跳板。通过这种攻击,我们能够实现一次任意位置写固定值(unsorted_chunks(av),也就是当前main_arena中存放unsorted bin 的地址)

攻击前提

能够写一个被free了的堆

  • UAF
  • chunk overlap

攻击效果

将某个地址写为unsorted_chunks(av).

利用方式

  1. 将 global_max_fast 修改,从而能够获得很大的堆块的fastbin,配合完成 fastbin attack
  2. 修改_IO_list_all完成攻击

注意事项

  • 被修改了bk的unsorted bin 在malloc的时候必须正好分配其大小,不然会触发报错。因为若分配大小不同,则会发生unsorted bin 遍历导致crash
  • 同时要保证当前的unsorted bin堆块在堆块链中的最后一个(unsorted bin是FIFO)

实例

我们这里拿how2heap的一个例子来看一下:

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
#include <stdio.h>
#include <stdlib.h>

int main(){

//......
unsigned long stack_var=0;
fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n");
fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_var);

unsigned long *p=malloc(400);

malloc(500);

free(p);
fprintf(stderr, "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer "
"point to %p\n",(void*)p[1]);

//------------VULNERABILITY-----------

p[1]=(unsigned long)(&stack_var-2);
fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");
fprintf(stderr, "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n",(void*)p[1]);

//------------------------------------

malloc(400);
fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}

去掉了一些没用的代码。这里帮忙理解一下:
由于在unsorted bin 的malloc过程中,源代码的逻辑如下:

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
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (victim -1->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.
*/

/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
//skip some code
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

这里我们根据源码可以知道,此时unsorted bin 并没有使用unlink,而是自己简单的实现了一个从链表中摘除chunk的逻辑。
我们这里复习一下unsorted bin的特征

  • 当仅有一个unsorted bin的时候, 其fd和bk指向main_arena,同时main_arena的fd和bk指向这个bin
  • FIFO,当第二个chunk进来的时候,bin1->fd = bin2, bin2->bk = bin1, main_arena->bk = bin1, main_arena->fd = bin2
    那么我们以这个情况举一个分配的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

main_arena
+-----------------------------+
| fd | bk |----+
+-----------------------------+ |
[0] |
+-----------------------------+ |
| prev_size | size | |
+-----------------------------+ |
| fd | bk |<---+
+-----------------------------+ |
[1] |
+-----------------------------+ |
| prev_size | size | |
+-----------------------------+ |
| fd | bk |<---+
+-----------------------------+

用上图的例子描述以下unsorted bin中的摘除逻辑翻译以下的话,是这样的:

  • 将最接近main_arena的 unosoted bin[0] 摘下来,让其main_arenaunsorted bin的bk指向 unsorted bin[1]

  • 然后让unsorted bin[1]的fd指向main_arena的地址

  • 如果满足(特殊情况)

    • 请求大小在 small bin范围
    • 此时只有一个unsorted bin
    • 当前摘下来的unsorted bin为last remainder
    • 当前堆块大小大于请求大小的时候
      那么此时会将当前的堆块切分成两块,并且将此时的main_arena的fd和bk重新指向这个堆块,同时完成last_remainder的分配
  • 如果请求大小 nb = 当前unsorted bin 的size,那么会将当前堆块直接交予用户

  • 如果请求大小 nb != 当前unsorted bin 的size,那么此时会将所有的堆块按照大小放入smallbin或者largebin中,之后:

    • 如果请求大小 nb < 当前unsorted bin 的size,会遍历当前堆块,将其中大小最接近nb的堆块取出来(也就是刚刚我们从unsorted bin中放进去的堆)切割后剩余部分交还给unsorted bin。
    • 如果请求大小 nb > 当前unsorted bin 的size,那就直接从top中进行请求s

所以攻击的时候,申请大小时一定要(1)和堆中的某一个大小一致(2)处于堆块链的末尾

根据例子中可以看到,其准备修改一个栈上的值为unsorted bin 在main_arena中的地址,结果如下:

可以看到,值已经被修改成功。

关于修改_IO_list_all的攻击思路,可以参考pwnhub old_chall