jueves, 3 de mayo de 2007

El origen del mal - 0x01

El logo del mal


Ya sabemos que un programa desarrollado de forma defectuosa puede facilitar que un usuario malicioso sea capaz de sobreescribir determinadas partes de la memoria. Para entender mejor la mecánica de este ataque conviene tener claras un par de cosas:




  1. Cómo se organiza un programa en memoria

  2. Cómo se ejecutan los programas en un ordenador


Asumimos que en todo momento hablamos de arquitectura x86, es decir los Intel y similares. Aunque las demás arquitecturas también son vulnerables.



[día 1 : Como adueñarnos de un programa]


1.- Organización de un programa en memoria. Cuando se ejecuta un programa este pasa de un fichero ejecutable a cargarse en memoria. En la memoria el programa toma esta forma:


Un programa en memoria

La parte que nos interesa es la de la Pila. Cuando el programa hace uso de funciones el área de memoria que se utiliza para gestiona sus direcciones es la pila. Es una zona de memoria de la que dispone el programa y cuyo tamaño va variando dependiendo de lo que haga el propio programa. Está acotado por dos registros del sistema: el EBP o Base Pointer que indica el principio de la pila y el ESP o Stack Pointer el cual marca el tope.


2.- Cómo se ejecutan los programas en un ordenador. El que realiza las operaciones en un ordenador es el procesador se llame pentium, sempron, celedon o cetrino. Como todo el mundo sabe el procesador no entiende las palabras; entonces, ¿solo entiende el 0 y el 1?

No exactamente. Los procesadores conocen un conjunto de instrucciones que tienen un número asignado, y ejecutando millones de esas instrucciones simples pueden hacer cualquier cosa.


Un programa ejecutable en definitiva es una ristra de instrucciones que el procesador comprende perfectamente. Cuando el programa se ejecuta, se carga en memoria y el procesador va leyendo una instrucción tras otra y la ejecuta. El orden de ejecución se lo indica un registro del sistema el EIP o Instruction Pointer que indica la posición de memoria en la que se encuentra la instrucción que el procesador debe ejecutar, tal y como se puede ver en este gráfico:
Un programa en ejecucion
Pues bien. Resulta que cuando el programa está en plena ejecución, si se llama a una función ocurre lo siguiente: se interrumpen el orden de instrucciones del programa principal, guarda el valor de EIP en la pila y pasan a ejecutarse las de la función. Cuando termine la función, para que el procesador sepa con qué instrucción debe seguir recupera el valor del registro EIP que había guardado y sigue con el programa. Este es el aspecto de la pila cuando se carga una función:
La pila en caso de ejecutarse una funcion

Pongamos como ejemplo el siguiente código y la funcion prueba, que recibe dos parámetros (x, y) y tiene un par de variables locales: cont y buffer1.


/**
* funcion.c
* Ejemplo de programa con una función
* por 64kbytes - http://alviento.cuatrovientos.org
*
* Para compilar desde linea de comandos:
* gcc -o funcion funcion.c
*
*/

#include

// Funcion simple con dos parámetros y un par de variables locales
void funcion (int x, int y)
{
int cont = 0;
char buffer1[12] = "aaaaaaaaaaaa";

for (cont=x;cont<y;cont++)
buffer1[cont] = 'a';

}
// Función principal, donde empieza todo
int main (int argc, char *argv[])
{
// Iniciamos una variable con un valor.
int a, b;

a = 0;

// Metemos el parámetro de la función
b = atoi(argv[1]);

funcion(a, b);

return 0;
}

En este caso este sería el aspecto de la pila:

La pila en este caso

Bien. Sabemos que es posible sobrescribir la memoria de un programa vulnerable, en caso de usar funciones podriamos atacar la zona de la pila.


La pila desbordada

Asímismo, al usar funciones sabemos que el valor del registro EIP se almacena en la pila. Para conseguir que un programa ejecute lo que nosotros queramos, tenemos que conseguir algo así:


La pila hackeada
Es decir hay que llenar las variables locales de código en binario y además machacar el contenido de EIP para que apunte a nuestro código. Cuando el procesador recupere el valor de EIP ejecutará las instrucciones que le hayamos dado y que se encontraran en el buffer.

La teoría necesaria para entender los buffer overflows está explicada. En la próxima ocasión y hablaremos de bytes y nos tendremos que entender en hexadecimal. Bye.

2 comentarios:

  1. No encuentro esos cacharrines con un punto para votar. De todas formas, yo creo que Pardo tiene más de 30 años.

    ResponderEliminar