Más de una ocasión se que habrás escuchado “una ejecución de código remoto”, pero ¿Qué es esto? ¿Cómo se hace? Bien aquí vamos a ver cómo podemos hacer una ejecución de código arbitrario en una aplicación vulnerable.
[— — — — — — — — — — BUFFER OVERFLOW — — — — — — — — — — — -]
Los desbordamientos de búfer han existido desde los primeros días de las computadoras aún al día de hoy existen. C es un lenguaje de alto nivel, pero se asume que el programador es responsable de la integridad de los datos, pues si los compiladores tuvieran esta tarea los binarios resultantes serían significativamente más lentos. Si bien la simplicidad de C aumenta el control del programador y la eficiencia de los programas, también puede resultar en programas que son vulnerables a desbordamientos de búfer y pérdidas de memoria si el programador no tiene cuidado. Esto significa que cada vez que se asigna memoria en una variable, no hay seguridad incorporada para asegurar que el contenido introducido a la variable sea el que se reservó para esa variable. Por ejemplo si a una variable se le han reservado 20 bytes de espacio de manera muy ventajosa se le podría introducir 30 bytes de información y esto está permitido aunque lo más probable es que el programa se bloquee.
Esto se conoce como desbordamiento de búfer ya que los bytes adicionales se desbordaran (como agua en un vaso lleno) y se derramarán de la memoria asignada, sobrescribiendo lo que suceda a continuación.
Fallos de segmentación(DoS)
Un fallo de segmentación, también conocido como violacion de segmento, se produce cuando una aplicación intenta acceder a una dirección de memoria que no ha sido asignada dentro del espacio de direcciones reservadas al proceso. Esto provoca una denegación de servicio y que la aplicación deje de funcionar.
Veamos el siguiente ejemplo:
Este es una programa típico escrito en C, veamos de manera visual lo que hablábamos antes.
Antes que nada y para fines de probar una explotación exitosa vamos a compilar el programa de forma diferente para quitar algunas protecciones y seguidamente al programa le vamos a dar privilegios activando el U+S (Notar que yo ya estaba como root) y hay que desactivar la protección ASLR:
Esto se hace con fines de que el usuario que ejecute la aplicación obtendrá de forma temporal y hasta que finalice el programa obtenga los permisos del administrador.
Como se puede ver en el programa al ejecutarse la salida es normal pues “Dalton” está muy dentro de los límites de lo reservado.
Pero ahora veamos qué pasa cuando introducimos más bytes de los reservados.
Antes que nada tengo que dar explicación que perl nos puede ayudar mucho con bash para no tener que escribir a mano las 60 A’s usamos la sintaxis $(perl -e ‘print “A”x70’ ).
Bien como se puede apreciar en la salida del programa hay una violación de segmento, esto sedebe a lo ya dicho, se a desborado la memoria a sonas no mapeadas (asignadas).
¿Pero a donde se desbordó?
Para contestar esta pregunta debemos estudiar el flujo del programa cómo lo procesa valga la redundancia el procesador.
0 Empieza main()
1 Instrucción X
2 Instrucción X
3 Llamada a Func()
4 Instrucción X
5 Termina el programa
6 Empieza Func()
7 Instrucción Y
8 Instrucción Y
9 Volver a main()
Sin embargo este no es el orden de ejecución el orden es el siguiente:
0 -> 1 -> 2 -> 3 -> 6 -> 7 -> 8 -> 9 -> 4 -> 5
Lo que se dice de ”llamar a Func()” en realidad se traduce a call y lo que escribiremos como “volver a main()” se traduce a ret, esto en instrucciones de bajo nivel (ensamblador). Cuando call fue ejecutado el contenido del registro IP que en ese momento era 4 fue copiado a la memoria y de esta forma IP puede obtener cualquier otro valor ya que una vez alcanzada la instrucción ret ésta sabrá cómo recuperar su valor original.
Pues bien el desbordamiento se produce en el registro IP y para este punto uno ya se da idea de que si modificamos IP, tendríamos capacidad de redirigir el flujo del programa, veamos:
Cuando introducimos las 70´s A’s estas al sobrepasar el buffer reservado a alcanzado al registro IP o ret y este se bloqueará, para abordar esto necesitamos verlo de manera aun más visual, vamos a usar una utilidad de linux llamada gdb, pero en este caso usaremos gdb-peda:
usando el comando gdb -q ./prog podemos ejecutar el programa bajo gdb, con r o run vamos a introducir solo unos cuantos bytes y hasta aquí todo bien, pero veamos que pasa cuando introducimos más de los quedeberian.
esto lo hacemos repitiendo el comando r $(perl -e ‘print “A”x70’) si observamos en los registros vemos que tanto EBP y EIP tienen en su contenido 0x41414141 osea las A’s en formato hexadecimal, ahora ya que sabemos que EIP puede ser alterado debemos saber cuántos byttes necesitamos antes de sobre escribir EIP para poder controlarlo, aquí la aplicación se a detenido por que 0x41414141 no es una dirección válida.
Con patterns arg 100 vamos a generar 100 bytes aleatorios para pasarlos como argumento al programa sin necesidad de estar copiando y pegando.
El resultado el mismo:
pero ahora usando pattern search veremos cuántos bytes necesitamos antes de que se desborde la memoria, en Registers contain pattern buffer podemos ver que el registro EIP solo necesita 44 bytes antes de desbordarse.
Muy bien ahora usemos y verifiquemos esto a nuestro favor, usando casi el mismo comando que antes pero agregando sólo 4 bytes más :
gdb-peda$ r $(perl -e ‘print “A”x44 . “B”x4’ )
Como hemos visto se ha modificado el registro EIP casi a nuestro antojo, pues ahora nos muestra el valor de B en hexadecimal, pero vayamos más allá.
Ahora introducimos algo más en el comando:
Observando la pila podemos ver que nuestro tercer argumento se repite en gran medida, si observamos los registros lo veremos mejor.
Los últimos 100 registros nos muestran que a nuestro tercer argumento, le restamos 4 podremos ver a nuestro registro EIP.
En este punto ya casi, casi podemos explotar el programa.
Pero en este punto hay que entender varias cosas, después de A el programa se desbordara y leerá a B, aquí la segunda posición la podemos reemplazar por alguna dirección valida que el procesador lea y consecuentemente se desplace a obtener a una shell ¿Como podría suceder esto? Bueno a los shellcodes no los usamos solos generalmente y de mucha ayuda podremos añadir una secuencia de bytes conocida como colchón estas son NOPS estas instrucciones de ensamblador que no hacen nada, se ejecutan y pasan de largo para procesadores intel la más conocida es “\x90”, conociendo esto si introducimos un colchón de nops los suficientemente largo, justo antes de nuestro shellcode y logramos que la dirección que sobrescribe EIP caiga dentro de este relleno, éstos se irán ejecutando hasta alcanzar nuestro shellcode, cuantas más instrucciones NOP introduzcas más direcciones serán válidas .
¿Pero qué es un shellcode?
Ya se, ya se, te estas preguntando ¿Que ch*os es un shellcode? Bueno aunque en esta entrega no te enseñaré a construir uno si vale la pena saber que es:
Un shellcode no es más que una cadena de códigos de operación hexadecimales, extraídos a partir de instrucciones típicas de lenguaje ensamblador. Si esta cadena es introducida dentro de una zona específica de la memoria, y podemos de algún modo redireccionar el flujo del programa a esa zona, entonces tendremos la capacidad de ejecutar dicho shellcode.
por el momento como en esta entrega no se estudiará a profundidad los shellcodes vamos a usar este, que nos proporcionará una shell:
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80
Extraídas de el siguiente código de ensamblador:
08048060 <_start>:
8048060: 31 c0 xor %eax,%eax
8048062: 50 push %eax
8048063: 68 2f 2f 73 68 push $0x68732f2f
8048068: 68 2f 62 69 6e push $0x6e69622f
804806d: 89 e3 mov %esp,%ebx
804806f: 89 c1 mov %eax,%ecx
8048071: 89 c2 mov %eax,%edx
8048073: b0 0b mov $0xb,%al
8048075: cd 80 int $0x80
8048077: 31 c0 xor %eax,%eax
8048079: 40 inc %eax
804807a: cd 80 int $0x80
Teniendo todo lo descrito hasta ahora podemos modificar nuestro comando de la siguiente forma:
Nuestra basura “A”, nuestro colcon de NOPS y por último nuestro shellcode, ahora si ya estamos por explotar nuestro programa.
Ejecutando el comando podemos ver que la pila en efecto se lleno con nuestro colchón.
Y así se ve el registro ESP
Ahora como estamos en una distribución de 32bits, esto es un formato litte-endian es decir el byte menos significativo se escribe primero, es decir, lo escribimos alreves. XD
Tan solo bastaría usar una de las direcciones de nuestro colchón (una que esté más o menos en medio ) para poder construir nuestro exploit.
Fantástico lo tenemos todo, procedemos a ejecutar, pero hagámoslo fuera de gdb, basta escribir q o quit para salir.
Y…
Pummmmmmm
Como podemos observar hemos ejecutado una shell, con los permisos del administrador.
Hemos comprometido la seguridad del sistema y ahora tenemos un dominio completo, amos y señor del sistema, para realizar cualquier clase de acción, agregar cuentas nuevas, poner un puerto a la escucha y esperar conexiones externas, usar el sistema para atacar otras máquinas en la red etc, el límite es tu imaginación.
Y bien se a llegado al final, espero que alla sido de tu agrado este pequeño articulo de sobre la explotación, pues el exploiting es la base de todas las técnicas de penetración, saber explotar te va a diferenciar entre un scrpt-kiddes y un hacker de verdad, por que recuerda las herramientas no hacen al hacker.
En otras entregas vamos a ver otros métodos de explotación, se despide tu buen vecino:
[ — — — — — — — — — — — — -dalt0x6.asm — — — — — — — — — — — — — ]