- •Благодарности
- •Список использованных сокращений
- •От издательства
- •Введение
- •Глава 1. Начало
- •Как провести аудит законно?
- •Методология взлома
- •Резюме
- •Глава 2. Получение информации из открытых источников
- •Введение
- •Что искать?
- •Использование Google для сбора информации
- •Ограничение поиска одним сайтом
- •Поиск файлов определенного типа
- •Поиск определенных частей сайта
- •Google Hacking
- •Поиск информации о людях
- •Архивные данные
- •Netcraft
- •Получение информации о домене
- •Автоматизация процесса
- •FOCA
- •Сбор базы данных адресов e-mail
- •recon-ng
- •Упорядочить информацию
- •Резюме
- •Глава 3. Получение информации от сетевых сервисов
- •Введение
- •Сканирование портов
- •Определение активных хостов
- •UDP-сканирование
- •NMAP
- •Получение информации от DNS-сервера
- •Типы записей
- •Взаимодействие с DNS-сервером
- •MX-записи
- •NS-запросы
- •Перебор имен
- •Перебор обратных записей
- •Передача зоны DNS
- •Получение информации с использованием SNMP
- •Получение информации с использованием NetBIOS
- •Null session
- •Работа с электронной почтой
- •Анализ баннеров
- •Получение информации от NTP-сервера
- •Поиск уязвимостей
- •Резюме
- •Глава 4. Атаки на веб-приложения
- •Знакомство с сookie
- •Межсайтовый скриптинг (XSS)
- •Включение локальных или удаленных файлов
- •SQL-инъекции
- •Резюме
- •Глава 5. Социальная инженерия
- •На кого обратить внимание?
- •Фазы атаки
- •Манипулирование людьми
- •Типы атак
- •Social-Engineer Toolkit
- •Резюме
- •Глава 6. Получаем пароли
- •Основные методы
- •Работа со списками паролей
- •Онлайн-атаки
- •Радужные таблицы
- •Резюме
- •Глава 7. Беспроводные сети
- •Краткий обзор Wi-Fi
- •Bluetooth
- •Резюме
- •Глава 8. Перехват информации
- •Пассивный перехват трафика
- •Активный перехват
- •Резюме
- •Глава 9. Обход систем безопасности
- •Системы обнаружения атак
- •Брандмауэры
- •Приманки
- •Резюме
- •Глава 10. Вредоносные программы
- •Вирусы
- •Черви
- •Шпионы
- •Рекламное ПО
- •Троянские кони
- •Практическая часть
- •Резюме
- •Глава 11. Metasploit Framework
- •Интерфейс
- •Вспомогательные модули
- •Полезная нагрузка
- •Практические навыки
- •Резюме
- •Глава 12. Передача файлов
- •TFTP
- •Загрузка файлов с использованием скриптов
- •Резюме
- •Глава 13. Превышение привилегий
- •Локальное повышение прав в Linux
- •Локальное повышение прав в Windows
- •Повышение привилегий в случае некорректной конфигурации прав доступа
- •Резюме
- •Глава 14. Перенаправление портов и туннелирование
- •Перенаправление портов
- •SSH-туннелирование
- •proxychains
- •Резюме
- •Глава 15. Переполнение буфера
- •Атаки, направленные на переполнение буфера
- •Введение
- •Что такое переполнение буфера?
- •Программы, библиотеки и бинарные файлы
- •Угрозы
- •Основы компьютерной архитектуры
- •Организация памяти
- •Разбиение стека (Smashing the stack)
- •Перезапись указателя фрейма
- •Атака возврата в библиотеку
- •Переполнение динамической области памяти
- •Пример нахождения уязвимости переполнения буфера
- •Резюме
- •Глава 16. Собирая все воедино
- •Стандарт выполнения тестов на проникновение
- •Подготовительная фаза
- •Договор о проведении работ
- •Получение разрешения
- •Сбор данных
- •Анализ уязвимостей
- •Моделирование
- •Эксплуатация уязвимостей
- •Постэксплуатационный этап
- •Отчет
- •Зачистка
- •Введение
- •Глава 17. Личный пример
- •Глава 18. Бумажная работа
- •Политика безопасности
- •Стандарты
- •Процедуры
- •Инструкции
- •Техническая документация
- •Глава 19. Обучение и тренировки
- •Тренировки
- •Глава 20. Защита от утечки информации
- •Глава 21. Брандмауэры
- •Глава 22. Системы обнаружения вторжения (IDS)
- •Глава 23. Виртуальные защищенные сети (VPN)
- •Компоненты виртуальной частной сети
- •Безопасность VPN
- •Создание VPN из компонентов с открытым исходным кодом
- •Заключение
198 Глава 15 • Переполнение буфера
На рис. 15.7 изображен буфер, в который помещен эксплойт для его переполнения. Используя этот подход, мы увеличиваем вероятность того, что начальный адрес перезапишет адрес возврата. Более того, теперь нам не обязательно перенаправлять выполнение именно на начало шелл-кода, будет достаточно и того, что АЛУ перей дет на любую из NOP-инструкций. Когда АЛУ попадет на NOP-инструкции, оно просто будет пропускать их одну за другой до тех пор, пока не дойдет до шелл-кода и не выполнит его.
Третье условие может быть выполнено заменой нуля на символ с кодом 90h, так как машинная инструкция с кодом 90h — это NOP.
Еще один способ избежать нулевого байта — маскирование части шелл-кода. Можно заменить все 0 на 1, а в шелл-код встроить функцию, которая преобразует их обратно.
Также существует возможность написания шелл-кода для х86-архитектуры с использованием буквенно-цифровых значений. Если открыть такой код в текстовом редакторе, он будет выглядеть как непонятный набор цифр, а также прописных и строчных букв. Такой подход дает атакующему большое преимущество, так как позволяет избежать обнаружения шелл-кода стандартными средствами защиты.
Перезапись указателя фрейма
Как мы уже могли убедиться ранее, возможность осуществления разбиения стека появляется вследствие отсутствия или неправильно организованной проверки размера данных, находящихся в буфере. И как следствие, максимальный размер шелл-кода заранее неизвестен.
Другой распространенной ошибкой при программировании на языке С является так называемая «off-by-one error», или ошибка на единицу. Чаще всего это происходит в цикле, выполняющем перебор элементов. Например, перебор элементов массива начинается с 1, а не с 0.
Изначально может создаться ложное впечатление, что ничего плохого из-за одного байта произойти не может, но это не так. На самом деле в данном случае перед злоумышленником открываются новые возможности манипуляцией буфером.
Представим себе ситуацию, в которой первая локальная переменная во фрейме стека является буфером, уязвимым к атакам типа «off-by-one error» во время обработки пользовательских данных. В случае, когда между данной переменной и указателем фрейма нет других данных (см. рис. 15.4), введенный дополнительный байт может переписать один байт сохраненного указателем фрейма (Х на рис. 15.4).
Хорошая новость состоит в том, что из-за сохраненного адреса возврата (V на рис. 15.4) у злоумышленника не будет возможности выполнить шелл-код, который он мог заранее загрузить в буфер.
Плохая же новость в том, что для достижения данной цели злоумышленнику придется совершить всего лишь пару дополнительных действий. Для начала отметим,
Атаки, направленные на переполнение буфера 199
что в архитектуре х86 память организована таким образом, что наиболее важным является байт с наименьшим адресом «little endian». Это означает, что из четырех битов, которые составляют слово, первым идет бит, имеющий наименьшую важность, или имеющий самый низкий адрес. Если провести аналогию с десятичной системой, то получится, что числа будут записаны в обратном порядке, например 1234 будет сохранен таким образом, что 4 будет иметь низший адрес в памяти, а 1 — высший.
Па а
А
а а
С а
а
а а
а
Л а •
•
Па а
А
а а
С а
а
а а
а
Л а •
•
Д • • ‹ а
func()_good
func()_bad
Па а
А
а а
С а
а
а а
а
func()_bad
А
а а а
-а
«За а»
Ш-
П • • ‹ а
Рис. 15.8. Перезапись указателя фрейма
Теперь предположим, что переполнение происходит в функции bad_func(), которая вызывается функцией good_func(). В ходе атаки злоумышленник сможет изменить низший бит — в нашем примере цифру 4 — сохраненного указателя фрейма функции good_func(). Почему это так важно? Представьте, что порядок битов в памяти будет другим, «big endian». В таком случае любое изменение значения указателя
200 Глава 15 • Переполнение буфера
фрейма привело бы к тому, что он указал бы на адрес, который находится вне текущего контекста исполнения программы. Например, 1234 поменялось бы на 3234. Но в нашем случае мы меняем низший байт, например 1234 на 1232. И в данном случае у злоумышленника есть все шансы поменять адрес указателя фрейма на такой, который привел бы к выполнению загруженного шелл-кода.
Как уже было сказано, указатель фрейма используется для доступа к параметрам и локальным переменным функции. Первым результатом изменения указателя фрейма функции good_func() будет ее работа с неправильными данными и, как следствие, возвращение неправильного результата. В лучшем случае после того, как функция попытается обратиться к ячейке памяти, находящейся вне допустимого ранее выделенного интервала, программа будет экстренно завершена. При худшем развитии событий будет достигнут эпилог функции good_func(). Как говорилось ранее, на третьем шаге эпилога будет выполнена команда pop, при помощи которой в указатель инструкции будет скопировано значение, находящееся за указателем фрейма.
Итак, единственное, что нужно сделать для выполнения шелл-кода, это изменить сохраненное значение указателя фрейма функции good_func() на такое, которое указывало бы на адресное пространство в области памяти, находящейся на одно слово ниже. Где, собственно, и будет находиться начальная часть шелл-кода.
Атака возврата в библиотеку
Для рассмотрения атак возврата в библиотеку (англ. Return-into-libc) вновь обратимся к рис. 15.2, на котором изображен стек перед вызовом my_func(). Если рассмотреть ситуацию с точки зрения вызываемой функции, то сначала стек будет содержать адрес возврата, который она должна использовать, а уже затем — параметры. Как мы упоминали в посвященном разбиению стека разделе, основной целью атакующего является запуск шелл-кода для получения доступа к системе. Обычно подобные шелл-коды используют в Unix-подобных системах такие функции, как system() или execve(), а в системах под управлением ОС Windows — WinExec(). В качестве параметра вызова данные функции используют имя и/или путь программы.
Как альтернативу разбиению стека используют прямой вызов определенной функции. Данный способ имеет одно большое преимущество — его нельзя предотвратить, используя защищенный от исполнения стек.
Для осуществления такого вида атаки будет достаточно перезаписать адрес возврата адресом функции, которую необходимо вызвать, и заполнить ячейку адреса возврата вызываемой функции случайными данными — «заглушкой», а в конце задать параметры вызова функции.
На рис. 15.9 показано состояние буфера и регистров после проведения атаки возврата в библиотеку, после второго шага эпилога. Нормальное состояние стека на аналогичном шаге показано на рис. 15.5. Начальный адрес целевой функции
Атаки, направленные на переполнение буфера 201
X |
|
|
|
|
Р •• |
||
|
|
|
|
|
|
|
|
|
|
|
|
|
IP |
|
R |
|
|
|
|
|
|
|
|
|
|
|
|
|
SP |
|
W |
|
|
|
|
|
|
|
|
Y |
|
|
|
BP |
|
P |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
а а1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
а а n |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
« а а» |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
W Q=&libfunc() |
|
|
SP |
|
|
||
|
|
|
|
W’ P
Рис. 15.9. Атака возврата в библиотеку
libfunc(), Q, будет занесен в указатель инструкции на третьем шаге эпилога оператором РОР по адресу R. Если данную ситуацию сравнить с изображенной на рис. 15.5, то можно заметить, что параметры для вызова libfunc() сдвинуты вверх на одно слово, — это необходимо для установки «заглушки». Интересно, что поддельное значение указателя фрейма Р будет снова занесено в стек после выполнения пролога функции libfunc(). В зависимости от ОС, архитектуры и целевой функции процесс нахождения адреса нужной функции может быть затруднен, но, безусловно, это вполне выполнимая задача.
Переполнение динамической области памяти
Возвращаясь к главе 6, вспомним, что при помощи функции malloc() программа может запрашивать необходимое количество памяти в динамической области (heap), а позже, после исполнения, вернуть ОС управление над данной областью при помощи функции free().
Если вы можете размещать в памяти буфер, следовательно, кто-то сможет этот буфер переполнить. Однако выполнение атаки, направленной на переполнение буфера, находящегося в динамической области, отличается от таковой при расположении буфера в стеке. Основное отличие заключается в том, что вы не найдете указателя фрейма или адреса возврата, который можно было бы перезаписать,