{"id":14284,"url":"\/distributions\/14284\/click?bit=1&hash=82a231c769d1e10ea56c30ae286f090fbb4a445600cfa9e05037db7a74b1dda9","title":"\u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0430 \u0442\u0430\u043d\u0446\u044b \u0441 \u0441\u043e\u0431\u0430\u043a\u0430\u043c\u0438","buttonText":"","imageUuid":""}

ROP-цепочки и гаджеты: учимся разрабатывать эксплойты

Предлагаю вникнуть в бинарные уязвимости и эксплойтостроение на примере просто ROP (return oriented programming) – одного из методов эксплуатации уязвимостей в ПО. Это достаточно мощный инструмент, с помощью которого можно обойти различные методы защиты. Конкретно мы будем разбираться с гаджетами и ROP-цепочками.

Что такое ROP-гаджеты

По умолчанию гаджет завершается RET (это инструкция возврата), которая расположена в ОЗУ в коде программы или распределяемой библиотеки. В процессе кибератаки мошенники делают так, чтобы все гаджеты выполнялись через инструкции возврата, а для этого они определенным образом формируют их цепочки (последовательности) – их и называют ROP-цепочками или ROP-chain.

​Вот так могут выглядеть гаджеты

Допустим, мы возьмем необходимые нам гаджеты в цепочку. Ее отправим в стек и добьемся выполнения любого кода. Код может быть вообще любым, но нам нужно вызвать шелл (оболочку). Под linux это делается с помощью execve() (аналог system()) – его и будем применять для создания системного вызова.

Linux System Calls

Системные вызовы помогают получить определенные системные функции. Полный их список с присвоенными номерами вызываем следующим образом:

Зачем нужен номер? Зная его, мы сможем вызвать конкретную системную функцию:

unistd.h представляет собой файл заголовка, через который мы получаем доступ к API нашей ОС. Его содержимое можно увидеть через простой редактор nano.

Системные вызовы для x86 и x64​

Что содержится в unistd_32.h?

В unistd_32.h ровно 336 системных вызова​

Видим, что перед нашим системным вызовом стоит 11.

Идем дальше – прототип системного вызова execve().

​Прототип системного вызова execve()

Filename – указатель на строку (у нас это путь к двоичному файлу ptr для "/bin/sh").

ARGV[] – перечень аргументов программы (отсутствуют, потому ставим 0).

Envp[] – аргумент параметров среды (снова 0).

Получаем:

Ассемблер

Разберемся, как у него обстоят дела с командами и регистрами.

Основные регистры:

EIP – служебный регистр, указывает на адрес текущей исполняемой инструкции;

ESP – хранит указатель на вершину стека;

EBP – указатель на фрейм, применяется для адресации данных в стек;

EAX – используется как универсальное хранилище при накоплении данных;

EBX – хранит адреса в памяти (регистр общего назначения, хранит любые данные в оперативной памяти);

ECX – применяется для организации циклов. Перед циклом в него заносят необходимое количество итераций, каждая команда loop уменьшает значение этого регистра на единицу. При достижении нуля цикл завершается;

EDX – аналог EAX, тоже универсальный аккумулятор значений, часто используется для умножения и деления (DIV, SUB).

Последние четыре регистра используются для хранения определенных значений.

Есть еще 6 с аргументами системных вызовов: EBX, ECX, EDX, ESI, EDI и EBP. Если аргументов 7 и больше, ячейка памяти первого сохраняется в EBX.

Снова возвращаемся к execve(). Для ее вызова нужно придумать, где хранить аргументы. Используем для этого те самые шесть регистров.

Для самого системного вызова нам понадобится int 0x80 (80h), где int – прерывание, а 0х80 – его номер.

Обработкой прерываний в linux занимается его ядро, оно же отвечает за выполнение системных вызовов другими программами. Ядро получает сведения о том, какой вызов хочет сделать определенная программа после того, как проанализирует содержимое EAX (туда записывается номер системного вызова).

Чтобы сделать системный вызов:

  • перемещаем его номер в EAX (у нас 11);
  • сохраняем аргументы в регистрах EBX, ECX и EDX;
  • делаем int 0x80 -> EAX (11) => run execve().

Получаем:

Дальше находим гаджеты, которые соответствуют нашим критериям, и собираем ROP-цепочку.

Формируем ROP-chain

Искать подходящие гаджеты удобно с помощью специальных инструментов. Их много, причем одни просто частично автоматизируют работу, а другие умеют сами формировать ROP-цепочки. Я предлагаю использовать в работе ROPgadget или онлайн-сервис ROPShell. Для нашего примера я выбираю второй вариант.

У нас есть два способа: найти гаджеты в программе stack7 или в библиотеке libc. И снова второй вариант лучше, так как чем «тяжелее» программа, тем больше в ней обнаружится гаджетов, хотя не факт, что нужные там будут.

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

Где находится библиотека?

Пока видим только ярлык, но сама библиотека располагается там же и называется libc-2.11.2.so. Скачиваем ее и загружаем в онлайн-сервис. Для скачивания рекомендую WinSCP – свободно распространяемый графический клиент SFTP под Windows.

Запускаем клиент WinSCP​
RSA-ключ​

Ищем в папке libc-2.11.2.so и загружаем его.

Со своего компьютера загружаем скачанный файл в онлайн-сервис ROPShell. Как это выглядит – смотрим здесь.

Теперь возьмем некоторые фрагменты из ранее написанного эксплойта.

Нам нужна часть кода.

По результатам анализа библиотеки получаем такую картину:

Вот что мы получили после анализа библиотеки​

Чтобы сформировать ROP-последовательность, найдем здесь три инструкции:

pop [register] – переносит данные с вершины стека в [register];

xor eax, eax; – записывает в EAX ноль;

inc eax; – добавляет к содержимому EAX на единицу.

Теперь переходим к конструированию ROP-эксплойта.

1. Переполняем буфер на 80 байтов. offset eip.

2. Переходим на RET address.

3. Обнуляем содержимое регистров ECХ и EDX.

Ищем гаджеты с нужными нам инструкциями. Пишем в ROPShell:

Подставляем четырехбайтное значение '\x00\x00\x00\x00', обнуляем содержимое регистра ECX.

И еще:

pop edx; ret

'\x00\x00\x00\x00' в регистр EDX записываем 0.

В списке есть регистр с одной из нужных нам инструкций​

Одного гаджета с обеими инструкциями "pop ecx; ret" & "pop edx; ret" мы не нашли, продолжаем поиски.

Значения подставляем такие, которые соответствуют выбранным гаджетам.

4. В регистр EAX записываем 0.

Пишем xor, eax в ropshell.

​Ищем xor eax

5. Снова нужен EAX, но сейчас записываем туда 11.

​Ищем inc eax

Сейчас в EAX записано значение 0, с помощью инкрементации увеличиваем его до 11.

6. Смотрим, что с EBX.

​Обращаемся к EBX

7. Добавляем 'bin/sh'.

8. Вызываем int 0x80.

​Прерывание int 0x80

9. Суммируем и печатаем.

Вместо адреса system() подставляем адрес сишной библиотеки. Нам нужен адрес, по которому мы искали гаджеты. Используем "info proc map" – эта команда позволит увидеть адресное пространство и понять, где libc загружен в память.

​Находим адрес libc

Адрес выяснили – libc загружен по адресу 0xb7e97000.

И теперь код эксплойта полностью.

Как проверить, что все верно?

​Эксперимент завершился успешно

Ура, root получили! Наш эксплойт целиком состоит из цепочки гаджетов ROP-chain. Об их классификации можно узнать здесь, а про ROP – здесь.

Больше интересных тем по кибербезопасности вы найдете на нашем форуме https://codeby.net. Всех желающих приглашаем на обучение – преподаватели нашей школы разработали курс по анонимности и безопасности в интернете. Узнать подробности и записаться: вам сюда.

0
Комментарии
-3 комментариев
Раскрывать всегда