Эксперт OTUS — Александр Колесников поделился с нами полезной статьёй, которую написал специально для студентов курса Reverse-Engineering. Basic


Приглашаем вас посмотреть demo day курса, в рамках которого наши преподаватели подробно рассказали о программе курса и ответили на вопросы.


Обратная разработка для того чтобы получить алгоритм это всегда заманчиво, но обратная разработка чтобы создать что-то новое это еще круче. В этой статье мы попытаемся использовать инструмент Frida, для того чтобы сделать чуть проще процесс анализа уязвимого приложения и чуть проще создание эксплойта под это приложение.

Все примеры в статье будут касаться атак на кучу в операционной системе Linux, поэтому запасаемся терпением и попкорном.

Heap

Heap или куча — область памяти, которая используется для динамического выделения памяти под большие объекты. Обычно, обращение к этой области осуществляется через вызов функции malloc. Функция занимается запросами на выделение памяти и по сути запускает цель алгоритмов, которые память предоставляют наиболее оптимальным способом и в наиболее оптимальном объеме. Алгоритмы памяти модифицируются с каждой новой версией ядра, поэтому единого алгоритма для действий по выделению памяти нет.

Достаточно долго исследование кучи и алгоритмов эксплуатации ее функционала были разбросаны по множеству статей. Каждый из подходов напрямую зависел от версии операционной системы или программного обеспечения. Обычно, главным критерием использования той или иной техники эксплуатации является версия libc, которую использует приложение. Существуют следующие атаки:

  1. heap grooming attack

  2. fastbin attack

  3. tcache attack

  4. unlink

  5. largebin

  6. unsortedbin attack

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

Frida

В наборе инструментов frida нам понадобятся как минимум 2 инструмента: frida-trace и MemoryMonitor. Использовать их будем по мере необходимости, в большинстве случаев хватит и одного инструмента. Изучение будем проводить сначала на примерах, которые будут демонстрировать работу атаки или механизма, и следом будем решать задание из уже завершенных  CTF.

Heap Grooming

Атака, которая работает с минимум версии libc 2.23. Атака заключается в том, что можно выделить одинаковые кусочки памяти, высвободить их, и когда аналогичные кусочки будут запрошены, то они будут предоставляться кучей в обратном порядке. Ограничение этой атаки заключается в том, что количество этих кусочков ограничено. В наших зданиях это количество не превышает 7.  Для демо этой методики будем использовать следующий код:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
  unsigned long int *mem0, *mem1, *mem2;
  
  mem0 = malloc(0x10); 
  mem1 = malloc(0x10); 
  mem2 = malloc(0x10); 
  
  printf("Allocated memory on:\nptr0: %p\nptr1: %p\nptr2: %p\n\n", mem0, mem1, mem2);
  
  free(mem0);
  free(mem1);
  free(mem2);
  
  printf("mem0 after free and alloc again: %p\n", malloc(0x10));
  printf("mem1 after free and alloc again: %p\n", malloc(0x10));
  printf("mem2 after free and alloc again: %p\n\n", malloc(0x10));
}

Для компиляции кода не требуется дополнительных опций. И так вывод от скомпилированной версии будет следующим:

В тестовом примере, безусловно все супер, выводятся данные по адресам и мы можем их видеть, но что если в приложении не будет printf? Попробуем сделать такие printf за счет frida-trace. Запустим команду:

Frida-trace -f test -i “malloc”

После первого прогона нужно модифицировать handler. Он находится в директории “handlers” откуда был запущен инструмент frida-trace. И файле malloc.js нужно в разделе OnLeave прописать строку, как показано на экране:

А теперь к реальной проблеме. В качестве примера будем использовать задание с pico CTF “Are you root”. Запустим исследуемое приложение через frida-trace:

Мы не открывали приложение в дизассемблере, а уже можем сказать, что для функции ввода логина используется куча. Судя по значениям, которые выделяются у нас на куче хранятся последовательно значения login; Auth level. Почему так? После ввода данных о логине мы видим последовательный вызов malloc с размером 0x10 и 0x7. Так как минимальное количество данных, которое выделяемся на куче может быть больше, то второе значение помещается в кусочек размера 0x10, просто остаток места не будет содержать полезные данные в результате у нас есть 2 ячейки - с адресами 0x1514eb0 и 0x1514ed0. Написание остального кода эксплойта становится тривиальным. 

TCACHE

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

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    unsigned long int *mem0, *mem1;
	  int target;
    
	  mem0 = malloc(0x10);
    mem1 = malloc(0x10);
    target = 0xdead;
    
    printf("mem0: %p\n", mem0);
	  printf("mem1: %p\n", mem1);
	  printf("int:  %p\n\n", &target);
    
    free(mem0);
    free(mem1);
    
 		printf("Next pointer for mem1: %p\n\n", (unsigned long int *)mem1);
 
    *mem1 = (unsigned long int)&target;
 		printf("Next pointer for mem1: %p\n\n", (unsigned long int )mem1);
     
    printf("Malloc Allocated: %p\n\n", malloc(0x10));
	  printf("Malloc Allocated: %p\n\n", malloc(0x10));
}

Скомпилируем и посмотрим, что может показать frida-trace с модификациями, которые мы вносили в предыдущий раз:

Несмотря на кажущуюся нереальность атаки, мы видим, что действительно удается выделить память по адресу, который используется для хранения значения переменной на стеке. Об этом говорит последний адрес, который был возвращен функцией malloc. Стоит только упомянуть, что подобные трюки возможны только если в приложении есть уязвимость типа Use-After-Free. Попробуем изучить задание с Plaid CTF “cpp”. Модифицируем скрипт для трассировки:

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

Теперь будет проще определить откуда вызывается функция и где, потенциально есть уязвимость.

Выводы

Написание эксплойтов это всегда тяжелый путь. Иногда исследования по обратной разработке в этой области можно сравнить с наблюдениями за динамическими процессами по фото. Фото не всегда делается во время, некоторые данные устаревают и это тормозит исследование. В этой статье мы попробовали усовершенствовать метод путем создания анимации. 


Узнать подробнее о курсе "Reverse-Engineering. Basic"

Читать ещё: