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.


No hay comentarios:

Publicar un comentario