viernes, 24 de marzo de 2017

Reversing I: direccionamiento en memoria


Hace casi unos 9 años, cuando tenía 13 de edad, empecé a interesarme por el área de Reversing y hasta el día de hoy me apasiona :) al iniciar uno empieza a tener contacto con analizadores de código, debuggers y si nos enfocamos en Windows: diferentes herramientas para el análisis de las cabeceras de los archivos PE (Portable Executable). Los términos “offset”, “dirección física”, “dirección virtual”, “dirección virtual relativa” se manejan constantemente en esta área. Recuerdo por aquellos tiempos haberme leído dos documentos bastante detallados sobre el formato PE, buscando en mi viejo HD ¡los encontré! así que les dejo los links de descarga al final del artículo para quienes quieran profundizar.

Entender bien la estructura del formato PE y la forma en que ocurre el direccionamiento en memoria es indispensable si nos interesa el área de reversing. Si bien el tema da para escribir manuales enteros, en este artículo pretendo dejar claro cómo es el direccionamiento en memoria al cargarse un archivo PE (o ejecutable de Windows) y repasar algunas cabeceras importantes de este formato.

Cuando abrimos un .exe con un editor hexadecimal, podemos ver en hexa los bytes del archivo en disco.



Llamamos “offset” a una posición de este archivo en disco. Por ejemplo, en la captura anterior, el byte marcado en rojo se encuentra en el offset 1805h.

En memoria, las cosas cambian, el offset 1805h no estará en la dirección de memoria 0x0040001805 por ejemplo (por si acaso nos imaginamos eso). La pregunta es: ¿Es posible llegar a la dirección de memoria de un offset y así conocer su correspondiente instrucción en ASM? Si, pero antes aclaremos algunos conceptos y luego hacemos el cálculo necesario :)

Cuando un ejecutable se carga en memoria, lo hará casi siempre tomando de base la dirección virtual 0x00400000. A menos que esa dirección virtual esté ocupada, en cuyo caso se utilizará otra -por ejemplo la 0x00800000- y se reubicará todo lo necesario para que el ejecutable funcione bien.



Las direcciones virtuales (VA - VirtualAddress) son visibles sólo por cada proceso y son convertidas por el sistema operativo a una dirección física. Por ejemplo, la VA del proceso A donde se cargó el ejecutable (0x00400000) puede estar mapeada a la dirección física 0x00500000. Mientras que la VA del proceso B (0x00400000) puede estar mapeada a la dirección física 0x00300000.

Hasta ahí queda claro la diferencia entre las direcciones físicas y virtuales, pero existen también las direcciones virtuales relativas (RVA) que son relativas a la dirección virtual donde se cargó el proceso. Esa dirección virtual donde se carga el proceso está definida por el “ImageBase” – una entrada de la cabecera PE “Image Optional Header”- cuyo valor suele ser 0x00400000, ya que como dijimos, es la dirección virtual donde el ejecutable intenta cargarse prácticamente siempre en primera instancia.

Dentro de un ejecutable podemos encontrar muchísimas VA y RVA que apunten a diferentes cosas. Las VA llevan consigo el ImageBase, por ejemplo, si dentro de un ejecutable vemos la dirección 0x00400800, se trata de una VA ya que se está indicando la dirección completa. Si fuera una dirección relativa o RVA, veríamos en su lugar: 0x00000800. El “loader” del sistema operativo será quien se encargue luego de convertir esas direcciones virtuales relativas a direcciones virtuales cuando sea necesario.

Entonces, por si quedó alguna duda:

VA = ImageBase + RVA
    Ejemplo: 0x00400000 + 0x00000800 = 0x00400800.
RVA = VA – ImageBase
    Ejemplo: 0x00400800 – 0x00400000 = 0x00000800.

La pregunta que nos quedó pendiente es ¿Cómo podemos saber la dirección virtual de un offset? Recordemos que las direcciones virtuales son las que podemos visualizar desde un debugger al cargar el ejecutable en memoria, si nos dirigimos a una VA especifica podemos conocer la instrucción ASM del programa en esa posición.

Según la teoría el cálculo es el siguiente:

Offset - Offset de la sección + Offset virtual de la sección + ImageBase 

Bien, ahí hay dos cosas de las que no hablamos: el offset de la sección y el offset virtual de la sección. Allí vamos:

Nuestro offset se encuentra dentro del espacio de una determinada sección del ejecutable que comienza en un offset específico, ese es el offset de la sección. Por ejemplo, si nuestro offset es el 7800h y tenemos la sección “code” que inicia en 7000h y tiene una longitud de 4000h bytes, entonces es obvio que esa es la sección donde nos encontramos y el offset de la sección es el 7000h. Así como está ese offset de la sección también está el offset virtual de la sección. Estos datos podemos verlos fácilmente utilizando alguna herramienta como PELord o Stud_PE.



Es más, estas mismas herramientas tienen conversores Offset <=> RVA y podemos utilizarnos para ahorrarnos el tener que hacer manualmente todo ese cálculo.



Otra utilidad de estas herramientas es poder ver detalladamente todas las cabeceras PE con sus respectivas entradas. Los archivos ejecutables tienen las cabeceras “Image File Header”, “Image Optional Header”, “Image Section Header” e “Image Data Directory” que proporcionan mucha información sobre el ejecutable con el que estamos tratando. Tenía intenciones de hablar de los principales campos/estructuras de estas cabeceras pero para que la nota no se haga muy larga ¡lo dejo para la próxima! Espero haber sido clara, estén atentos a la segunda parte.

Link de descarga de manuales y tools: https://mega.nz/#F!11ljSKQB!IoZ_umxtwFUxewL1uIbAXA.

¡Nos leemos pronto!.

Author: Sheila A. Berta.
Tw: @UnaPibaGeek
Web: www.semecayounexploit.com.

No hay comentarios.:

Publicar un comentario