jueves, 26 de julio de 2012

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

Cómo se ejecuta un proceso:

Antes de empezar tengo que aclarar que voy a estar trabajando con una máquina de 32 bits y que por lo tanto las direcciones que aparecerán serán de 32 bits, pero toda la teoría es aplicable (al menos hasta donde yo sé) a máquinas de 64 bits... sólo que con direcciones más largas :P.

A la hora de jugar con exploits e intentar desarrollarlos, una de las principales cosas que uno debe saber es el entorno donde se ejecuta el proceso a explotar.

Durante la ejecución, un proceso no es más que una secuencia de bytes en memoria, pero esa memoria tiene ciertas características y orden.

Antes de explicar cómo se organiza la memoria durante la ejecución de un proceso introduzcamos algunos conceptos:

Sección: Un programa está dividido en secciones. Estas secciones puede que se carguen o no en memoria cuando el programa este en ejecución. Veamos un ejemplo de esto:

$ objdump -h /bin/bash

/bin/bash:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .interp       00000013  08048154  08048154  00000154  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  08048168  08048168  00000168  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  08048188  08048188  00000188  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     0000364c  080481ac  080481ac  000001ac  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       000084b0  0804b7f8  0804b7f8  000037f8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       000080d1  08053ca8  08053ca8  0000bca8  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  00001096  0805bd7a  0805bd7a  00013d7a  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 000000b0  0805ce10  0805ce10  00014e10  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rel.dyn      00000040  0805cec0  0805cec0  00014ec0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rel.plt      00000618  0805cf00  0805cf00  00014f00  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         00000030  0805d518  0805d518  00015518  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000c40  0805d548  0805d548  00015548  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text         0008e9cc  0805e190  0805e190  00016190  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE

 13 .fini         0000001c  080ecb5c  080ecb5c  000a4b5c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .rodata       00019ef8  080ecb80  080ecb80  000a4b80  2**5
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 15 .eh_frame_hdr 0000002c  08106a78  08106a78  000bea78  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame     0000009c  08106aa4  08106aa4  000beaa4  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .ctors        00000008  08107f04  08107f04  000bef04  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 18 .dtors        00000008  08107f0c  08107f0c  000bef0c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 19 .jcr          00000004  08107f14  08107f14  000bef14  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 20 .dynamic      000000d8  08107f18  08107f18  000bef18  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000004  08107ff0  08107ff0  000beff0  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got.plt      00000318  08107ff4  08107ff4  000beff4  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 23 .data         00004390  08108320  08108320  000bf320  2**5
                  CONTENTS, ALLOC, LOAD, DATA
 24 .bss          00004f0c  0810c6c0  0810c6c0  000c36b0  2**5
                  ALLOC
 25 .gnu_debuglink 0000000c  00000000  00000000  000c36b0  2**0
                  CONTENTS, READONLY

Estas son las secciones que contiene el binario de bash, como ya dijimos algunas se cargarán en el proceso en memoria y otras no. Pero las secciones aquí mostradas no son las únicas que va a tener el proceso. Cuando ejecutemos un programa "aparecen" secciones adicionales como la pila o el heap. Realmente no me gusta llamarlas secciones pero muchísima gente las llama de tal forma. Al final lo que hay que tener claro es que los datos (¡los bytes, vaya!) que hay en esas secciones se cargan en la memoria del proceso.

Región: Se le llama "región de memoria" o "región" a un trozo de la memoria del proceso contigua en direcciones y con los mismos permisos para todos sus bytes. A veces también se le llama "segmento". Veamos las regiones de un proceso ejecutando el bash anterior:
$ ps | egrep "bash"
 2406 pts/1    00:00:00 bash 

$ cat /proc/2406/maps
001d2000-00208000 r-xp 00000000 fc:03 382867     /lib/libncurses.so.5.7
00208000-0020a000 r--p 00035000 fc:03 382867     /lib/libncurses.so.5.7
0020a000-0020b000 rw-p 00037000 fc:03 382867     /lib/libncurses.so.5.7
0020b000-00362000 r-xp 00000000 fc:03 382855     /lib/libc-2.12.1.so
00362000-00364000 r--p 00157000 fc:03 382855     /lib/libc-2.12.1.so
00364000-00365000 rw-p 00159000 fc:03 382855     /lib/libc-2.12.1.so
00365000-00368000 rw-p 00000000 00:00 0
0058a000-00594000 r-xp 00000000 fc:03 383014     /lib/libnss_files-2.12.1.so
00594000-00595000 r--p 00009000 fc:03 383014     /lib/libnss_files-2.12.1.so
00595000-00596000 rw-p 0000a000 fc:03 383014     /lib/libnss_files-2.12.1.so
00b3f000-00b40000 r-xp 00000000 00:00 0          [vdso]
00d1f000-00d28000 r-xp 00000000 fc:03 383016     /lib/libnss_nis-2.12.1.so
00d28000-00d29000 r--p 00008000 fc:03 383016     /lib/libnss_nis-2.12.1.so
00d29000-00d2a000 rw-p 00009000 fc:03 383016     /lib/libnss_nis-2.12.1.so
00d9c000-00db8000 r-xp 00000000 fc:03 382771     /lib/ld-2.12.1.so
00db8000-00db9000 r--p 0001b000 fc:03 382771     /lib/ld-2.12.1.so
00db9000-00dba000 rw-p 0001c000 fc:03 382771     /lib/ld-2.12.1.so
00dd0000-00dd2000 r-xp 00000000 fc:03 383008     /lib/libdl-2.12.1.so
00dd2000-00dd3000 r--p 00001000 fc:03 383008     /lib/libdl-2.12.1.so
00dd3000-00dd4000 rw-p 00002000 fc:03 383008     /lib/libdl-2.12.1.so
00f29000-00f2f000 r-xp 00000000 fc:03 383012     /lib/libnss_compat-2.12.1.so
00f2f000-00f30000 r--p 00006000 fc:03 383012     /lib/libnss_compat-2.12.1.so
00f30000-00f31000 rw-p 00007000 fc:03 383012     /lib/libnss_compat-2.12.1.so
00f9a000-00fad000 r-xp 00000000 fc:03 383011     /lib/libnsl-2.12.1.so
00fad000-00fae000 r--p 00012000 fc:03 383011     /lib/libnsl-2.12.1.so
00fae000-00faf000 rw-p 00013000 fc:03 383011     /lib/libnsl-2.12.1.so
00faf000-00fb1000 rw-p 00000000 00:00 0
08048000-08107000 r-xp 00000000 fc:03 342053     /bin/bash
08107000-08108000 r--p 000be000 fc:03 342053     /bin/bash
08108000-0810d000 rw-p 000bf000 fc:03 342053     /bin/bash
0810d000-08112000 rw-p 00000000 00:00 0
089ef000-08c04000 rw-p 00000000 00:00 0          [heap]
b741c000-b7443000 r--p 00000000 fc:03 18415      /usr/share/locale-langpack/es/LC_MESSAGES/bash.mo
b7443000-b744a000 r--s 00000000 fc:03 408043     /usr/lib/gconv/gconv-modules.cache
b744a000-b744b000 r--p 004e7000 fc:03 408807     /usr/lib/locale/locale-archive
b744b000-b756a000 r--p 002a3000 fc:03 408807     /usr/lib/locale/locale-archive
b756a000-b776a000 r--p 00000000 fc:03 408807     /usr/lib/locale/locale-archive
b776a000-b776c000 rw-p 00000000 00:00 0
b777e000-b7780000 rw-p 00000000 00:00 0
bf8b1000-bf8d2000 rw-p 00000000 00:00 0          [stack]

Vemos que tiene un montón de regiones y cuyos nombres no tienen nada que ver con las secciones del programa. Aún así las secciones del programa estarán en algunas de estas regiones. Fijémonos por ejemplo en las direcciones de comienzo y fin de la fila en negrita, comienza en 0x08048000, que corresponde al comienzo de la sección .text con dirección 0x0805e190 y offset 0x00016190 (0x0805e190 - 0x00016190 = 0x08048000). Pero en esa región tenemos más de una sección del programa.

Sé que esto así de golpe es bastante duro de digerir y que parece que no vamos a llegar a ningún sitio, pero más adelante haremos cosas que requerirán saber un poco cómo funciona esto. Además así de paso usamos algunas de las herramientas de las GNU binutils que siempre viene bien.

Siendo un poco más genéricos y simplificando todo esto tenemos que tener claro que un proceso tiene varias de las secciones que trae el programa original y algunas más (heap, stack por ejemplo). En general siempre se nos suele mostrar algo parecido a lo siguiente:


Aunque hay que tener cuidado ya que este esquema puede inducir a error. Como ya hemos dicho, la memoria de un proceso no se divide en secciones, sino en regiones y pueden englobar más de una sección.

Como vemos en la imagen anterior, existen unas secciones llamadas stack y heap que seguro hemos oido hablar de ellas anteriormente, ya que tienen mucha relevancia.

En la pila (stack) suelen ir las variables locales a las funciones además de cierta información de control, en otra entrada hablaremos más profundamente de lo que hay en la pila y con ejemplos. Por ahora baste decir que la pila suele estar en direcciones bastante altas de memoria y que crece hacia direcciones más bajas, esto siempre cuesta verlo al principio pero con la experiencia lo iremos interiorizando.

El heap es otra sección con bastante importancia, allí residen los datos de toda la memoria que el proceso va pidiendo dinámicamente (con malloc() hablando rápido y mal). A diferencia de la pila ésta crece hacia direcciones más grandes de memoria. Esto significa que si un proceso empieza a pedir mucha memoria y a enlazar llamadas a funciones una tras otras sin retornar habrá un punto en el que dichas secciones "chocarán".

Y con esto ya es suficiente teoría de ejecución de procesos por ahora.

Saludos.

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.