Слепое программирование с возвратом

Слепое программирование с возвратом (англ. Blind return-oriented programming, BROP) — это техника эксплуатации, позволяющая создать эксплойт даже в тех случаях, когда атакующий не обладает бинарным файлом целевой программы. Атаки слепого программирования с возвратом, продемонстрированные Биттау и соавторами, позволили обойти такие механизмы защиты, как рандомизация пространства адресов процессов (ASLR) и стековые канареечные значения (stack canaries) на 64-битных системах[1].

Предпосылки

С учётом современных улучшений в области безопасности операционных систем и аппаратного обеспечения, таких как проект PaX (используемый в ядре Linux), невозможна прямая инъекция кода. В результате исследователи в области информационной безопасности разработали новую атаку под названием программирование с возвратом (англ. return-oriented programming, ROP), чтобы обойти защиту памяти NX (non-executable). Такая атака основывается на управлении выполнения программы через стек и, особенно, контроль возвратных адресов. В основе ROP лежит понятие гаджета — последовательности машинных команд, завершающихся инструкцией возврата (ret), при определённом состоянии стека. Гаджет может выполнять простые операции, например, загрузку слова из памяти в регистр, или более сложные, например, условный переход. При достаточно большом бинарном файле можно собрать тьюринг-полное множество гаджетов, чего достаточно для исполнения произвольного шеллкода. Одно из ключевых допущений классических атак ROP заключается в наличии у атакующего бинарного файла программы-жертвы, что позволяет заранее знать адреса гаджетов.

Сценарии применения BROP

Слепое программирование с возвратом может быть применено в следующих случаях:[2]

  1. Когда сервис работает как закрытый бинарный файл, и обнаружение уязвимостей возможно только при помощи техник фаззинга и пентеста.
  2. Когда используется известная уязвимость в открытой библиотеке, однако бинарный файл приложения, её использующего, остаётся закрытым.
  3. Когда требуется взломать открытый сервер с неизвестным бинарным файлом.

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

Фазы атаки

Чтение стека

Возвратные адреса на стеке обычно защищаются с помощью стековых канареек (stack canaries). Канареечное значение вызывает аварийное завершение программы при его изменении из-за переполнения буфера. В модели атаки BROP переполнение буфера осуществляется по одному байту за раз: каждая попытка приводит либо к сбою программы, либо к продолжению её работы. Если произошёл сбой, это значит, что значение было угадано неверно, поэтому можно перебором (в среднем 128 попыток из 256) точно восстановить значение. На 64-битных архитектурах для утечки полного значения канарейки требуется четыре таких чтения стека. После утечки канарейки можно также пытаться перебрать возвратный адрес. Важно отметить, что восстановление точного значения возвратного адреса может быть затруднено — атакующему достаточно получить любой адрес внутри текстового сегмента пространства адресов.

Слепое программирование с возвратом

Этот этап является ядром атаки. Его цель — инициировать системный вызов write и отправить дамп бинарного файла атакующему. Системный вызов write требует три параметра: сокет, буфер и длину. Согласно конвенциям вызова x86-64 параметры передаются через регистры, поэтому требуется подобрать подходящие последовательности инструкций pop rdi; ret, pop rsi; ret и pop rdx; ret для задания аргументов. Пример простой цепочки ROP для вызова write:

  1. pop rdi; ret (сокет)
  2. pop rsi; ret (буфер)
  3. pop rdx; ret (длина)
  4. pop rax; ret (номер системного вызова write)
  5. syscall

Проблема заключается в том, что даже если нужные гаджеты найдены, после передачи управления по возврату может возникнуть обращение к неисполняемому стеку. Для решения этой проблемы были введены так называемые стоп-гаджеты (stop gadgets) — инструкции, вызывающие блокировку программы, например, бесконечный цикл или блокирующий системный вызов (например, sleep), что позволяет атакующему продолжить дальнейшую атаку, поскольку процесс остаётся активен.

В реальном применении используются дополнительные оптимизации. Например, можно не использовать прямой системный вызов write, а обратиться к соответствующей функции через таблицу процедурных ссылок (PLT). Также иногда применяется функция strcmp для инициализации регистра RDX, поскольку гаджеты вида pop rdx; ret встречаются крайне редко.

Построение эксплойта

После того как удалось обнаружить функцию записи (write) через PLT, атакующий может получить дамп бинарного файла, чтобы искать новые гаджеты стандартными средствами анализа ROP. Наличие достаточного числа гаджетов позволяет создать шеллкод и, как следствие, получить полный контроль над системой, включая root-доступ.

Защита

Ключевое предположение в модели BROP — сервис перезапускается после каждого сбоя и при этом не происходит повторной рандомизации адресного пространства. Поэтому включение повторной рандомизации при каждом запуске практически полностью предотвращает атаку BROP. Ещё одним методом, реализованным в NetBSD и Linux, является задержка (sleep) при краше: это значительно замедляет атаку и даёт администратору время для расследования подозрительной активности. Также против традиционных атак с угоном потока исполнения, включая ROP и BROP, эффективна технология контроля целостности потока управления (Control Flow Integrity), однако она ведёт к заметным накладным расходам на производительность.

Похожие атаки

Другой атакой, схожей по характеру с BROP, является JIT-ROP (Just-In-Time-ROP, программирование с возвратом «на лету»). Она также использует раскрытие информации для обхода защиты ASLR и поиска гаджетов в бинарном файле для реализации атаки ROP путём эксплуатации утечки данных. Однако, в отличие от BROP, JIT-ROP не является интерактивной и не реагирует на сбои во время выполнения: вместо этого атакующий запускает скрипт для поиска гаджетов, а затем формирует эксплойт для доставки. Кроме того, JIT-ROP требует две разные уязвимости (в куче и стеке), которые должны быть известны заблаговременно, тогда как BROP достаточно только одной стековой уязвимости[3].

Примечания

Литература

Категории