Разработка эксплойтов, часть 1: Анатомия переполнений буфера

  • Автор темы Автор темы LeSh1y777
  • Дата начала Дата начала

LeSh1y777

Пользователь
Регистрация
25/9/25
Сообщения
5,682
Репутация
49
Лайки
152
Депозит
-8.95$
Введение Переполнение буфера

Переполнение буфера, пожалуй, самый коварный тип атак. Переполнение буфера происходит, когда в область памяти помещается слишком много данных для выделенного пространства, и память переполняется. Это переполнение позволяет злоумышленнику выполнить свой собственный, специально созданный код. Код злоумышленника часто представляет собой руткит или другой шелл-код, позволяющий злоумышленнику удалённо управлять системой.

6a4a49_197f9fe197ac4df18ce807d3e21ae2a3~mv2.jpg


В этой статье я попытаюсь дать вам элементарное понимание очень сложной темы. Надеюсь, в будущем я смогу предложить более продвинутый курс по переполнению буфера здесь, на hackers-arise.com, где мы могли бы посвятить больше ресурсов только этой теме. Но здесь, в нескольких уроках, я хочу, чтобы вы хотя бы немного познакомились с:

1. Что такое переполнение буфера?

2. Риски переполнения буфера

3. Основной механизм переполнения буфера

4. Как использовать фаззинг для поиска переполнений буфера

5. Основная терминология и понятия в данной области

6. Инструменты, используемые для анализа и отладки кода

7. Написание простого переполнения буфера

8. Написание шелл-кода

Некоторые определения

Чтобы понять, что такое переполнение буфера, необходимо определить несколько терминов. Понимание переполнения буфера и разработка эксплойтов требуют более глубокого понимания внутренней архитектуры и работы процессора, его регистров памяти и того, как они обрабатывают инструкции и данные. Вот лишь некоторые термины, которые вам необходимо знать для успешного освоения этого модуля.

API – Интерфейс прикладного программирования

Код ассемблера – низкоуровневый язык программирования с несколькими очень простыми операциями.

Big Endian – старший байт сохраняется первым

Буфер – выделенная область памяти фиксированного размера

Байт-код – код, написанный на языке высокого уровня

Компилятор – программа, преобразующая язык высокого уровня в машинный код.

Отладчик — программное обеспечение, позволяющее нам отлаживать проблемы в нашем коде, либо подключаясь к среде выполнения приложения, либо запуская его на виртуальной машине.

Дизассемблер – программный инструмент для преобразования скомпилированных программ в машинный код.

DLL – программный компонент в системах Windows, содержащий функциональные возможности, используемые многими программами.

GDB – отладчик GNU (GDB) – фактический отладчик в системах Unix и Linux. Мы будем использовать его далее в модуле.

Куча – область памяти, выделяемая динамически

Интерпретатор — читает и выполняет программный код построчно, не сохраняя его для повторного использования. Интерпретаторы облегчают достижение платформенной независимости.

Little Endian – наименее значимый бит сохраняется первым. Системы x86 используют метод little endian.

Машинный язык – код, который может читать и понимать процессор

Malloc – вызов функции, которая выделяет n байтов в куче

Memset/Memcpy – memset – это функция, используемая для заполнения кучи указанным количеством байтов, в то время как memcpy копирует определенное количество байтов из одного буфера в другой.

printf – самая распространённая функция LIBC для вывода данных

Песочница — контролируемая среда для выполнения кода, не позволяющая коду влиять на внешние системы.

Шелл-код (shellcode) – традиционно байт-код, запускающий оболочку. Сейчас он имеет более широкое значение. В целом, он относится к любому коду, который выполняется для взлома системы.

Знаковые – целые числа со знаком имеют знаковый бит, обозначающий наличие знака. Данные могут быть как положительными, так и отрицательными.

Стек – область памяти, предназначенная для хранения временных данных.

strcpy/strncpy – обе эти функции LIBC имеют проблемы с безопасностью. strcpy копирует данные из одного буфера в другой без каких-либо ограничений по размеру, гарантируя возможность переполнения буфера. Функция strncpy добавляет параметр размера к strcpy, но может быть неправильно рассчитан при динамической генерации.

Беззнаковые – беззнаковые типы данных могут быть положительными или нулевыми. Отрицательные значения невозможны.

Теория памяти

Прежде чем углубляться в переполнение буфера, необходимо рассмотреть основы теории памяти. Знаю, знаю… теория вам не нужна, и я, как правило, стараюсь её избегать, но в данном случае она неизбежна. Без понимания теории практическое применение будет бессмысленным. Это как пытаться наладить сеть, не понимая TCP/IP.

Чтобы выполнить переполнение буфера, нам потребуется манипулировать памятью таким образом, чтобы заставить процессор выполнить наш код. Начнём с переполнения буфера в стеке, которое заключается в переполнении стека памяти и последующей перезаписи смежных со стеком областей памяти.

Сначала давайте рассмотрим, как организована память программы.

6a4a49_08ca5683352140f09437170c6cc488a9~mv2.png


Стек растёт вниз. Куча растёт вверх. Текстовый сегмент содержит программный код и сегменты данных, содержащие глобальную информацию. Старшие адреса совместно используются стеком и кучей, которые система выделяет во время выполнения. Стек имеет фиксированный размер, тогда как куча — динамическая.

Процессоры Intel имеют регистры общего назначения, которые используются для хранения данных. К ним относятся:

EIP – указатель инструкций

ESP – указатель стека

EBP – базовый указатель

ESI – индекс источника

EDI – индекс назначения

EAX – аккумулятор

EBX – база

ECX – счетчик

EDX – данные

Все эти регистры будут важны для нашего анализа, но ESP, EBP и EIP имеют решающее значение для понимания и выполнения переполнения буфера.

Анатомия переполнения буфера

ESP указывает на вершину стека (на схеме ниже это зелёная область вверху) по самому низкому
адресу памяти, а EBP — на самый высокий адрес внизу стека (зелёная область в нижней части схемы справа). EIP содержит адрес следующей инструкции, которую должен выполнить процессор (ниже EBP). Чтобы выполнить переполнение буфера, нам нужно манипулировать EIP и заставить его указывать на наш вредоносный код.

6a4a49_d4c0447f45484f12b50d5211359656e6~mv2.png
Стек представляет собой структуру FIFO. Для добавления данных в любой стек необходимо использовать инструкцию PUSH в ассемблерном коде. PUSH помещает следующий элемент данных в стек. Данные в стеке хранятся сверху вниз, поэтому новые данные добавляются наверх, а все существующие данные перемещаются вниз. Затем ESP (указатель стека) перемещается на меньший адрес памяти.

При запуске программы создаётся стековый фрейм с локальными переменными, который помещается на вершину стека с помощью команды PUSH. Ключ к атаке с переполнением буфера — получить доступ к EIP (красная область на схеме) или адресу возврата. Получив к нему доступ, мы можем указать на наш вредоносный код.

6a4a49_7869a0a8a9d34138bc3757977fc45cb3~mv2.png


При вводе данных они попадают в стек в обратном порядке. Стек растёт вниз, к EBP. Если нам удастся успешно записать в стек больше данных, выделенных для него, мы, возможно, сможем продвинуться мимо EBP в EIP (красный), перезаписав EIP и указав на наш код.

6a4a49_8f775c5146c54f12828842e064a0514a~mv2.png


Затем EIP укажет на наш код и начнёт его выполнение. Вуаля! Мы выполнили переполнение буфера и получили контроль над системой.

6a4a49_3793ca24df454e569c3cdbb6253084ca~mv2.png


Конечно, это слишком упрощённая концепция переполнения буфера, но с чего-то же надо начинать. В новых операционных системах используются новые методы для ограничения атак на переполнение буфера (хотя они всё ещё возможны), такие как ASLR (рандомизация адресного пространства) и DEP (предотвращение выполнения данных). В этом курсе мы будем считать, что ASLR и DEP отключены или никогда не были реализованы, и выделим переполнения буфера в таких средах в специальный курс, который я надеюсь вскоре здесь представить.

Спасибо Марку Карвалью за прекрасную графику, иллюстрирующую переполнение буфера в стеке.

Угроза переполнения буфера

При исследовании безопасности вы часто будете встречать упоминания об «удалённом выполнении кода» или «произвольном выполнении кода». Практически в каждом случае речь идёт об уязвимости, основанной на переполнении буфера того или иного типа. Например, если обратиться к бюллетеням безопасности Microsoft на Technet, можно найти множество бюллетеней безопасности с предупреждениями об «удалённом выполнении кода». Это почти наверняка переполнение буфера. Хотя в последние годы таких бюллетеней безопасности становится всё меньше, они всё ещё появляются довольно регулярно и почти всегда оцениваются Microsoft как критические.

6a4a49_323e9dad087345ed886d6c16ea1819a6~mv2.png


Давайте перейдём на сайт www.securityfocus.com. Security Focus, пожалуй, моя любимая база данных об уязвимостях, просто потому, что информация хорошо структурирована и охватывает все типы уязвимостей и поставщиков программного обеспечения.

Среди наиболее проблемных программных продуктов последних лет с точки зрения переполнения буфера оказался Adobe Flash Player. Похоже, что в Flash Player почти каждый день обнаруживаются новые уязвимости. В тот день, когда я зашёл на сайт www.securityfocus.com и выбрал Adobe в качестве поставщика программного обеспечения, а Flash Player в качестве заголовка, я обнаружил следующие уязвимости, представленные ниже. В этот день, 7 мая 2015 года, было обнаружено четыре (4) новых уязвимости переполнения буфера. Видно, что названия каждой из них немного отличаются: от «Уязвимости удалённого выполнения кода» до «Уязвимости переполнения буфера» и «Уязвимости переполнения буфера на основе кучи», но все они являются переполнениями буфера. Все они очень опасны и попадают в ту критическую категорию, которую мы рассматриваем в этом модуле.

6a4a49_6e1816345fe44fc9a2913d8b8ec23dcd~mv2.png


Далее



В следующих нескольких статьях мы воспользуемся фаззерами для обнаружения переполнений буфера, а затем разработаем несколько базовых переполнений буфера на архитектуре Intel x86, чтобы продемонстрировать принципы и коварство этого типа уязвимостей.
 
Назад
Сверху Снизу