PE
Portable Executable
Última actualización
Portable Executable
Última actualización
Los Portable Executable (PE) es el formato de archivo para ejecutables en Windows. Algunos ejemplos de extensiones de archivos PE son .exe
, .dll
, .sys
y .scr
. En esta página se abordará de forma introductoria la estructura PE, que es importante conocer al construir o realizar ingeniería inversa de malware.
La siguiente imagen muestra una estructura simplificada de un Portable Executable. Cada encabezado que se muestra en la imagen se define como una estructura de datos que contiene información sobre el archivo PE.
El primer encabezado de un archivo PE siempre está precedido por dos bytes, 0xAD y 0x5A, comúnmente conocidos como MZ. Estos bytes representan la firma del encabezado DOS, que se utiliza para confirmar que el archivo que se está analizando o inspeccionando es un archivo PE válido. El encabezado DOS es una estructura de datos definida de la siguiente manera:
Los miembros más importantes de la estructura son e_magic
y e_lfanew
.
e_magic ocupa 2 bytes y tiene un valor fijo de 0x5A4D o MZ.
e_lfanew es un valor de 4 bytes que contiene el desplazamiento al inicio del encabezado NT. Cabe destacar que e_lfanew siempre se encuentra en un desplazamiento de 0x3C.
Antes de pasar a la estructura del encabezado NT, está el DOS stub, que es un mensaje de error que imprime "Este programa no se puede ejecutar en modo DOS" en caso de que el programa se cargue en modo DOS o "Disk Operating Mode". Vale la pena señalar que el mensaje de error puede ser modificado por el programador en tiempo de compilación. Esto no es un encabezado PE, pero es bueno tenerlo en cuenta.
El encabezado NT es esencial ya que incorpora otros dos encabezados de imagen: FileHeader
y OptionalHeader
, que incluyen una gran cantidad de información sobre el archivo PE. Al igual que el encabezado DOS, el encabezado NT contiene un miembro de firma que se utiliza para verificarlo. Por lo general, el elemento de firma es igual a la cadena "PE", que está representada por los bytes 0x50 y 0x45. Pero como la firma es de tipo DWORD, la firma se representa como 0x50450000, que sigue siendo "PE", excepto que está rellenada con dos bytes nulos. El encabezado NT se puede acceder utilizando el miembro e_lfanew dentro del encabezado DOS.
La estructura del encabezado NT varía según la arquitectura de la máquina.
La única diferencia entre ambas versiones de bits es el OptionalHeader
, IMAGE_OPTIONAL_HEADER32
e IMAGE_OPTIONAL_HEADER64
.
Se puede tener acceso por medio de la estructura NT con la siguiente estructura:
Los campos mas importantes de la estructura son:
NumberOfSections
- El número de secciones en el archivo PE (se discutirá más adelante).
Characteristics
- Banderas que especifican ciertos atributos sobre el archivo ejecutable, como si es una biblioteca de vínculos dinámicos (DLL) o una aplicación de consola.
SizeOfOptionalHeader
- El tamaño del siguiente encabezado opcional.
El encabezado opcional es importante y aunque se le llama "opcional", es esencial para la ejecución del archivo PE. Se le llama opcional porque algunos tipos de archivo no lo tienen.
El encabezado opcional tiene dos versiones, una para sistemas de 32 bits y otra para sistemas de 64 bits. Ambas versiones tienen miembros casi idénticos en su estructura de datos, con la diferencia principal siendo el tamaño de algunos miembros. Se utiliza ULONGLONG en la versión de 64 bits y DWORD en la versión de 32 bits. Además, la versión de 32 bits tiene algunos miembros que no se encuentran en la versión de 64 bits.
El encabezado opcional contiene una gran cantidad de información que se puede utilizar. A continuación se muestran algunos de los miembros de la estructura que se utilizan comúnmente:
Magic: Describe el estado del archivo de imagen (imagen de 32 o 64 bits).
MajorOperatingSystemVersion: El número de versión principal del sistema operativo requerido (por ejemplo, 11, 10).
MinorOperatingSystemVersion: El número de versión secundario del sistema operativo requerido (por ejemplo, 1511, 1507, 1607).
SizeOfCode: El tamaño de la sección .text (se discutirá más adelante). AddressOfEntryPoint: Desplazamiento al punto de entrada del archivo (normalmente la función principal).
BaseOfCode: Desplazamiento al inicio de la sección .text.
SizeOfImage: El tamaño del archivo de imagen en bytes.
ImageBase: Especifica la dirección preferida en la que se cargará la aplicación en memoria cuando se ejecute. Sin embargo, debido a los mecanismos de protección de memoria de Windows como la Distribución Aleatoria del Espacio de Direcciones (ASLR), es raro ver una imagen mapeada en su dirección preferida, ya que el Cargador PE de Windows asigna el archivo a una dirección diferente. Esta asignación aleatoria realizada por el cargador PE de Windows puede causar problemas en la implementación de técnicas futuras porque algunas direcciones que se consideran constantes fueron modificadas. El cargador PE de Windows luego pasa por la reubicación de PE para corregir estas direcciones.
DataDirectory: Uno de los miembros más importantes en el encabezado opcional. Es un arreglo de IMAGE_DATA_DIRECTORY, que contiene los directorios en un archivo PE (se discutirán a continuación).
El Data Directory o Directorio de Datos se puede acceder desde el último miembro del encabezado opcional. Es un arreglo del tipo de dato IMAGE_DATA_DIRECTORY que tiene la siguiente estructura de datos:
El arreglo de Directorios de Datos tiene un tamaño de IMAGE_NUMBEROF_DIRECTORY_ENTRIES
, que es un valor constante de 16. Cada elemento en el arreglo representa un directorio de datos específico que incluye información sobre una sección PE o una Tabla de Datos (el lugar donde se guarda información específica sobre el PE).
Se puede acceder a un directorio de datos específico utilizando su índice en el arreglo.
El directorio de exportación de un PE es una estructura de datos que contiene información sobre las funciones y variables que se exportan desde el ejecutable. Contiene las direcciones de las funciones y variables exportadas, que pueden ser utilizadas por otros archivos ejecutables para acceder a las funciones y datos. El directorio de exportación generalmente se encuentra en las DLL que exportan funciones (por ejemplo, kernel32.dll
exportando CreateFileA
).
La tabla de importación de direcciones es una estructura de datos en un PE que contiene información sobre las direcciones de las funciones importadas desde otros archivos ejecutables. Las direcciones se utilizan para acceder a las funciones y datos en los otros ejecutables (por ejemplo, Application.exe importando CreateFileA desde kernel32.dll).
Las secciones PE contienen el código y los datos utilizados para crear un programa ejecutable. Cada sección PE tiene un nombre único y generalmente contiene código ejecutable, datos o información de recursos. No hay un número constante de secciones PE, ya que diferentes compiladores pueden agregar, eliminar o fusionar secciones según la configuración. Algunas secciones también se pueden agregar posteriormente de forma manual, por lo que es dinámico y el campo IMAGE_FILE_HEADER.NumberOfSections ayuda a determinar ese número.
Las siguientes secciones o segmentos PE son las más importantes y existen en casi todos los PE:
.text: Contiene el código ejecutable escrito.
.data: Contiene datos inicializados, es decir, variables inicializadas en el código.
.rdata: Contiene datos de solo lectura. Estas son variables constantes precedidas por "const".
.idata: Contiene las tablas de importación. Estas son tablas de información relacionadas con las funciones llamadas desde el código. Esto es utilizado por el Cargador PE de Windows para determinar qué archivos DLL cargar en el proceso, junto con qué funciones se utilizan en cada DLL.
.reloc: Contiene información sobre cómo corregir las direcciones de memoria para que el programa pueda cargarse en la memoria sin errores.
.rsrc: Se utiliza para almacenar recursos como iconos y mapas de bits.
Cada sección PE tiene una estructura de datos IMAGE_SECTION_HEADER que contiene información valiosa sobre ella. Estas estructuras se guardan bajo los encabezados NT en un archivo PE y se apilan una encima de la otra, donde cada estructura representa una sección.
Recordemos que la estructura IMAGE_SECTION_HEADER es la siguiente:
Al analizar los elementos, cada uno de ellos es altamente valioso e importante:
Name
: El nombre de la sección (por ejemplo, .text, .data, .rdata).
PhysicalAddress
o VirtualSize
: El tamaño de la sección cuando está en memoria.
VirtualAddress
: Desplazamiento del inicio de la sección en la memoria.
PE Overview - https://0xrick.github.io/win-internals/pe2/
DOS Header, DOS Stub and Rich Header - https://0xrick.github.io/win-internals/pe3/
NT Headers - https://0xrick.github.io/win-internals/pe4/
Data Directories, Section Headers and Sections - https://0xrick.github.io/win-internals/pe5/
PE Imports (Import Directory Table, ILT, IAT) - https://0xrick.github.io/win-internals/pe6/