How To Exploit: Segmentacion de Memoria.. (Paper 4).

Iniciado por shkz, Febrero 05, 2015, 01:31:27 AM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Febrero 05, 2015, 01:31:27 AM Ultima modificación: Febrero 05, 2015, 05:49:36 PM por cnfs
Este es el cuarto paper que escribo sobre como encaminar los conocimientos de assembler, gdb, etc, hasta llegar y ver de lleno explotaciones, caza de bugs, etc. No se queden con este paper porque intento que NO sea un texto de programacion o assembler (es por eso que les pido que no lo muevan a programacion etc, porque careceria de ser un tutorial de programacion, esta orientado a familiarizarse con cosas necesarias para poder meternos de lleno en el reversing y exploting. Escribire un poco de definiciones necesarias y luego un codigo para verlo en la practica via GDB. GNU/Debugger. Los primeros papers los pueden encontrar aca:
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta O en mi humilde blog :) No tienes permitido ver los links. Registrarse o Entrar a mi cuenta.



Sin dudas algo fundamental que no se puede pasar por alto es saber como se divide la memoria de un programa compilado, y es algo que voy a tratar de explicar para que se entienda bien, SI NO SE TIENE CLARO ESTO, se va a complicar bastante el recorrido hacia explotar vulnerabilidades o realizar reversing a software. Es como caminar a ciegas.. Tratare de que sea lo menos aburrido posible. Trataré...

La memoria de un programa compilado se divide en 5 segmentos que son fundamentales y cada uno tiene un propósito especial:

Codigo (o texto)
Datos
Bss
Heap
Stack



- Codigo (o texto): Llamada de ambas maneras, en esta seccion no tendremos ningun permiso de escritura. Esto es intencional para asi poder evitar que se modifique el codigo del programa.
Tampoco es el lugar donde se almacenan las variables, por ende lo unico que encontraremos es 'codigo'. Si por alguna razon nosotros intentaramos escribir alli, el programa colgaria, o alertaria de que esta out-of-band y finalizaria. Tambien suele referirse al contenido de esa parte de memoria como 'static values' por lo que explique recien, y como 'global values' ya que esa zona al ser read-only los valores estan disponibles para cualquier parte del programa que lo necesite.

- Datos y Bss: En esta seccion como se ve en la imagen, es donde se almacenan las variables tanto estaticas como globales. Una vez mas si vemos la imagen, en el campo DATOS vemos que se almacenan las variables estaticas y globales 'inicializadas'. Mientras que en Bss, vemos que se encontraran las variables 'no inicializadas'

Heap: (Seguramente hayan leido en algun lado sobre.. 'heap overflow'), en esta seccion de memoria, el programador tiene total permiso de escritura, es usada como memoria dinamica durante la ejecucion del programa para asignar (allocate) o liberar (free) valores que el programa necesite/no necesite. Es llamado dynamic memory por la frecuencia en que puede cambiar su contenido mientras el programa esta corriendo. No posee un tamaño 'fijo'. Por ende la altitud de la pila crecera o decrecera en base a la cantidad de memoria reservada para su uso. El crecimiento del heap es de abajo.. hacia arriba.

Stack (o pila): Esta zona tambien tiene un tamaño dinamico de memoria, se usa como memoria temporal para almacenar variables de funcion locales y argumentos de llamadas a funcion.
Imaginenlo de esta manera, cuando un programa llama a una determinada funcion, esta funcion va a poseer su propio conjunto de variables locales, y el codigo de esta funcion estara en otra posicion de memoria como habiamos explicado anteriormente en Codigo (o Texto).
Como el contexto y el EIP deben cambiar una vez que se invoca a una funcion, se utiliza la stack para recordar todas las variables pasadas, la posicion a la que debe volver el puntero EIP una vez finalice la funcion y todas las variables locales usadas por esa funcion. (Recuerden, EIP es el puntero de instruccion, imaginenlo como el dedo indice de nuestra mano cuando vamos leyendo un libro, y vamos siguiendo la lectura con el dedo para no perdernos..).

La stack, es un conjunto ordenado de elementos que se agregan o se quitan. Las direcciones altas de memorias 'estan ABAJO' y las direcciones bajas de memoria 'estan ARRIBA'.

La cima de la pila, es conocida como TOP OF THE STACK o tope de la pila. Lo interesante que tiene la pila, es que como dijimos es un conjunto donde se agregan o quitan elementos (imaginenlo como un mazo de cartas), y la manera en que los elementos se apilan o desapilan es por: ULTIMO elemento apilado es el PRIMERO en salir. (Conocido en ingles como LIFO, Last In, First Out). Dos de las funciones basicas que veremos seguido sobre la pila, son "push" y "pop", push inserta un nuevo elemento al 'tope' de la pila, y pop hara lo contrario, lo eliminara del tope.




Marcos de Pila, o stack frame (Sacado de Google, editado por mi, me gusto la definicion):
Cuando se invoca a una funcion, se apilan varias cosas juntas, en un marco de pila. En principio el registro "EBP" al cual llamaremos: Puntero de Marco, Frame Point. Se usa para hacer referencia a las variables de funcion local en el marco de pila actual. Cada marco de pila contiene los parametros de la funcion, las variables locales, dos punteros que son necesarios para volver a dejar las cosas como estaban: el puntero de marco anterior (SPF) y la direccion de retorno que se usa para devolver el EIP a la siguiente instruccion despues de la llamada de funcion. Esto restauraria el contexto de funcion del anterior marco de pila...



A continuacion vamos a hacer una practica de esto, adjunto un codigo basico que va a ayudar a entender mejor todo esto, y luego lo analizaremos con GDB.

Código: c
#include <stdio.h>
void SoyUnaFuncion(int a, int b, int c, int d, int e) {
   int variableLocal1; // Se declaran variables LOCALES de esta funcion.
   int variableLocal2;
   variableLocal1 = 99;
   variableLocal2 = 999;
}

int main() {
   SoyUnaFuncion(1,2,3,4,5); // Invocamos a una funcion "SoyUnaFuncion".
}



Se declaran dos funciones, la clasica main() que es donde iniciara el programa, y luego esta llamara a la funcion "SoyUnaFuncion" (que imaginacion la mia eh xD), y se le pasaran 5 argumentos..

Dentro de la funcion 'SoyUnaFuncion' se declaran dos variables locales. Llamadas: variableLocal1, y variableLocal2 (estoy hecho una luz de imaginacion xD), con sus respectivos valores, 99 y 999.
Como habiamos aclarado al principio, en la zona de la STACK/PILA, se almacenaran estas variables locales de funcion.. Vamos a ver esto con atencion desde el debugger.

Compilamos el archivo con el flag -g. Y lo debuggeamos con gdb:



Presten atencion a lo que resalte en amarillo. Se lo llama "Prologo de procedimiento" o en su ingles "Procedure Prologue", estas primeras instrucciones "configuran el Marco de la Pila".
Reservan el puntero de marco en la pila, y reservan memoria de pila para las variables de funcion locales.

Siguiendo con el codigo de la funcion main(), vimos que lo unico que hacia este basico programa era invocar a la funcion "SoyUnaFuncion" y pasarle 5 argumentos -> "SoyUnaFuncion(1,2,3,4,5)". Estos argumentos seran apilados en la pila, y habiamos comentado que la manera de apilar y desapilar era mediante LIFO -last in first out- osea, ultimo entrado, primero salido.
Veamos la parte resaltada ahora como podemos observar estos 5 numeros enteros pasados a la funcion "SoyUnaFuncion":



Luego vemos que al pie del ultimo argumento, hay un "CALL [0x80483ed]", donde empezara nuestra funcion invocada "SoyUnaFuncion".. Luego de ese CALL tenemos la instruccion LEAVE que es la direccion de retorno que EIP seguira una vez que ese CALL finalice.
Bien, aca es donde el contexto cambia, y hay que prestar atencion..

Cuando ese CALL se ejecute, la direccion de regreso sera agregada a la pila y el rumbo de la ejecucion pasara al principio de la funcion "SoyUnaFuncion" como lo muestra la imagen siguiente:




Observamos el prologo nuevamente de instrucciones que crearan el otro Marco de Pila.

Vemos como con el "PUSH ebp", se apila el valor actual que tiene EBP. Se le da el nombre de SFP (Saved Frame Pointer). Puntero de Marco Anterior, y luego se utilizara para devolver a EBP a su estado anterior.

Vemos la siguiente instruccion "MOV ebp, esp" esto copia el contenido del registro ESP a EBP.
De esta manera se creara el nuevo puntero de marco, y referenciara a las 'VARIABLES LOCALES' de la funcion. (recordemos el codigo "variableLocal1, y variableLocal2").

Por ultimo el "sub esp, 0x10" reservara memoria para estas dos variables dichas, restandole 10 a ESP en este caso.

En la imagen vemos claramente las dos variablesLocales que declaramos. Por sino se dieron cuenta 0x63, y 0x3e7, corresponden a 99 y 999. Los valores que les asignamos a las variables, rapidamente con pcalc vemos los valores de cada uno en Decimal - Hex - Binario:



Ahora vamos a poner algunos breakpoints, para que el debugger se detenga y nos muestre con claridad todo esto..



(1) Como vemos, el gdb se detiene donde se lo pedimos.. en el primer breakpoint de la funcion main().
(2) El valor de ESP esta en 0xbffff0d4. Y el valor de EBP esta en 0xbffff0e8 (Recuerden este valor).

Este breakpoint se encuentra antes de crearse el marco de pila para el CALL a la funcion 'SoyUnaFuncion'.

Si continuamos hasta el segundo breakpoint, ya habremos llegado a la segunda funcion, despues del prologo de procedimiento, donde ya se habrá creado el marco de pila..



Bien, para concluir con esto.. paso a explicar la imagen.

(1) El segundo breakpoint nos deja luego de la creacion del marco de pila, justo en el momento que se asigna la variableLocal1 que tiene 99 de valor. Y se mueve a la direccion de memoria [ebp-0x8] y la segunda variable local a [ebp-0x4]. En el punto (3) imprimimos la direccion destino en dos variables, para recordar esas direcciones.

El punto (2) nos muestra info de EBP y ESP. Vemos como la stack esta al tope en [0xbffff0bc].
Tipearemos 'nexti' dos veces para que el breakpoint donde habiamos parado avance dos instrucciones mas que era donde las variablesLocales estaban insertandose en la pila.. Una vez hecho esto Si examinamos esta zona de la pila entenderemos todo mejor..



En el punto (1) al final del marco de la pila vemos los 5 argumentos de la funcion que habiamos pasado, 5,4,3,2,1. En el punto (2) seguido a los argumentos, vemos la direccion 0x8048435, esa es la direccion de retorno, luego del CALL a la funcion!!..



En el punto (3), se encuentra el puntero de marco anterior, que era el EBP en el anterior marco de pila, (les dije que se lo recuerden ese valor mas arriba). En el punto (4) vemos ambas variablesLocales asignadas en la funcion, las cuales tenian valor 99 y 999..en hexa como vimos arriba, 0x63 y 0x3e7 ;D.

Para finalizar, el programar terminara concluyendo su trabajo, y todo el marco se desapilara de la stack.. Pronto el 5to y ya metiendonos cada vez mas a fondo para explotar cosas.. tiempo al tiempo :)

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta - c0nfused.
Security Researcher
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

hermano simplemente impecable!! que buen nivel da gusto leer asi, en algunas partes me he perdido sinceramente pero es porq no controlo mucho assembler que digamos estoy en un curso de explotacion y te he seguido bastante el ritmo

(eres el c0nfused de la ekoparty verdad?) antes leia tu antiguo blog, gracias x compartir esto!! me apasiona el reversing aunq no se tanto!!

Gracias!. Si soy ese mismo. Espero sea de utilidad, o al menos resuelva mas dudas de las que pueda traer.
Security Researcher
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta