My Profile Photo

[L]ord [R]NA


Father, son and husband, Red Teamer, Developer, Chess Player, Bitter Truths Distiller, Ex [SHNI/H-Sec] Staff Member, Amateur Astronomer, RedTeamRD Staff/Co-Founder


Disclaimer [ES]

Disclaimer [EN]


Formato PE Para Dembowsers

El Formato PE (Portable Executable), es un formato de archivos ejecutables, bibliotecas DLL, archivos de objetos, entre otros, utilizados por los Sistemas Operativos Windows, en sus versiones de 32 y 64 bits. En sí, estos archivos son una estructura de datos que contiene la información necesaria para ser cargados por el PE Loader del Sistema Operativo y hacerle la vida más fácil al mismo.

Consideremos como funciona CreateProcess, este es el proceso para archivos ejecutables con excepción de las DLL, las cuales se cargan vía LoadLibrary. El procedimiento para iniciar un proceso es el siguiente:

  1. Se convierten y validan todos los parámetros y flags recibidos por CreateProcess
  2. Se lee el ejecutable y se cargan todas las secciones, el tamaño de las mismas, así como la posición relativa desde la dirección base (Veremos esto más adelante).
  3. Se crea el Windows Process Object. En este punto se crea el EProcess, el KProcess y el PEB del proceso en cuestión (Para más información Windows Internals.).
  4. Se crea el Thread Inicial, así como el stack y su contexto (Para más información Windows Internals).
  5. Se realiza el proceso de inicialización para el timo de Subsistema Windows que vaya a ser utilizado, enviando a este la información necesaria, como el MANIFEST del archivo a ejecutar, el ID del proceso que crea el mismo o el manejador del proceso y los threads.
  6. Se inicia la ejecución del Thread Inicial, para dar inicio al nuevo proceso. Este es el punto afectado por flag CREATE_SUSPENDED.
  7. Inicialización del nuevo proceso desde el Entry Point, valor contenido dentro del PE Header.

Esquema de Creacion de Procesos:

Diagrama de Creacion de Procesos

Partiendo del listado anterior, podemos ver la importancia de conocer al menos los puntos básicos del PE Header, los cuales nos facilitarían el trabajo a la hora de realizar ciertas técnicas como el Process Hollowing, IAT Hooking o PE Injection.

Bloques del Formato PE

DOS HEADER

Todo ejecutable en Windows, por cuestiones de compatibilidad, inician con un DOS Header, que va desde la posición 0x00 a la posición 0x3c en el archivo, sin importar si es visto en el archivo con un editor hexadecimal o cargado en memoria (Las diferencias entre la ubicación de las secciones y el código, relativos a la base, es casi idéntica, con ciertas diferencias que veremos más adelante). Para el PE Format, lo importante de esta sección es el Magic Number, que define el mismo como ejecutable, y está ubicado en las primeras dos posiciones del DOS Header, tal cual podremos ver en una muestra tomada de notepad.exe y la ubicación del PE Header, que está en la posición 0x3c. Los valores restantes son necesarios para tener una respuesta en casos de ser ejecutados en entornos DOS (16 bits).

Valores del DOS Header visto desde WinDBG

PE HEADER

El PE Header es un poco más “complejo” y necesario para manipular o entender archivos ejecutables de 32 o 64 bits, inicia con otro Magic Number que es “PE”, y como pudimos ver en el DOS Header, debería estar en la posición 248 relativo al inicio del archivo.

PE Signature visto desde WinDBG

La estructura del PE Header, es _IMAGE_NT_HEADERS para 32 bits e _IMAGE_NT_HEADERS64 para 64 bits, conteniendo la siguiente estructura (ref. winnt.h). La diferencia de ambas estructuras radica en el OptionalHeader, de ambas.

PE Header en Memoria visto desde WinDBG

  • Para sistemas de 32 Bits:
typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
  • Para sistemas de 64 Bits:
typedef struct _IMAGE_NT_HEADERS64 {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

IMAGE_FILE_HEADER

El File Header, contiene información importante como el tipo de arquitectura que se utilizara en el equipo, tamaño del Optional Header, el cual es necesario para saber en que parte inician las secciones contenidas en el ejecutable, así como el número de secciones.

Image File Header desde WinDBG

Los puntos importantes de la misma, mencionados más arriba, son el tipo de arquitectura, manejados en Machine (0x0 relativo al inicio de IMAGE_FILE_HEADER); los cuales pueden ser 0x014c para 32 bits (x86), 0x0200 para Intel Itanium y 0x8664 para 64 bits (x64). Como podemos ver en la imagen anterior, el ejecutable con el que estamos realizando la prueba es un ejecutable de 64 bits. Después de Machine, tenemos el número de secciones, en nuestro caso 7. El timestamp de cuando fue creado el ejecutable, representado como el número de segundos pasados desde el 1ro de enero de 1970. Un puntero a la tabla de símbolos (Requerido para las DLL). El número de símbolos contenidos en la tabla (requerido para las DLL). El tamaño del Optional Header, en nuestro caso 0xf0. Las características que en nuestro caso son dos, IMAGE_FILE_EXECUTABLE_IMAGE (0x0002) e IMAGE_FILE_LARGE_ADDRESS_AWARE (0x0020), los cuales son acumulables.

Estructura C++ para IMAGE_FILE_HEADER:

typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine;
  WORD  NumberOfSections;
  DWORD TimeDateStamp;
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD  SizeOfOptionalHeader;
  WORD  Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

IMAGE_OPTIONAL_HEADER

El Optional Header, es un tanto más interesante que el File Header, porque contiene información requerida a la hora de inicializar un proceso (Entry Point), así como el tamaño de alineación en el archivo (como estará escrito en disco) o la memoria (como estará escrito en memoria), el tamaño de la sección de código, de datos inicializados, del stack, de la zona reservada para Heap.

Image Optional Header visto desde WinDBG

Muestra del Optional Header (0x18 bytes relativos al inicio del PE Header)

En este header, también existe un Magic Number para especificar que tipo de ejecutable es el que contiene el Optional Header, IMAGE_NT_OPTIONAL_HDR32_MAGIC (0x10b) para el Optional Header de 32 bits, IMAGE_NT_OPTIONAL_HDR64_MAGIC (0x20b) para el Optional Header de 64 bits, como nuestro caso, IMAGE_ROM_OPTIONAL_HDR_MAGIC para un Optional Header en roms. Podemos encontrar en el mismo, otros campos importantes como SizeOfCode, SizeOfInitializedData y SizeOfUninitializedData, que contienen el tamaño de la información de la sección de código, de datos inicializados y de datos no inicializados, todos estos alineados con respecto a FileAlignment de esta misma sección. Nos encontramos con quizás uno de los valores más importante a la hora de iniciar ejecución que es AddressOfEntryPoint (Modificado en técnicas como PE Injection), el cual nos dice en que dirección, relativa a la base en memoria, se iniciara la ejecución.

En otro orden, podemos encontrar campos como BaseOfCode que nos dice en que dirección relativa a la base en memoria, iniciara la sección de código, el ImageBase, nos dice que dirección de memoria, será la dirección base en memoria del proceso (Solo aplicable cuando no existe ASRL), y las alineaciones de las secciones, tanto en memoria (SectionAlignment), como en archivo (FileAlignment), tanto como el tamaño del ejecutable en memoria (SizeOfImage), el cual debe estar alineado al valor de SectionAlignment, el tamaño de todos los Headers en el archivo (DOS Header, PE Header, Section Headers) en SizeOfHeaders, el cual debe estar alineado a FileAlignment. Los tamaños del Stack Inicial (SizeOfStackCommit), el total reservado para el Stack (SizeOfStackReserve), aumentando el stack, hasta llegar al tamaño máximo reservado, a medida que sea necesario. Los mismos campos existen para el Heap del Sistema Operativo, en SizeOfHeapCommit y SizeOfHeapReserve, respectivamente.

Al final tendriamos la cantidad de Data Directories en el final del Optional Header (NumberOfRvaAndSizes), asi como cada uno de los Data Directories al final del Optional Header, en el cual se encuentra el famoso IAT, a traves del cual se ejecutan tecnicas como IAT Hooking.

Data Directories visto desde WinDBG

Y por ultimo, para finalizar con la misma, en el Optional Header tenemos el campo de DLLCharacteristics, que permite saber que tipo de posibles protecciones tiene el archivo ejecutable (en nuestro caso 0xc160) y el subsistema donde sera cargado nuestro ejecutable, en nuestro caso IMAGE_SUBSYSTEM_WINDOWS_GUI (2).

Estructura en C++ para IMAGE_OPTIONAL_HEADER:

typedef struct _IMAGE_OPTIONAL_HEADER64 {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  ULONGLONG            ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  ULONGLONG            SizeOfStackReserve;
  ULONGLONG            SizeOfStackCommit;
  ULONGLONG            SizeOfHeapReserve;
  ULONGLONG            SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

IMAGE_SECTION_HEADER

Saliendo del PE Header, vamos a una sección no menos importante, sin la cual, aunque el sistema sabría que tamaño tiene cada sección, la alineación que debería tener y la arquitectura de la misma, no sabría donde estaría ubicada; para localizar la zona de las secciones, utilizaremos el tamaño del Optional Header guardado en el FILE HEADER y lo sumaremos a la posición inicial del Optional Header. Si el cálculo es correcto, deberíamos obtener el nombre de la primera sección, y podríamos avanzar a través de estas, hasta llegar al total de secciones, también contenidas en el File Header (en nuestro caso 7).

Section Header visto desde WinDBG

Las veces anteriores iniciábamos con una imagen de como se ve el Header en memoria, en esta ocasión, debido a la repetitividad del Header, iniciaremos mostrando la estructura del mismo. ¿Por qué esto? para tener una mayor comprensión de la estructura de las secciones en el Formato PE.

Estructura en C++ para el Section Header:

typedef struct _IMAGE_SECTION_HEADER {
  BYTE  Name[IMAGE_SIZEOF_SHORT_NAME];
  union {
    DWORD PhysicalAddress;
    DWORD VirtualSize;
  } Misc;
  DWORD VirtualAddress;
  DWORD SizeOfRawData;
  DWORD PointerToRawData;
  DWORD PointerToRelocations;
  DWORD PointerToLinenumbers;
  WORD  NumberOfRelocations;
  WORD  NumberOfLinenumbers;
  DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

Mencionaremos debajo las más comunes y tomaremos como ejemplo la sección “.text” de notepad.exe (Siéntanse en la libertad de abrir el Debugger o editor hexadecimal / los valores a la izquierda, son relativos al inicio de la sección especifica):

Iniciaremos por el nombre de las secciones (Name), el primer byte es un punto (“.”) y el nombre de la sección no puede exceder una longitud de 8 bytes, en caso de ser necesario un nombre de sección mayor, el primer byte será un slash, seguido de un offset en la tabla de Strings del ejecutable.

Valor de Name en el Section Header

El Physical Address (Misc.PhysicalAddress) y Virtual Size (Misc.VirtualSize), aunque no lo parezca (Vaya nombres le pusieron), almacenan el tamaño real de la información de la sección en el archivo, antes de ser alineados al valor de File Alignment en el Optional Header.

Valores de PhysicalAddress y VirtualSize

El Virtual Address (VirtualAddress) contienen la posición relativa a la base del ejecutable cargada en memoria; en este punto será cargada toda la información de la sección, cuando esté en memoria.

Valor de VirtualAddress en el Section Header

SizeOfRawData contiene el valor de la información de la sección en el archivo, alineada al valor de File Alignment en el Optional Header (Debe ser un valor múltiplo de File Alignment para estar correcto). Puede ser validado tomando el VirtualSize y completando hasta el próximo valor múltiplo de File Alignment (0x200)

Valor de SizeOfRawData en el Section Header

PointerToRawData contiene la dirección física de la información de la sección en el archivo. Este valor también debe estar alineado con respecto al File Alignment en el Optional Header.

Valor de PointerToRawData en el Section Header

Debe ser múltiplo del File Alignment en Optional Header (0x200)

Los cuatro valores previos a las características, son comunes en archivos “.OBJ”, previos a ser enlazados para crear un ejecutable, contienen las redirecciones, la cantidad de redirecciones y el número de líneas, para archivos ejecutables (“EXE”), el valor es 0.

Valores de PointerToRelocations, PointerToLinenumbers, NumberOfRelocations y NumberOfLinenumbers en el Section Header

Las características (Characteristics), al igual que en el File Header, son acumulables y dejan entender al sistema operativo que tipo de secciones son. Las más importantes son IMAGE_SCN_CNT_CODE (0x20), que te especifica que la sección es ejecutable, IMAGE_SCN_CNT_INITIALIZED_DATA (0x40) que marca la sección como una sección de data inicializada, IMAGE_SCN_CNT_UNINITIALIZED_DATA (0x80) que marca la sección como una sección de data no inicializada. IMAGE_SCN_MEM_EXECUTE (0x20000000) para secciones ejecutables, IMAGE_SCN_MEM_READ (0x40000000) para secciones con permiso de lectura, IMAGE_SCN_MEM_WRITE (0x80000000) para secciones con permiso de escritura.

Valor de Characteristics en el Section Header

La sección es ejecutable, de código y con permisos de lectura.

Referencias

Quien guste profundizar mas (Espero que esten motivados), puede darse un paseo por los capitulos 5 de Windows Internals 6 - Part 1, y capitulo 3 de Windows Internals 7 - Part 1. Microsoft provee como capitulo de prueba, el capitulo 5 de Windows Internals 6, les dejo el link mas abajo. Tampoco esta demas echarle un ojo a la documentacion de las estructuras del PE Format, todas estan contenidas en la libreria winnt.h (Importada por la libreria windows.h); por ultimo, como extra, podrian darle un vistazo a un articulo de 1994 de Matt Pietrek, sobre Windows PE Format (link 3).

  1. http://download.microsoft.com/download/1/4/0/14045A9E-C978-47D1-954B-92B9FD877995/97807356648739_SampleChapters.pdf
  2. https://docs.microsoft.com/en-us/windows/win32/api/winnt/
  3. https://docs.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)?redirectedfrom=MSDN