martes, 24 de junio de 2014

Programación dirigida por retorno. (II) - Un ejemplo práctico

Finalicé el post anterior con el compromiso de continuar con un ejemplo práctico de Programación dirigida por retorno.

Pongámonos en un caso extremadamente simple, un programa lee un fichero que contiene dos líneas, la primera es un identificador de sesión (numérico) y la siguiente es una contraseña válida para ese identificador de sesión.

Hay una matriz en la pila que se encarga de recibir ambos valores y coloca en el elemento apuntado por el identificador de sesión el valor la contraseña asociada.

El resto es demasiado sencillo se pregunta si el valor introducido es igual a la entrada que hay en una tabla de contraseñas. Si es igual el procedimiento sigue, si no lo es da un mensaje de error y finaliza.


{
FILE *fd;
int in1,in2;
int arr[20];
char var[20];

if (argc !=2){
printf(mensaje0,*argv);
return -1;
}
fd = fopen(argv[1],"r");
if(fd == NULL)
{
fprintf(stderr,mensaje1);
return -2;
}
memset(var,7,sizeof(var));
memset(arr,6,20*sizeof(int));
while(fgets(var,20,fd))
{
in1 = atoll(var);
fgets(var,20,fd);
in2 = atoll(var);
/* fill array */
arr[in1]=in2;
//printf("%d - %d\n", arr[in1], tabla[in1]);
if (arr[in1] != tabla[in1])
{
printf("La coordenada no es correcta\n");
return ;
}
printf("La coordenada es correcta\n");
printf(mensaje4);
return;
}
}



Esto está así por simplicidad. En un sistema más complejo la tabla de valores estaría fuera del programa y al menos procesada mediante un algoritmo de hashing. Pero todo este tema nos da un poco igual porque lo que a nosotros nos interesa simplemente dirigir el retorno.





He tomado algunas libertades para hacer más sencillo y comprensible el programa el programa:

Unos campos de memoria de relleno para simplificar los cálculos:



Unas inicializaciones de memoria raras. Lo correcto habría sido inicializarlas a 0x00 pero las he inicializado con los valores 6 y 7 para que nos sea más fácil de identificar en la pila.



Arrancamos Ollydbg y cargamos el programa compilado. Con la información que nos suministra este depurador podemos localizar en que punto está la verificación de la clave correcta: 

if (arr[in1] != tabla[in1])




Y colocamos un breakpoint en este punto.

Como los datos los tenemos en un fichero lo primero que tenemos que hacer es indicar a Ollydbg cuales el argumento de entrada. Lo realizamos desde el menú Debug -> Arguments

Ponemos el nombre del fichero que queramos  que previamente habremos creado con los siguientes valores:

2
93

Ejecutamos hasta que se pare y comprobamos los valores de los registros EAX y EDX. Al ser iguales el programa continúa en la direccion 0x004017F2 (la contraseña es correcta).



Si cargamos el fichero con otros valores:

2
95

Y ejecutamos, cuando se pare el programa podemos ver que los  valores de los registros EAX y EDX sin distintos. El programa da el mensaje de aviso y llega a la instrucción return.

El objetivo, que veremos en la próxima entrada es alterar el contenido de la pila para que nos lleve a la instrucción 0x004017F2 y poder continuar le ejecución del programa como si la contraseña hubiera sido correcta.







miércoles, 18 de junio de 2014

Programación dirigida por retorno. (I) - La teoría

Antes de entrar en lo que es la Programación dirigida por retorno (en adelante la nombraré por sus siglas en inglés: Return Oriented Programming) vamos a refrescar un poco lo que es el el desbordamiento de buffer y su posible utilización para ejecutar código arbitrario.

En anteriores post hemos entrado en este tema a fondo  pero vamos a recordar un poco para quienes no sigan este blog en que consiste.


Buffer Overflow y sus contramedidas.


Consiste alterar mediante un bloque datos la información de la pila, introduciendo código y modificando una dirección de retorno para redirigirla a ese código introducido.

Los compiladores, desde hace tiempo, usan diversas técnicas para  luchar contra estos ataques. Sin entrar en detalles estas técnicas se suele basar en cargar en la pila encima de la dirección de retorno un valor aleatorio y también colocarlo en el heap de memoria. Antes de hacer el return se comprueba que ambos valores coinciden. Para más información aconsejo esta lectura.

El popular compilador gcc tiene también su opción para proteger el stack con la opción -fstack-protector.

Otra solución  ha sido la implantanción en los procesadores de un mecanismo que impidiera la ejecución de código ubicado en la pila. Esta solucion tiene varios nombres XD, NX según el fabricante de ordenadores y a veces se habla indistintamente de esta capacidad hardware con que el sistema operativo la use y hablamos de Data Execution Prevention que es una capacidad software del sistemo operativo que incluso puede simularse en procesadores sin NX.

El conocer un poco más estos mecanismos da para mucho y en este artículo, en lo que nos sirve nos hemos de quedar con la idea de que existe una proteccion que impide la ejecución de código desde la pila

Bien DEP, impide que se pueda ejecutar un código que previamente he metido de forma aviesa en la pila. Pero DEP no impide que coloque lo que quiera en la pila y por lo tanto no va a impedir que sobreescriba la dirección de retorno correcta por una de mi interés.
Lo único que tengo que hacer es que ese código que quiero de que ejecute no esté en la pila. Podemos hacer entonces dos cosas: llamar a un función que nos interese del sistema, por ejemplo construir una llamada al sistema  o bien podemos hacer que el retorno fuera a otro punto del programa comprometido con lo que estamos alterando su comportamiento.

Hasta aquí la teoría, en el siguiente post veremos mediante un sencillo ejemplo como simplemente alterando al dirección de retorno de una función podríamos comprometer sofisticados sistemas de verificación.





martes, 27 de mayo de 2014

Debugging (I) Llamadas al sistema (System calls) y III

En la arquitectura x86 las llamadas al sistema se realiza mediante la instrucción SYSENTER. (aunque no siempre ha sido así. Hasta los Pentium II era mediante la instruccion INT 2E)

Esta sustitución ha sido para mejorar el rendimiento de las llamadas al sistema porque eliminan cierta sobrecarga en la gestión de interrupciones que, en el caso de la interrupción 2E, era superflua.

Tenemos una forma ya fácil de localizar la llamada al sistema. Solo tendremos que situarnos con ollydbg en el módulo ntdll.dll y localizar esa instrucción.

Pero antes vamos a seguir ampliando nuestros conocimientos. La última función que encontramos dentro del modo usuario (anillo 3) es KiFastSystemCall. Localicemos dentro el módulo ntdll.dll el código.

En la ventana de ejecución de Ollydbg solo tendremos que seleccionar Search for --> Name in all modules. Y aquí la vemos. Sólo dos líneas, tres sin incluimos la instrucción RET.

KiFastSystemCall                   8BD4            MOV EDX,ESP
7C90E512                              0F34            SYSENTER
KiFastSystemCallRet                C3              RET

Y a continuación, debajo vemos:

KiIntSystemCall                    8D5424 08       LEA EDX,DWORD PTR [ESP+8]
7C90E524                                  CD 2E        INT 2E
7C90E526                                        C3        RET

Que son las formas antigua y modernas de llamar la sistema. Lo que nos que esta versión sistema también puede puede funcionar en procesadores más antiguos.
Los nombres de cada una de las funciones también nos dan una pista: llamada al sistema y llamada rápida al sistema.

En la instrucción SYSENTER colocaremos un breakpoint y lo dejaremos deshabilitado porque nos interesa que se pare en le función fopen que es la que estamos siguiendo. Ponemos otro breakpoint en la llamada a función fopen

Damos Ejecutar (F9). Cuando el programa se pare en la llamada a fopen iremos a la ventana de breakpoint y habilitaremos la parada en la instrucción SYSENTER y volvemos a dar F9. Como esperábamos se nos para en la instrucción SYSENTER.

Observemos el registro EAX que contiene el valor 0x54. Esto valor es el número que identifica a la llamada del sistema que va a abrir el fichero.

Ahora a ejecutar hasta el código de usuario (Alf+ F9) para ver si hay más llamadas al sistema. Nos volvemos a parar nuevamente. Esta vez el registro EAX tiene el valor 0x25. Seguimos con Alt+F9, se nos para una vez más, esta vez tenemos en el registro EAX el valor 0xB3.

A la cuarte vez que damos Alt+F9 el programa se para ya en el código de usuario. La conclusión es que la función fopen, no tiene una llamada al  sistema. !Tiene cuatro¡.

Podemos realizar un proceso similar con la función toupper para tener la certeza de que esta función no tiene ninguna llamada al sistema.

Conclusión. 

Ambas funciones, que nos suministra el juego de funciones de las bibliotecas de C y que pueden parecer iguales tienen en su interior un uso de la arquitectura del procesador muy diferente. Y, si bien es bueno, aislar de ciertas complejidades a la codificación también debemos ser conscientes cuando programamos que no todas las cosas son por dentro iguales.... aunque por fuera no seamos capaces de notarlo.

<-- Previa entrada