Возьмем, например, программу-дрозофилу:
main(int argc, char *argv[] )
{
process_data(argv[1]);
}
#define FIXED 16
process_data (char *data)
{
char buf[FIXED];
strcpy (buf, data);
return;
}
Для нее стек после вызова process_data() будет выглядеть примерно так, как это показано на рис. 9.1.
Рис. 9.1. Состояние стека после вызова уязвимой функции
Теперь уже не надо быть суперхакером, чтобы заметить, что адрес возврата находится не только в одном сегменте с локальными переменными, но и имеет больший адрес. Тогда, передав в качестве данных строку, имеющую заведомо больший размер, чем у отведенного под ее обработку буфера, мы сможем затереть все, что лежит в памяти выше, чем этот буфер, так как функция strcpy() будет копировать данные до тех пор, пока не встретит нуль-символ \0. В нашем примере достаточно передать как входной параметр строку длиной более 15 байт для выхода за границу буфера плюс еще несколько байт для изменения собственно адреса возврата.
Не случайно в приведенных выше рассуждениях ни разу не встретилось упоминания о конкретной операционной системе. Действительно, технология переполнения локального буфера весьма универсальна и будет работать практически в любой ОС (об ограничениях чуть ниже), поэтому читатель может скомпилировать программу-дрозофилу в его любимой ОС и посмотреть на результат, подав на вход, скажем, строку из 30 единиц (этого должно быть достаточно для любой ОС и любого компилятора). UNIX-системы при этом выведут что-то типа «Segmentation fault, core dumped». Информация от Windows NT (рис. 9.2) для хакера более наглядна – по ней сразу понятно, что произошло именно переполнение буфера с возможностью подмены адреса возврата, так как адрес, на котором «споткнулась» программа, был не чем иным, как 0x31313131. Это соответствует шестнадцатеричному коду для строки из четырех единиц. Если ввести строку, состоящую из неодинаковых символов, например 01234567890abcdefghijklmnopqst, то по выведенному адресу станет ясно, в каком месте строки должен стоять будущий адрес возврата.
Рис. 9.2. Информация о сбое программы в Windows NT
Итак, цель – передача управления – хакером достигнута. Теперь дело за малым. Нужно выполнить следующие шаги:
1. Найти подходящую программу, которая не только содержит процедуру, похожую на process_data(), но и выполняется с большими привилегиями. Если хакеру доступны исходные тексты, то особое внимание надо обратить на программы, содержащие функции strcat(), strcpy(), sprintf(), vsprintf(), gets(), scanf() и т. п. Если исходных текстов нет, то остается ручной (или автоматизированный) поиск уязвимых программ, то есть подача на вход длинных строк и оценка результатов.
2. Определить для найденной программы, какой размер буфера надо использовать, где в буфере должен располагаться адрес возврата и т. п.
3. Написать код, на который осуществится переход. Для ОС UNIX стандартный вариант – вызов оболочки следующим образом:
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
Для Windows NT это сделать сложнее.
4. Каким-то образом внедрить свой код в систему (хороший вариант – расположить его все в той же строчке). При этом злоумышленнику надо проверить, чтобы вызываемая функция при обработке этой строки не испортила данный код. Другая проблема – если process_data() использует strcpy() или любые другие стандартные функции работы со строками, то код должен быть написан так, чтобы он не содержал нулей, потому что в противном случае его копирование остановится на первом нуле. Заметьте, что код вызова оболочки уже содержит, по крайней мере, три нуля: один в конце "/bin/sh" и два NULL. Возможен вариант, когда не обойтись без нулей (например, сам адрес возврата должен их содержать), тогда можно, например, зашифровать код так, чтобы нули исчезли, а затем в начале кода использовать его расшифровщик.
В 1990 и 1995 годах Кристофером Клаусом (Christopher Klaus) [26] было протестировано около 80 программ на 9 различных платформах. Специальная программа подавала на вход строки длиной до 100 000 символов. В результате 25–33 % программ в 1990 году и 18–23 % в 1995 году работали некорректно – зависали, сбрасывали аварийный дамп и т. п. Интересно, что в коммерческих версиях UNIX этот процент доходил до 43, тогда как в свободно распространяемых он был меньше 10. Впрочем, справедливости ради надо отметить, что только две программы-демона вели себя таким образом в 1990 году, а через 5 лет эти ошибки были исправлены.
На практике, когда мы занимались анализом безопасности одного из шифраторов IP-трафика, построенного на базе OC FreeBSD 2.2, нам потребовалось совсем немного времени, чтобы найти типичную ошибку переполнения буфера в SUID root-программе suidperl. Получить полномочия суперпользователя удалось передачей в качестве параметра строки из 1 197 байт, содержащей стандартный код вызова оболочки.
Читать дальше
Конец ознакомительного отрывка
Купить книгу