• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Heap exploitation, Use After Free & Double free (Часть 4)

timeshout

RAID-массив
Пользователь
Регистрация
29.06.2022
Сообщения
62
Реакции
83
Введение в эксплуатацию двоичных файлов x64 Linux (часть 1)
Введение в бинарную эксплуатацию x64 Linux (часть 2)
Heap exploitation, Overflows (часть 3)

В этой статье мы обсудим концепцию использования ранее освобожденной памяти, которая широко известна как Use-After-Free (UAF).

1657770641491.png



Definition
a chunk of memory can basically exist in two states, which is either in-use or free. An in-use chunk carries, along with the data payload, information about its size, the arena it belongs, the previous chunk and whether or no it is mmap’d. When a chunk is free, it is added to a particular list (tcache, fastbins etc.) depending on the current state of the program. During this state it carries information about its size as well as the memory addresses of other chunks, so it can be easily traced and reused by following a process which requires a new allocation.

1657770730534.png


Когда программа пытается получить доступ к блоку, который был помечен аллокатором как свободный, результат будет непредсказуемым, поскольку он обычно зависит от состояния программы до или после этого события. Это состояние может включать способ, которым будет использоваться ошибочная ссылка, но, что более важно, каково текущее содержимое чанка. Состояние, которое я только что описал, называется Use After Free (UAF) и является одной из наиболее часто встречающихся ошибок, последствия которой варьируются от простого сбоя программы до выполнения произвольного кода.


Use After Free - это класс уязвимостей, которые возникают, когда программа пытается разыменовать указатель, указывающий на освобожденный фрагмент.

Возьмем для примера указатель p, который указывает на чанк A, содержащий адрес функции f1. Предположим, что по какой-то причине A был освобожден и добавлен в список свободных чанков. Теперь представьте, что в какой-то момент A снова выделяется и на этот раз содержит адрес функции f2. Поскольку p все еще указывает на A, при повторном обращении к нему будет инициировано выполнение f2:



1657770791030.png




First Fit
> https://github.com/shellphish/how2heap

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

Если мы скомпилируем и запустим программу, то получим следующий результат:

1657770879832.png



В точках 1,2 переменная a указывает на 0x5558007bf010, которая содержит строку this is A

В точке 3 переменная a освобождается.

В точке 4 переменная c запрашивает одинаковый размер с переменной a, таким образом, первый кусок, который подходит, это тот, на который указывала переменная a.

В точке 5, при повторном обращении к переменной a, будет выведено This is C!, так как чанк был перезаписан переменной c


UAF Example 0
> https://googleprojectzero.blogspot.com/2015/06/what-is-good-memory-corruption.html

Код:
#include <stdio.h>
#include <stdlib.h>

struct unicorn_counter { int num; };

int main() {
    struct unicorn_counter* p_unicorn_counter;
    int* run_calc = malloc(sizeof(int));
    *run_calc = 0;
    free(run_calc);
    p_unicorn_counter = malloc(sizeof(struct unicorn_counter));
    p_unicorn_counter->num = 42;
    if (*run_calc) execl("/bin/sh", 0);

При запуске этого кода просто откроется окно Shell:

1657770980796.png





Но почему? Ведь *run_calc был установлен в 0, поэтому строка 13 будет иметь значение false и execl никогда не запустится. Давайте загрузим программу в gdb и установим точку останова после первого malloc и присвоения *run_calc значения 0:


1657771022155.png



Как и ожидалось, run_calc указывает на 0x00005555555592a0, который содержит нулевое значение:

1657771094624.png



p_unicorn_counter будет указывать на тот же чанк с run_calc из-за того же требования выделения в строке 11, поэтому после строки 12 чанк будет выглядеть следующим образом:


1657771114159.png




При повторном обращении к run_calc она будет содержать значение 0x2a, таким образом, if будет успешным, и execl будет вызван.

UAF Example 1
C:
#include <stdio.h>
#include <stdlib.h>

typedef void (*fp)();

void func1(){
        printf("[*] --------> This is func 1\n");
}
void func2(){
        printf("[*] --------> This is func 2\n");
}

void main()
{
        fp *pointer1 = malloc(sizeof(fp));
        *pointer1 = func1;
        (*pointer1)();

        printf("[1] pointer1 points to %p\n",pointer1);
        printf("[2] Freeing pointer1\n");
        free(pointer1);


        fp *pointer2 = malloc(sizeof(fp));
        *pointer2 = func2;
        (*pointer2)();


        printf("[3] pointer2 points to %p\n",pointer2);

        printf("[4] Using pointer1 after free\n");

        (*pointer1)();
        gets();
}


В строке 4 мы определяем fp как указатель на функцию, которая не получает никаких параметров и возвращает void. В строке 15 мы определяем указатель типа fp, который указывает на func1 (строка 16). В строке 21 мы вызываем функцию free для указателя1 и повторяем тот же процесс для func2 и указателя2. Проблема возникает в строке 33, поскольку мы повторно используем указатель, который ранее был освобожден. Если мы скомпилируем и запустим программу, то получим следующий результат:

1657771176046.png



Хотя до состояния [4] все шло как ожидалось, сразу после него мы наблюдаем вызов func2, хотя мы никогда не присваивали адрес func2 указателю pointer1. Чтобы понять, что произошло, давайте загрузим программу в gdb и установим несколько точек останова после вызовов malloc и free:

1657771202290.png



После первого malloc и присвоения адреса func1 указателю1 мы имеем следующие распределения:

1657771232398.png



1657771277973.png





Впоследствии, после вызова free, чанк, на который указывает указатель1, добавляется в список tcache:

1657771306651.png


Поскольку указатель1 по-прежнему указывает на 0x00005555555592a0, вызов (*pointer1)(); в строке 33 нашей программы вызовет функцию func2.


UAF Example 2
Код:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

struct auth {
  char name[32];
  int auth;
} auth;

struct auth *authVar;
char *service;

int main(int argc, char **argv)
{
  char line[128];

  while(1) {
      printf("[ auth = %p, service = %p ]\n", authVar, service);

      if(fgets(line, sizeof(line), stdin) == NULL) break;

      if(strncmp(line, "auth ", 5) == 0) {
          authVar = malloc(sizeof(auth));
          memset(authVar, 0, sizeof(auth));
          if(strlen(line + 5) < 31) {
              strcpy(authVar->name, line + 5);
          }
      }
      if(strncmp(line, "reset", 5) == 0) {
          free(authVar);
      }
      if(strncmp(line, "service", 6) == 0) {
          service = strdup(line + 7);
      }
      if(strncmp(line, "login", 5) == 0) {
          if(authVar->auth) {
              printf("you have logged in already!\n");
          } else {
              printf("please enter your password\n");
          }
      }
  }
 }

Программа содержит цикл while (строки 19-44) и действует в соответствии с вводом пользователя, который поступает в строке 22. Ввод команды auth, за которой следует строка, вызывает бранч в строке 25. В результате будет выделено место в соответствии с размером структуры auth и выделенные байты будут установлены в ноль (строка 26). Если строка, переданная после "auth ", меньше 31 байта (строка 27), ее содержимое будет скопировано в область памяти, на которую указывает переменная authVar->name.

Команда service будет использовать strdup для дублирования заданной строки с помощью функции malloc:

The strdup() function returns a pointer to a new string which is a duplicate of the string s. Memory for the new string is obtained with malloc(3), and can be freed with free(3).

The auth struct requires 36 bytes in total, this is 32 bytes for the name and 4 more for the auth integer. The allocator needs to add 8 more bytes to track the size of the chunk which creates a requirement of 0x24 bytes which will finally result a 0x30 bytes allocation due to the 16 bytes alignment. When the program calls the free function the chunk will be added to the tcache in order to serve the next (similar size) allocation requirement. Here is where the service command gets into frame. If we create an allocation requirement for 0x30 bytes, the freed chunk will be assigned to the char *service pointer. Since control the input, according to line 35, we can overwrite the auth integer value with an arbitrary one and pass the login check.
Давайте сначала попробуем сделать это предположение:

1657771452451.png


Как и ожидалось, значение 123456789012345678901234567890AB перезаписало целое число authVar->auth, что позволило нам войти в систему под именем admin. Давайте загрузим программу в gdb, чтобы получить лучшее представление. Установим одну точку останова после функции strcpy и запустим программу:

1657771511976.png




После ввода "auth admin" у нас есть следующие блоки:

1657771533665.png





authVar указывает на 0x555555559ac0 и выделенный чанк имеет размер 0x30 байт:

1657771559967.png



Нажатие кнопки reset вызовет функцию освобождения для выделенного чанка, и он будет добавлен в tcache:

1657771588275.png


Если набрать service 123456789012345678901234567890AB, то будет создано требование на 34 байта (включая пробел после service и новую строку), поэтому указатель service будет указывать на то же место с authVar:

1657771620752.png


Теперь введем логин и рассмотрим чанк по адресу 0x555555559ac0

1657771642804.png


Значение 0x0a42 перезаписало int auth, поэтому if(authVal->auth) будет оценено как true и позволит нам войти в систему.

1657771671299.png




Double Free
C:
#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);
}

В строках 11-18 программа заполняет список tcache.

1657771731550.png



В строках 20-23 программа выделяет еще три чанка размером 1x8, а в строке 30 мы имеем первый вызов free для указателя a. В строке 39 мы имеем второй вызов free для указателя a. До строки 39 список fastbins выглядел следующим образом:

1657771749185.png




И после строки 39 (обратите внимание на адреса в начале и конце списка):

1657771775536.png


В строках с 42 по 44 у нас есть три распределения, которые будут обслуживаться из фастбинов, но поскольку первый и последний чанк одинаковы, a и c будут указывать на одно и то же место 0x5555555593a0. Ошибочное распределение можно проверить, просто запустив программу:

1657771789590.png


Обратите внимание в последних 3 строках, что a и c указывают на 0x55b275b703a0



 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх