Hacking serio básico: Introducción a los "shellcodes" (I)
Cómo programar una shell code para utilizar en tus xploits (Parte 1)
Introducción
Cuando se produce un overflow en un programa, podemos sobreescribir EIP “Instruction Pointer” (entre otras cosas). Si sobreescribimos este registro con una dirección que contiene código propio, se ejecutará. Esto es, podemos ejecutar código con los permisos que tiene el programa ejecutado. ¿Qué pasa si el programa tiene el bit de suid activado? Lo dejo a vuestra imaginación.
Encontrar este tipo de xploits es muy difícil hoy en dia, puesto que hay muchas herramientas de análisis de código que evitan que se produzcan tremendos errores de seguridad. Aun así, podemos encontrar o programar algo a medida simplemente para aumentar conocimientos, que es en lo que consiste la curiosidad (lo otro se llama ‘prensa rosa’).
Nasm – Ensamblador tipo intel para x86
NASM es un ensamblador libre de tipo intel. Lo he elegido porque es con lo que yo estoy familiarizado.
Como siempre, los que tengan debian o similares:
Linux System Calls
Las llamadas a sistema en Linux se hacen con parámetros en los registros e invocando la interrupción 80h.
Un ejemplo sería:
Podéis consultar una tabla de syscalls aquí
Ejemplo clásico: ejecutar una shell (/bin/sh)
Para ejecutar una shell tenemos que tener en cuenta que debemos administrar un parámetro de cadena, esto es “/bin/sh” a la función syscall correspondiente. Pero, ¿cómo hacemos esto si no sabemos en qué parte de la memoria irá nuestra shellcode? Mmmm… difícil a primera vista.
Bien, existe un truco. Cuando se realiza un “call”, la cima de la pila contiene la siguiente dirección de memoria que va tras ella, así, al hacer “ret”, se podrá apuntar el PC (contador de programa) a esa dirección y seguir con el código del programa. Así que … con estos datos sabemos que la siguiente dirección de memoria tras un “call” podemos conseguirla de la pila con “pop
Ejemplo:
Al final de nuestra variable cadena debería haber un caracter nulo ‘\0’ , pero las shellcodes no se llevan muy bien ni con ellos, ni con los retornos de carro y algunas cosas más; así que tenemos que añadirlo nosotros.
Cuidado con operaciones que tengan 0’s. Hacer “mov eax, 0” hará que el binario compilado contenga caracteres ‘\0’.
Recordad que para poner algo a 0 basta con hacer un XOR consigo mismo.
Vale. Ahora, miramos en la tabla de syscalls y vemos que sys_execve es la 11.
La implementación en C es la siguiente:
Así que necesitamos tres punteros a los argumentos. El primer puntero ya sabemos cómo conseguirlo, el segundo, NULL, no queremos argumentos y en la opción de envp le metemos un NULL, que no nos hace falta para nada. Así la llamada a la syscall execve tendra a eax=11 (0xb), ebx = dirección donde empieza ‘/bin/sh’, ecx = NULL y edx = NULL.
Más o menos quedaría así:
Esto lo grabamos por ejemplo a un archivito llamador “exec.asm” y ejecutamos:
Ahora se ha generado un archivito llamado “exec”. Ese archivo binario es nuestra shellcode!!! Pero, ¿cómo la probamos? No se puede ejecutar sin más, pues no es un elf, ni tiene ningún formato de ejecutable, es código máquina puro y duro.
Probando la shellcode
Para probarla podemos usar el siguiente programita en C. Su uso es:
El programita que carga el fichero como función es:
Así que cuando ejecutamos el comando obtenemos:
…carga la shell sh!!! Funciona!!! (con ctrl-d podemos retornar a nuestra shell original).
Hemos hecho un binario en código máquina que ejecuta una shell en un linux x86 en tan sólo 31 bytes.
Ahora que tenemos esto, ¿cómo lo usamos en nuestro exploit? Vamos a verlo, con un ejemplo de uso en el lenguaje de sistemas C.
Cómo usar el archivo compilado en nuestro código en C
Una vez que tenemos nuestro archivo binario creado por nasm podéis usar el siguiente programita en C para convertir vuestra shell en una variable de C. Para compilarlo haremos:
Para usarlo:
Como salida nos encontramos:
Esta variable la podremos usar como shell code en nuestro exploit … pero eso ya es otra receta.
Esta receta es sólo una introducción y pretende ser guía para algún curioso que pare por aquí. No es ejemplo del mejor C, ni del mejor asm x86…