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.