domingo, 30 de diciembre de 2012

Estructura PE (Portable Executable) en Windows

Conceptos:
  • Word: Contiene 2 Bytes.
  • Dword: Contiene 2 Words.
  • Qword: Contiene 2 Dwords.
  • DQWord: Contiene 2 Qwords.  
  • Offset: Numero de desplazamiento, o direccion fisica o virtual.
  • Raw Offset: Direccion fisica de un archivo.
  • Virtual Offset: Direccion en memoria de un archivo.
  • RVA: Cada Seccion establece una Direccion Virtual Relativa (RVA) de un raw offset donde RVA=Offset-PointerToRawData+VirtualAddress.
 La estructura de un archivo PE esta conformada de la siguiente manera:





MS-DOS Header: Es una cabecera que esta incluida en los archivos DOS como en los archivos PE, esta formada de la siguiente manera.


typedef struct _IMAGE_DOS_HEADER
{
     WORD e_magic;
     WORD e_cblp;
     WORD e_cp;
     WORD e_crlc;
     WORD e_cparhdr;
     WORD e_minalloc;
     WORD e_maxalloc;
     WORD e_ss;
     WORD e_sp;
     WORD e_csum;
     WORD e_ip;
     WORD e_cs;
     WORD e_lfarlc;
     WORD e_ovno;
     WORD e_res[4];
     WORD e_oemid;
     WORD e_oeminfo;
     WORD e_res2[10];
     DWORD e_lfanew;
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
e_magic: Contiene las siglas MZ, el cual todo archivo ejecutable contiene,estas siglas son en honor a Mark Zbikowski, diseñador del formato DOS, usado en los archivos ejecutables MS-DOS.

e_cblp: Contiene el numero de Bytes en la ultima pagina del archivo, si este valor es 0 significa que utiliza el ultimo bloque completo el cual es de 512 Bytes.

e_cp:Contiene el numero de bloques en el archivo que pertenecen a este.

e_crlc:Contiene el numero de rehubicaciones despues de la cabecera.

e_cparhdr:Contiene el tamaño de la cabecera en paragrafos.

e_minalloc:Contiene el numero minimo de paragrafos extras, el programa no se puede cargar si no hay por lo menos este tamaño de memoria disponible.

e_maxalloc:Contiene el numero maximo de paragrafos extras para el programa, normalmente el SO reserva toda la memoria restante para el programa, pero con este campo podemos limitar esa reserva.

e_ss:Contiene el valor relativo inicial para el Stack Segment (Pila).

e_sp:Contiene el valor inicial para el registro SP de la UCP.

e_csum:Contiene el valor de la suma de las palabras en el archivo.

e_ip:Contiene el valor inicial del reistro IP de la UCP.

e_cs:Contiene el valor inicial del reistro CS de la UCP.

e_lfarlc:Contiene el raw offset de la primera rehubicacion del archivo.

e_ovno:Numero de superposicion del programa, normalmente 0.

e_res:Reservado.

e_oemid:Identificador OEM.

e_res2:Reservado.

e_lfanew: Contiene el raw offset hacia la cabecera PE Signature.

para un archivo PE en esta estructura solo nos interesa el campo e_magic  que debe ser siempre "MZ" y el campo e_lfanew.

DOS-Stub: 
Es una aplicacion valida que sera ejecutada en el entorno MS-DOS, la cual se encuentra seguida de MS-DOS Header, esta contiene por defecto la leyenda "This program cannot be run in DOS mode".



PE Signature Header 
Esta cabecera esta conformadade la siguiente forma:

typedef struct _IMAGE_NT_HEADERS 
{
  DWORD                 Signature;
  IMAGE_FILE_HEADER     FileHeader;
  IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

Signature: Contiene la leyenda "PE\0\0" la cual identifica el inicio de la cabecera.
FileHeader: Contiene la estructura IMAGE_FILE_HEADER.
OptionalHeader: Contiene la estructura IMAGE_OPTIONAL_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; 
Machine: Es un numero que identifica el tipo de maquina donde debe ser ejecutado.
 
NumberOfSections: es el numero de secciones que contiene el ejecutable, tambien indica el tamaño de la tabla de las secciones la cual va seguida del File Header, el Loader de Windows limita el numero de secciones a 96.

TimeDateStamp: Contiene el numero de segundos transcurridos desde la fecha Enero de 1970, el cual indica cuando fue creado el archivo.

PointerToSymbolTable: Contiene el offset de la tabla de simbolos, COFF Symbol Table, si esta no existe apuntara al offset 0.

NumberOfSymbols: Contiene el numero de entradas en la COFF Symbol Table, seguida de esta tabla se encuentra la String Table.

SizeOfOptionalHeader: Contiene el tamaño de la estructura Optional Header.

Characteristics: Flags que especifican los atributos del archivo.



typedef struct _IMAGE_OPTIONAL_HEADER 
{
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                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;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
Magic: Es un entero sin signo que identifica el tipo del archivo, 0x10B para un ejecutable normal, 0x107 para un archivo de tipo ROM, 0x20B para un archivo PE32+.
MajorLinkerVersion: Indica la version principal del Linker.  
MinorLinkerVersion: Indica la version secundria del Linker.
SizeOfCode: Contiene el tamaño de todas las secciones de codigo del archivo.
SizeOfInitializedData: Contiene el tamaño de las secciones de datos inicializados. SizeOfUninitializedData: Contiene el tamaño de las secciones de datos NO inicializados. AddressOfEntryPoint: Contiene el punto de entrada de la primera instruccion en memoria del archivo que sera ejecutada.
BaseOfCode: Contiene la Direccion Relativa del Archivo Base cuando sea cargada en memoria. BaseOfData: Contiene la Direccion Relativa que apunta al inicio de la seccion de datos en memoria. ImageBase: Es la direccion preferida del primer byte en memoria, este debe ser multiplo de 64 K, por defecto tiene los valores: 0x10000000 para DLL, 0x00010000 para Windows CE, 0x00400000 para Windows NT, Windows 2000, Windows XP, Windows 95, Windows 98, Windows Me.  
SectionAlignment: Este es el campo que contiene la alineacion en bytes de las secciones del archivo cuando son cargadas en memoria, la cual debe igual o mayor que FileAlignment, el valor por defecto es el tamaño de pagina de la arquitectura.  
FileAlignment: Contiene el numero de bytes de la alineacion de las secciones en el archivo ejecutable, el valor de este debe ser una potencia de 2 entre 512 y 64 K, el valor por defecto es 512, si el SectionAlignment es menro que el tamaño de pagina de la arquitectura FileAlignment debe ser igual al SectionAlignment.  
MajorOperatingSystemVersion: contiene el numero principal de la version del sistema operativo requerido.
MinorOperatingSystemVersion: contiene el numero secundario de la version del sistema operativo requerido.  
MajorImageVersion: contiene el numero principal de la version del archivo.
MinorImageVersion: contiene el numero secundario de la version del archivo.  
MajorSubsystemVersion: contiene el numero principal de la version del Subsistema requerido.  
MinorSubsystemVersion: contiene el numero secundario de la version del Subsistema requerido.  
Win32VersionValue: Es un numero reservado y debe ser 0.
SizeOfImage: contiene el tamaño en Bytes del archivo incluyendo todas las cabeceras, cuando la imagen es cargada en memoria esta debe ser multiplo de SectionAlignment.  
SizeOfHeaders: contiene el tamaño en Bytes de MS-DOS Stub, PE Header, y las Secciones, debe ser multiplo de FileAlignment.  
CheckSum: contiene el valor del algoritmo para calcular la suma de comprobacion dictada por IMAGHELP.DLL  
Subsystem: Subsistema requerido para ejecutar el archivo.  

DllCharacteristics:   Caracteristicas del archivo.


SizeOfStackReserve: contiene el tamaño de la pila de ejecucion reservada, solo SizeOfStackCommit es ejecutada, el resto esta de forma disponible en una pagina a la vez hasta que se alcanza el tamaño reservado.  
SizeOfStackCommit: El tamaño de la pila a ejecutar.
 SizeOfHeapReserve: contiene el tamaño del espacio local a reservar, solo SizeOfHeapCommit es ejecutado, el resto del espacio se encuentra disponible una pagina al tiempo hasta ser alcanzada el espacio reservado.  
SizeOfHeapCommit: contiene el tamaño del espacio local a ejecutarse.
LoaderFlags: Reservado y debe ser 0.  
NumberOfRvaAndSizes: contiene el numero de entradas en Data-Directory en el resto de la cabecera opcional, aqui describe la localizacion y el tamaño.


typedef struct _IMAGE_DATA_DIRECTORY
{
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;


VirtualAddress: Contiene el RVA de la tabla en memoria.
Size: Contiene el tamaño de la tabla en bytes.

Podemos encontrar los siguientes directorios:
 

[Export Table]: Seccion llamada .edata contiene informacion sobre las funciones a las cuales otros ejecutables pueden acceder mediante enlaces dinamicos, generalmente esta tabla es usada por archivos DLL.



Export Directory:


Export Address: Contiene un serie de RVA hacia las funciones exportadas, las cuales estan indexadas por su numero ordinal, cada una de las entradas usa uno de los dos formatos siguientes:


para distinguir el formato al que apunta debemos  mirar si el RVA esta en el rango Export Directory Address  el cual es Export Directory Size si se encuentra fuera de ese rango es un ExportRVA al cual sumando el ImageBase obtendremos su direccion en memoria, pero si este se encuentra en el rango es un ForwarderRVA el cual apuntara a una cadena.


Name Pointer: Contiene una matriz de direcciones RVA hacia la tabla Export Name, cada puntero tiene es de tamaño DWORD, estos estan ordenados de lexicamente para permitir busquedas binarias, el RVA es relativo a su ImageBase.

Ordinal Table: Contiene una serie de valores de tamaño WORD, los cuales son los indices de cada funcion en la tabla de exportacion de direcciones, para obtener el indice corrector de cada funcion debera restarsele este valor al OrdinalBase.

Export Name: Contiene una serie de cadenas correspondientes a las apuntadas por la tabla Name Pointer, estas cadenas son publicas, lo cual hace que otros ejecutables puedan ubicarlas sin ningun inconveniente, las cadenas terminan en null byte.



[Import Table]: Todos los ejecutables que enlazan funciones del sistema operativo o propias contienen una seccion de .idata, la cual contiene esta estructura.

La tabla Import Table, esta conformada por una serie de entradas de directorio de importacion, una entrada por cada importacion, la ultima entrada es vacia o llena de valores null, para indicar el final de la tabla de importaciones.

esta tabla contiene esta estructura:



La Import Lookup Table es una serie de 32 Bits para PE32 o de 64 Bits para PE32+, por lo cual el Bit 31 es el mas significativo o el Bit 63 respectivamente, la siguiente entrada es nula para indicar el final de la tabla, esta contien esta estructura:


Hint/ Name Table RVA:

la diferencia entre Import Lookup Table o FirstThunk  y  Import Address Table o OriginalFirstThunk, es que en el archivo ambas apuntan a la misma direccion pero una vez cargadas en memoria, para FirstThunk se le asigna el valor de la funcion en memoria y OriginalFirstThunk tiene un RVA apuntando al nombre de la funcion.


[Resource Table]: Está seccion casi siempre es llamada .rsrc aqui son guardados los recursos del programa tales como, iconos, imagenes, versiones, etc. Esta informacion esta organizada por directorios, donde existe un directorio raiz que contiene la cantidad de subdirectorios con sus tipos y direcciones.

Estructura general de la seccion .rsrc


 El directorio de una tabla de recursos contiene la siguiente estructura:



 Las entradas contienen la siguiente estructura:

los datos de una entrada contienen la siguiente estructura:

Resource Directory String: contiene el siguiente formato:



[Exception Table]: llamada en el ejecutable la seccion .pdata la cual contiene un conjunto de entradas de la tabla de funcion que se utiliza para controlar las excepciones del programa, estas entradas deben seleccionarse de acerdo con las direcciones de la funcion (el primer campo de cada estructura) antes de ser emitidos a la imagen final, segun la plataforma se determina cual sera el formato de esta tabla, de las cuales puede ser una de las siguientes:

Para 32 Bits


Para ARM, PowerPC, SH3 y SH4 plataformas en Windows CE



Para64 Bits y plataformas Itanium:


 Para ARMv7




[Cetificate Table]: esta tabla esta compuesta por un conjunto de datos alineados en 16 Bytes en donde se encuentran las entradas de los atributos del certificado, cada entra de certificado de atributo contiene los siguientes campos:


El valor de la direccion virtual de la entrada de la tabla de certificado en el encabezado opcional del directorio de datos es un archivo de desplazamiento a la entrada de atributo del primero certificado, a las entradas posteriores se accede mediante el avance en bytes de dwLength de esa entrada, redondeando a un multiplo de 8 bytes, desde el inicio de la entrada de atributo del certificado, y asi continua hasta que la suma de los valores redondeados dwLength es igual al valor de tamaño de la entrada de la tabla Certificados en el encabezado opcional del directorio de datos, si la suma de los valores redondeados en dwLength no ss igual al valor de tamaño, o bien el certificado de atributos o tabla o el tamaño es erroneo, el archivo dara error al cargar el certificado.


[Base Relocation Table]: Esta tabla es util para cuando el ejecutable no puede ser cargado en la direccion del ImageBase original por lo cual sera cargada en otra direccion, un ejecutable tiene muchas direcciones fisicas tanto en su Image Data Directory como en los saltos a las importaciones, y si nos encontramos en una base distinta a la predefinida por el ImageBase el ejecutable no funcionara puesto que no podria llegar a la direccion correcta de esa informacion y es por esto que existe esta tabla, donde se busca reubicar esas direcciones estaticas para que se pueda tener acceso a esa informacion sin ningun inconveniente.

las reubicaciones vienen divididas en bloques y cada uno representa las reubicaciones de 1 pagina de memoria (4096 Bytes).

  cada entrada tiene el tamaño de un WORD y esta formada de la siguiente manera:


Tipos de Base Relocation



[Debug Table]: Este directorio se compone de una serie de entradas de directorio de depuración cuya ubicación y el tamaño se indica en el Image Optional Header, este direcotorio de depuracion puede estar en una seccion descartable, puede estar en otra seccion o puede no estar en ninguna seccion.

Cada entrada de directorio de depuración identifica la ubicación y el tamaño de un bloque de información de depuración. El RVA especificado puede ser cero si la información de depuración no está cubierto por un encabezado de sección (es decir, que reside en el archivo de imagen y no es mapeado en el espacio de direcciones en tiempo de ejecución). Si está asignado, el RVA es su dirección.


Debug Type:



[Architecture]: Reservada.

[Global Ptr]: Contiene el RVA del valor que se almacena en el registro de puntero global. El miembro tamaño de esta estructura debe ser fijado a cero.


[TLS Directory]: (Thread Local Storage) Es una clase especial de almacenamiento que soporta Windows, donde se guarda informacion del thread por lo tanto se ejecutara posteriormente a la creacion de este, de este derivan los Thread Local Storage Callbacks, los cuales son una serie de direcciones virtuales que apuntan a funciones a ser llamadas por el Loader despues de la creacion del Thread, la finalidad de los TLS Callbacks es principalmente la inicializacion de objetos y es por eso que tiene soporte de funciones. 

Código ejecutable accede a un objeto de datos TLS estático a través de los pasos siguientes:

1. A la hora del enlace, el vinculador establece la dirección del campo de índice del directorio TLS. Este campo indica una ubicación en la que el programa espera recibir el índice TLS.

2. Cuando se crea un subproceso, el cargador se comunica la dirección de la matriz de la rosca TLS mediante la colocación de la dirección del bloque de entorno hilo (TEB) en el registro FS. Un puntero a la matriz TLS es en el desplazamiento de 0x2C desde el principio de TEB. Este comportamiento es Intel x86-específica.   

3. El cargador asigna el valor del índice TLS en el lugar que se indica la dirección del campo Índice.

4. El código ejecutable recupera el índice TLS y también la localización de la matriz TLS.

5. El código utiliza el índice TLS y la ubicación de la matriz TLS (multiplicando el índice por 4 y usarlo como compensación de la matriz) para obtener la dirección del área de datos TLS para el programa dado y el módulo. Cada hilo tiene su propio TLS área de datos, pero esto es transparente para el programa, que no necesita saber cómo los datos se asignan para hilos individuales.

6. Un individuo TLS objeto de datos se accede como algunos de desviación fija en el área de datos TLS.


La matriz TLS es un conjunto de direcciones que el sistema mantiene para cada hilo. Cada dirección de este vector da la ubicación de los datos TLS para un módulo dado (EXE o DLL) en el programa. El índice TLS indica qué miembro de la matriz a utilizar. El índice es un número (significativo sólo en el sistema) que identifica el módulo.

El directorio TLS tiene el siguiente formato:

  

[Load Config Table]: La entrada de directorio de datos para una estructura previamente reservada SEH configuración de carga debe especificar un tamaño particular de la estructura de configuración de la carga debido a que el cargador del sistema operativo siempre espera que sea de un cierto valor. A este respecto, el tamaño es realmente sólo una comprobación de versión. Por razones de compatibilidad con Windows XP y versiones anteriores de Windows, el tamaño debe ser de 64 x 86 para las imágenes.

La estructura de configuración de carga tiene la siguiente distribución para 32-bit y 64-bit archivos PE.

 

[Bound Import Table]: Cuando un ejecutable utiliza este mecanismo lo que hace es guardar en el directorio Import Address Table las direcciones virtuales de las funciones de la librería obligada, es decir no guarda los RVA si no ya las direcciones reales, este proceso ayuda como optimización ya que con esto el ejecutable no necesita carga cada función si no simplemente cargar la librería y comprobar que las direcciones que ya tiene no estén obsoletas, si no lo están el programa evita la parte de carga de funciones, de lo contrario el programa cuenta con la información de los nombres de las importaciones y la librería lo cual le permitiría corregir esto y cargar las direcciones que están obsoletas.


Este vendría siendo un excelente mecanismo de optimización siempre y cuando se tenga en cuenta que la DLL que utilice este mecanismo no cambie de un sistema a otro, como es el caso de MSVBVM60.DLL, la libreria que utilizan todos los ejecutables generados por VISUAL BASIC 6.0. Cada ejecutable utiliza este mecanisco y le permite mayor velocidad al iniciar. Ahora si la informacion no concuerda y tiene que cargar las funciones no habrá mucha diferencia que como sucede en el import directory. El paso clave de este metodo es que el loader compruebe correctamente si la informacion que tiene obligada el ejecutable concuerda con la libreria que tiene el sistema. 












Este directory utiliza la siguiente estructura que sirve como base de información para comprobar si las direcciones son obsoletas:






























Sections Table Header
Esta tabla se encuentra siempre seguida de la Optional Header debido a que no tiene ningun apuntador que indique en que direccion se encuentra, el numero de secciones esta determinado por el campo NumberOfSections de la estructura FileHeader, las cuales empiezan a ser numeradas a partir de 1 y no de 0, el orden de las secciones esta dado por el linker, una seccion esta seguida por la otra directamente, cada seccion contiene una estructura de 40 Bytes.

typedef struct _IMAGE_SECTION_HEADER 
{
  BYTE  Name[QWORD];
  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;
 
Name: Contiene el nombre de la seccion el cual esta codificado en UTF-8, si el nombre es menor a 8 caracteres esta contiene el NULL BYTE o fin de cadena, si esta es exactamente de de 8 caracteres, no tendra fin de cadena, para nombres mas largos este campo puede utilizar una barra seguida por una representación ASCII de un número decimal que es un desplazamiento en la String Table, las secciones de codigo ejecutables no utilizan la String Table por lo cual no es posible un nombre de seccion de mas de 8 caracteres.
Misc.PhysicalAddress: Raw Offset de la seccion en el archivo.
Misc.VirtualSize: Contiene el tamaño total de la seccion cuando es cargada en memoria, si este valor es mayor que SizeOfRawData es completada con ceros, esta seccion solo es valida para secciones ejecutables para Object files debe ser 0.
VirtualAddress: Contiene la direccion al primer byte de la seccion cuando es cargada en memoria, para Object Files es la direccion del primer byte antes de la reubicacion.
SizeOfRawData: Contiene el tamaño de los datos inicializados, este valor debe ser multiplo de FileAlignment del Optional Header, si este valor es menor que VirtualSize el resto de la seccion precargada es 0, cuando una seccion no contiene datos inicializados esta valor debe ser 0.
PointerToRawData: Contiene el puntero a la primera pagina de la seccion donde se encuentran los datos inicializados de la seccion, si esta seccion no contiene datos inicializados este valor debe ser 0, de lo contrario debe ser multiplo de FileAlignment.
PointerToRelocations: Contiene el puntero al principio de las rehubicaciones de la seccion, si no hay reubicaciones debe ser 0.
PointerToLinenumbers: Contiene el punto al principio de entradas de numeros de linea de la seccion, este valor debe ser 0, para una imagen ejecutable, porque la informacion de depuracion COFF esta en desuso.
NumberOfRelocations: Contiene el numero de reubicaciones para la seccion, este valor debe ser 0 para una imagen ejecutable.
NumberOfLinenumbers: Contiene el numero de LineNumbers de la seccion, este valor debe ser 0 para una imagen ejecutable debido a que la informacion de COFF debugging es obsoleta.
Characteristics: contiene los Flags que caracterizan a la seccion.


 

IMAGE_SCN_LNK_NRELOC_OVFL: Indica el numero de traslados para la seccion cuando excede los 16 bits reservados por la seccion, si esto es asi y NumberOfRelocations es 0xFFFF, el valor de relocaciones estara ubicado en el campo VirtualAddress de la prima reubicacion, esto seria un error si la seccion tiene la caracteristica IMAGE_SCN_LNK_NRELOC_OVFL y hay menos de 0xFFFF reubicaciones en la seccion.