jueves, 19 de julio de 2012

Minicurso de exploiting (parte 1): Introducción a los procesos

Llevo bastante tiempo desconectado y últimamente he tenido ganas de empezar otra vez con el exploiting. Para repasar lo que sé, he decidido intentar escribir una serie de entradas a modo de curso de exploiting. No pretende ser nada profesional, pero sí pretende que pueda ser seguido desde cero por alguien que no sepa exploiting pero sí programar y más o menos cómo funcionan los ordenadores. A ver si hago algo medianamente decente y que ayude a alguien además de a mi.

Lo primero a la hora de entrar en este mundo creo que debe ser saber qué es el exploiting (y el reversing). Doy mi visión:
  • Reversing: El conjunto de técnicas y metodologías empleadas para averiguar qué hace un programa/proceso de ordenador y cómo lo hace a partir solamente del binario, sin contar con el código fuente, y quizás buscarle alguna vulnerabilidad.
  • Exploiting: El conjunto de técnicas y metodologías empleadas para sacar provecho de una vulnerabilidad en un programa/proceso de ordenador, generalmente consiguiendo que dicho programa se comporte de manera distinta a como sus diseñadores y programadores esperan.
Como se puede adivinar a partir de estas definiciones, generalmente el exploiting lleva asociada una parte de reversing también, aunque no siempre es necesario.

Se que esto es bastante teórico y que este blog es más bien práctico, pero también hay que tener en cuenta que cuanto más se domina la teoría más fácil resulta la práctica, y todo curso no puede prescindir de la teoría básica (tampoco creo que nadie se muera por ello).

Pasemos a explicar un poco cómo funciona un proceso.

Introducción a la ejecución de procesos:  

Cómo arranca un proceso:
Como sabemos los programas tienen un único punto de entrada, una primera instrucción a ejecutar. Cuando estamos aprendiendo a programar con C por ejemplo, siempre se nos dice que el programa empieza en la función main(). En verdad esto no es del todo correcto, en main() comienza el código escrito por nosotros y será una función a la que se pasará después de ejecutar ciertas rutinas. Un programa no es el código que nosotros escribimos, es eso y más. Para poder ver todo el programa tenemos que mirar el binario del mismo, no el código fuente. Supongamos por ejemplo el típico hola mundo:


#include <stdio.h>

int main(int argc, char *argv[]) {
   printf("Hola mundo\n");
   return 0;
}



No hay misterio. Simplemente escribe "Hola mundo" y termina. Sin embargo ya hemos dicho que main no es lo primero que se ejecuta, vamos a demostrarlo. Al compilar este programa (en un GNU/Linux corriente y moliente) obtenemos un binario en formato ELF. Usando el programa readelf podremos ver sus tripas, y entre los datos que ahí se reflejan está la dirección de la primera instrucción del programa, la de verdad:

$ readelf -h hola_mundo
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048300
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4400 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         8
  Size of section headers:           40 (bytes)
  Number of section headers:         29
  Section header string table index: 26

Vemos que la primera instrucción ejecutada se encuentra en la dirección 0x8048300, ¿será esa la dirección de main?. Probemos a buscarla:

$ objdump -d hola_mundo | egrep "<main>"
080483b4 <main>:

Y aunque cercana, vemos claramente que no es la misma dirección, ¿y entonces a que función pertenece?:

$ objdump -d hola_mundo | egrep "8048300"
08048300 <_start>:
 8048300:    31 ed                    xor    %ebp,%ebp

Pues ahí tenemos la respuesta, en este caso pertenece a una función llamada _start. El compilador, además del código de nuestras funciones, mete también otras rutinas que se ejecutan tanto antes como después de las nuestras, en general estas rutinas dejan el estado del programa en lo que nuestro código espera que esté. Con esto ya hemos aprendido un poco más sobre los programas y los procesos y hemos desmitificado ese "el main() es lo primero que se ejecuta". Y esto además tiene moraleja que quiero dejar muy clara, nunca te creas del todo lo que te cuentan, pruébalo tu si puedes y si no por lo menos se consciente de que no lo has probado. Asegurar cosas que no sabes al 100% no es bueno. Ni siquiera lo que pongo aquí deberías creertelo.

Aunque el caso más común es que la función _start sea la primera que se ejecute, que no nos importe mucho todas esas rutinas previas a nuestro código, etc, no hay que olvidar que todo esto puede ser manipulado para fastidiar. Cambiar el punto de entrada, stripear el binario para que no contenga información de depuración ni símbolos, etc. Sólo son bytes en un fichero, así que su manipulación es posible.

Esto que cuento aquí no es que nos vaya a ser muy útil (al menos de momento), pero ya que estoy obligado a hablar de procesos quería comentarlo.

Saludos.

No hay comentarios:

Publicar un comentario