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

Статья Heap Exploitation, FastBin Dup Consolidate (часть 4.3)

timeshout

RAID-массив
Пользователь
Регистрация
29.06.2022
Сообщения
62
Реакции
83
Введение в эксплуатацию двоичных файлов 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)
Heap Exploitation, FastBin Dup Consolidate (часть 4.2)

The unlink macro

Мы видели из предыдущего сообщения, что во время процесса освобождения чанков и при возникновении определенных условий, аллокатор объединяет соседние чанки в более крупные для более эффективного распределения памяти. Проще говоря, предположим, что у вас есть чанки A, B, C и B освобождается, тогда согласно текущей реализации free будет проверять, используются ли A или C, и если нет, то попытается создать более крупный чанк, объединив их в B . Реализация этой логики показана в приведенном ниже фрагменте кода:

1659755456983.png


После макроса unlink в строках 3977 и 3986 мы приходим к следующему определению:

1659755521176.png


Этот код изменит (см. строки 1350-1351) указатели fd и bk заголовка чанка (см. рисунок ниже) в контексте перестановки двойного списка:

1659755546670.png


Этот процесс аналогичен удалению узла из двойного списка:

1659755564423.png


Но прежде чем что-то произойдет, выполняется проверка на валидность (в строке 1347):

1659755584459.png


Сначала заметим, что FD = P→fd и BK = P→bk , поэтому FD должен указывать на следующий соседний кусок, а BK - на предыдущий соседний кусок:

1659755604745.png


Таким образом, чтобы двойной связный список был действительным, BK→fd и FD→bk должны указывать на P :

1659755645510.png


После проверки этого условия мы получили следующие назначения FD->bk = BK и BK->fd = FD, так что наши чанки теперь будут выглядеть следующим образом:

1659755663941.png


Exploitation plan

Как мы уже упоминали в начале статьи, мы полностью контролируем содержимое чанка и благодаря ошибке переполнения можем изменять метаданные соседнего чанка. Поэтому на пути к успешной эксплуатации мы должны пройти проверку на отвязку:

1659755707991.png


Для этого мы сделаем следующее:

Создадим поддельный чанк внутри управляемого чанка.

Мы собираемся вставить определенные значения в определенные адреса памяти, чтобы сформировать действительную структуру чанка внутри сектора данных контролируемого чанка:

1659755727902.png


Поскольку мы хотим пройти проверку на несвязанность, значения, которые мы собираемся вставить в качестве fd и bk в поддельный чанк, должны указывать на структуры, соответствующие указатели fd и bk которых будут указывать обратно на наш поддельный чанк! Чтобы представить себе эту концепцию, давайте посмотрим на пару рисунков:

1659755774676.png


Представьте, что таблица Global_Var формирует chunk struct, тогда по адресу памяти 0x6020b0 мы будем иметь размер предыдущего чанка, по адресу 0x6020b8 - размер текущего чанка, по адресу 0x6020c0 - указатель fd и по адресу 0x6020c8 - указатель bk. Таким образом, у нас есть fake_chunk.fd→bk = 0x1967030 и fake_chunk.bk →fd=0x1967030, что пройдет проверку на валидность отвязки.

Следующий шаг:

Измените заголовок следующего чанка, чтобы показать фальшивый чанк как свободный

Помните: из-за переполнения кучи мы можем писать за границы КОНТРОЛИРУЕМОГО ЧАЙНА, поэтому мы можем модифицировать заголовок следующего ЧАЙНА:

1659755811887.png


Что касается типа модификации, напомню, что заголовок выделенного чанка состоит из текущего размера и размера предыдущего чанка, если и только если предыдущий чанк свободен. Я размещаю структуру чанка еще раз, чтобы вам не пришлось прокручивать страницу вверх:

1659755831208.png


Помните также, что размер mchunk_size включает в себя 3 флага, на которые указывают последние 3 бита значения. Так, если размер равен 0x10 и предыдущий чанк используется, mchunk_size будет выглядеть следующим образом:

1659755849779.png


Остальные флаги нас сейчас не волнуют, поскольку нам нужно перевернуть только последний бит, чтобы указать, что предыдущий чанк не используется. Это запустит процесс обратной консолидации, который, в свою очередь, запустит макрос unlink.

И последнее, но не менее важное: размер mchunk_prev_size должен соответствовать размеру поддельного чанка, чтобы обойти остальные проверки безопасности. Если все будет так, как должно быть, то при создании NEXT CHUNK, FAKE CHUNK будет консолидирован, а указатели fd, bk FAKE CHUNK будут перезаписаны в двух последующих шагах:

1659755872804.png


1659755881635.png



Assume the following C program:
C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

typedef void (*dn)();

uint64_t *chunk0_ptr;

void doNothing()
{
    printf("nothing\n");
}
void shell()
{
  system("/bin/sh");
}

int main()
{

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


    chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
    uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1

    chunk0_ptr[1] = chunk0_ptr[-1] - 0x10;
    chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
    chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);

    uint64_t *chunk1_hdr = chunk1_ptr - header_size;
    chunk1_hdr[0] = malloc_size;
    chunk1_hdr[1] &= ~1;

    free(chunk1_ptr);

    dn d = doNothing;

    chunk0_ptr[3] = (uint64_t) &d;
    chunk0_ptr[0] = (uint64_t) &shell;

    (*d)();

}

Давайте разберем это построчно, чтобы понять суть. В строке 6 мы определяем указатель на функцию, которая возвращает void и не принимает никаких параметров. В строке 8 мы определяем указатель на беззнаковое целое число, а в строках с 10 по 17 мы определяем две функции, doNothing, которая не делает абсолютно ничего, и shell, которая открывает оболочку. В строке 26 мы имеем первый malloc размером 0x420 байт, поэтому после этого оператора у нас будут следующие куски:

1659755927851.png



Таким образом, chunk0_ptr указывает на 0x555555555592a0 (часть данных чанка), в то время как заголовок того же чанка начинается на 0x10 байт раньше, по адресу 0x555555559290 . Наконец, адрес chunk0_ptr находится по адресу 0x55555555558018 , поэтому, чтобы продолжить, после первого malloc мы имеем следующее:

1659755944723.png


В строке 27 мы имеем второй вызов malloc, и после этого наши куски будут выглядеть следующим образом:

1659755963785.png


Creating a fake chunk

1659755987628.png


Строка 29 установит размер поддельного чанка в 0x421, поскольку chunk0_ptr[-1] равен 0x431

1659756002947.png


А в строках 30-31 мы устанавливаем указатели fd/bk поддельного чанка:

chunk0_ptr[2] = 0x55555555558018 - 3 * 8 = 0x555555558000
chunk0_ptr[2] = 0x55555555558018 - 3 * 8 = 0x555555558008
Таким образом, наш управляемый набор чанков будет выглядеть следующим образом:

1659756040241.png


И это пройдет проверку на развязку, поскольку, как вы помните из проверки на развязку, у нас будет следующее:

FD = P->fd => FD = 0x0000555555558000
BK = P->bk => BK = 0x0000555555558008
Впоследствии FD→bk переместит указатель FD на 3 позиции вперед (так как это позиция bk в заголовке чанка => 0x0000555555558000 + 0x18 = 0x0000555555558018), а FD→fd переместит указатель BK на 2 позиции вперед (так как это позиция fd в заголовке чанка => 0x0000555555558000 + 0x10 = 0x00005555558018).

1659756062274.png


Подводя итог, можно сказать, что на данный момент мы работаем следующим образом:

1659756134568.png


“Fixing” the next chunk’s header

Это самая простая для понимания часть: поскольку мы можем писать за пределами контролируемого чанка, будет тривиально перезаписать соседний. Именно это и демонстрируют строки 33-35:

1659756162968.png


chunk1_hdr[0] будет указывать на размер mchunk_prev_size, а chunk1_hdr[1] - на текущий размер. Строка 35 перевернет последний бит, чтобы фальшивый чанк отображался как неиспользуемый:

1659756181203.png


Теперь мы готовы звонить бесплатно:

1659756196578.png


After free


Давайте теперь посмотрим, как действует функция free. Обратите внимание, что перед ее вызовом мы имеем следующее:*0x555555558018 = 0x00005555555592a0

1659756222681.png

1659756230174.png




Во время свободного выполнения будут выполняться следующие утверждения:

1659756256096.png


И немедленно:

1659756273944.png


1659756288283.png


Write anything anywhere
Вспомните из нашей программы С следующие строки сразу после бесплатного звонка:

1659756318095.png


Переменная d указывает на функцию doNothing, но поскольку мы контролируем содержимое chunk0_ptr, мы можем изменить значение chunk0_ptr[3], таким образом, после строки 41 мы получим следующее:

1659756333145.png


Поэтому это будет &chunk0_ptr = 0x00007fffffffe338, который содержит адрес doNothing. Итак, chunk0_ptr[0] указывает сюда:

1659756350642.png


Таким образом, строка 42 перезапишет содержимое этого адреса памяти адресом функции shell:

1659756382354.png


Это завершает последний этап эксплуатации:



1659756395097.png





The toddler’s introduction to Heap Exploitation, Unsafe Unlink(Part 4.3)
 


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