В данном руководстве будет продемонстрирована эксплуатация переполнения буфера с помощью ret2libc.
Автор: sickn3ss
http://sickness.tor.hu
Перевод: SecurityLab.ru
ПРИМЕЧАНИЕ: Перед чтением данного руководства рекомендуем вам просмотреть:
Во второй части данного руководства, при компиляции уязвимого приложения, мы использовали флаг -z execstack для gcc. Таким образом, был получен исполняемый стек. Но проблема в том, что большинство современных операционных систем по умолчанию использует неисполняемый стек.
В предыдущих частях данного руководства эксплоиты разрабатывались на Backtrack 4 R2. В данном руководстве эксплоит будет создан на Debian Squeeze.
Необходимые навыки:
Данное руководство не будет понятным пользователям, которые не владеют вышеуказанными знаниями.
Выражаясь простыми понятиями, неисполняемый стек предотвращает выполнения кода в области памяти стека (или динамической памяти). Также неисполняемый стек может предотвращать запись исполняемого кода, что может в некоторых случаях предотвратить эксплуатацию переполнения буфера. Таким образом, произвести переполнение буфера путем инъекции в стек исполняемого кода представляется невозможным, если используется неисполняемый стек.
Более подробно о неисполняемом стеке можно прочитать здесь: http://en.wikipedia.org/wiki/NX_bit
Для того чтобы обойти это ограничение, мы воспользуемся техникой “ret2libc” (возвращение к libc).
Эксплоиты, продемонстрированные в предыдущих статьях данной серии, имеют такую структуру:
##############################
мусорные данные + NOP + шеллкод + EIP (переписанный с помощью инструкции JMP/CALL в регистр, указывающий в область мусорных данных и NOP)
##############################
В случае с неисполняемым стеком, данная техника работать не будет. Выполнение инструкции jmp для стека приведет к ошибке сегментации. Вот здесь нам и пригодиться библиотека libc. Вместо перезаписи EIP с помощью инструкции, мы производим перезапись EIP с помощью функций, которые содержаться в библиотеке libc, после которых укажем аргументы функций.
ПРИМЕЧАНИЕ: Можно сделать так, чтобы код возвращался в любое место, которое вам необходимо. Но в этих целях чаще всего используется libc, так как она всегда используется в приложениях и предоставляет полезные вызовы.
Мы разобрались с тем, для чего нужно использовать libc. Теперь давайте перейдем к практическому применению техники вызова переполнения буфера.
Мы располагаем уязвимым приложением:
##############################
#include <stdio.h>
#include <string.h>
void evil(char* input)
{
char buffer[500];
strcpy(buffer, input); // Vulnerable function!
printf("Buffer stored!\n");
printf("Buffer is: %s\n\n",input);
}
int main(int argc, char** argv)
{
evil(argv[1]);
return 0;
}
##############################
В предыдущей части данного руководства мы производили компиляцию приложения с помощью флага -z execstack в gcc. На этот раз попробуем произвести компиляцию, используя настройки по умолчанию (используем неисполняемый стек).
Рис. 1
Присоединяем уязвимую программу к gdb и устанавливаем точки остановки на “call evil” и “ret” из функции “evil” для вычисления смещения, в которое будут переданы нужные данные.
Рис 2
Рис. 3
После установки точек остановки, отправляем приложению пакет мусорных данных и смотрим, что из этого получиться.
Рис. 4
Рис. 5
Как было показано, для перезаписи нам дополнительно требуется 8 байтов, что в итоге даст 516 байт (Это означает, что объем мусорных данных должен составлять 512 байтов).
Теперь, давайте проверим, можно ли использовать libc для отправки нижеуказанной команды в GDB.
###########################
maint info sections ALLOBJ
###########################
Рис. 6
Мы не получили никаких нулевых данных, следовательно libc нам подходит.
Требуемое нам смещение равно 516. В начале данного руководства, было сказано о том, что EIP будет переписано с помощью инструкции JMP/CALL, так как это приведет к ошибке сегментации. Вместо этого, мы попытаемся перезаписать EIP с помощью одной из функций libc, а затем продолжим вызывать различные функции и передавать требуемые аргументы.
Мы будем работать с такими функциями:
Нам необходимо отыскать адреса функций system() и exit(), а также адрес “/bin/bash”, который необходимо указать как аргумент для system().
Структура нашего эксплоита, будет выглядеть таким образом:
##############################
мусорные данные * 512 + system() + exit() + адрес /bin/bash
##############################
Давайте найдем все необходимые адреса для формирования работоспособного эксплоита.
Рис. 7
Адрес system(), по всей видимости, действительный.
Рис. 8
Данный адрес хранит нулевой байт, поэтому использовать его нельзя. Функция exit() для наших целей не обязательна, эксплоит будет работать и без нее, но давайте все же попытаемся ее отыскать. Если мы окажемся в ситуации, в которой, например, выходная функция будет содержать нулевой байт, нам удастся быстро найти ей замену, равную значению exit+смещение.
По адресу 0xb7ebc304 можно найти функцию <exit+4>, которая нам подходит.
Рис. 9
Теперь найдем /bin/bash.
Рис. 10
Давайте проследим за /bin/bash.
Рис. 11
Теперь нам необходимо изменить адрес таким образом, чтобы заменить только “/bin/bash” для получения действительных аргументов для system().
Рис. 12
Все необходимые адреса найдены, перейдем к эксплуатации:
##############################
мусорные данные * 512 + “\x80\x61\xec\xb7” + “\x04\xc3\xeb\xb7” + “\x73\xf7\xff\xbf”
##############################
Посмотрим, что у нас получиться:
Рис. 13
Эксплуатация прошла успешно.
Наш канал — питательная среда для вашего интеллекта