AUTOR: nuMIT_or
HERRAMIENTAS: · Hex
Workshop u otro buen editor hexdecimal.
· eXeScope
v4.40 (opcional)
FUENTES: KATZ, Randy.- "The Portable Executable File Format
from Top to Bottom"
Contenido
INTRO
ALGO SOBRE LA MEMORIA VIRTUAL
ARCHIVOS EN GENERAL Y EL FORMATO PE
¿32 BITS?: MÁS CABEZAS
OTRA CABEZA MÁS
CAMPOS CRÍTICOS DEL ENCABEZADO
VERIFIQUEMOS
MÁS CABEZAS ¡HASTA CUÁNDO!: encabezados de las secciones
TABLAS DE DATOS
ALGUNAS PREGUNTAS - ALGUNAS RESPUESTAS
He tenido noticias de algunos interesados en conocer sobre un tema en el que he estado trabajando: el formato de los archivos ejecutables portables, conocidos como los "PE files". Como acostumbro a llevar notas de lo que hago, he reunido y ordenado este material esperando que pueda ser útil. No me responsabilizo del uso que hagan otros de él.
PE es el formato adoptado por W32 (Windows a 32 bits) para sus archivos ejecutables (EXE), sus librerías dinámicas (DLL), dispositivos virtuales (VXD), etc. W32 adoptó este formato debido a las posibilidades que quedaban abiertas al implementar direcciones y punteros de 32 bits y debido al manejo de la memoria que estas posibilidades exigían. Es por esto, que para comprender mejor la estrcutura del encabezado de los archivos PE conviene un conocimiento mínimo de cómo Windows administra la memoria del sistema.
ALGO SOBRE MEMORIA VIRTUAL
=============================
W32 implementa un manejo de memoria que supone paginación, es decir, un espacio ficticio de memoria llamado espacio de direcciones o de nombres virtuales. Este espacio quedaría dividido en bloques de memoria fija 4 KB llamados "páginas". Al proceso de dividir la memoria en páginas se llama paginación. Se trata de una técnica muy empleada en los sistemas operativos multitareas (capaces de ejecutar más de una tarea al mismo tiempo) para manejar la memoria dinámica del sistema (RAM = Randow Access Memory = Memoria de Acceso Aleatorio) de manera que ninguna tarea altere los datos de otra o la intervenga negativamente en su comportamiento.
W32 emplea punteros y direcciones de 32 bits. Esto quiere decir que, si lo tuvieramos, podríamos usar una memoria de 4GB para cada programa en ejecución. Pero aunque no tengamos todavía ships con esta capacidad de memoria, los programas en W32 corren como si la tuvieran a su disposición. El sistema W32 asigna a cada proceso o programa en ejecución un espacio de direcciones ficticio de 4GB, un montón de memoria que sólo existe imaginariamente. Luego, para ejecutar código del programa o disponer de sus datos, transforma, a través de un par de tablas, las direcciones ficticias en direcciones reales en la memoria física o RAM.
Para comprender cómo W32 transforma direcciones virtuales en direcciones físicas para que el CPU pueda acceder a las instrucciones del programa montadas en la RAM, sugiero la lectura del artículo "La Memoria en W32: intro" que seguramente acompaña a este que lees ahora.
Este tema de la memoria virtual da para más, pero que conviene tratar aparte. Por ahora nos ocupan los PE.
ARCHIVOS EN GENERAL Y EL FORMATO PE
======================================
Un archivo no es más que un conjunto de datos organizados en varias entidades llamadas "registros". Un registro es lo que conocemos en programación como "estructura", es decir, un espacio en memoria que se emplea para guardar datos de manera ordenada.
Una estructura se divide en entidades de tamaño definido destinadas a almacenar datos de justo ese tamaño. A cada una de estas "celdas" de la estructura la llamamos campos.
Las estructuras o registros permiten almacenar información, en sus campos, sobre entidades particulares. En este sentido, la estructura es una colección de campos que pueden ser tratados como una unidad por algún programa. Es un bloque de datos organizado en campos sobre una entidad específica.Toda estructura posee un nombre que la identifica y permite localizarla o manejarla; este nombre es una varable del tipo de la estrcuctura que nombra y permite el tratamiento de las entidades de las cuales informa la estrcutura.
Ahora bien, a la información contenida en un archivo se puede acceder de manera secuencial o directa. El modo secuencial es lento en muchos casos ya que se necesita revisar cada dato desde el comienzo del archivo con orden secuencial, hasta dar con el buscado. Por esto, la mayoría de las veces lo mejor es ir directamente al dato buscado, sin tener que revisar todos los campos del archivo.
Para facilitar el acceso directo a los datos, se pueden colocar unos directorios al comienzo del archivo que indiquen dónde se encuentran ciertos tipos de datos. Los directorios son estructuras cuyos campos, llamados entradas del directorio, son tablas que tienen la misma estrcutura. Funcionan como un "directorio" telefónico. Si busco un dato de cierto tipo, reviso el directorio y veo dónde están agrupados esos datos. Luego voy a la sección donde se encuentran los datos de este tipo. Seguramente en esta sección habrá una tabla que me informe sobre lo que hay en ella. Reviso entonces esta tabla y con lo que me dice, busco el dato que me interesa: no tengo que ir dato por dato para encontrar lo que busco.
Este es el principio a partir del cual se ideó el formato de los archivos PE, y facilitar el montaje del programa en la RAM: se coloca al principio del archivo una serie de estructuras que informan sobre el contenido del archivo. El contenido del archivo se divide en secciones cada una con datos de cierto tipo, formando el cuerpo del archivo. Las estructuras al comienzo del archivo forman el encabezado del archivo y nos dicen la dirección donde se ubica cada sección, su tamaño y sus atributos. A la vez, cada sección implementa tablas con información particular sobre el contenido de su cuerpo.
Entonces, en los archivos con formato PE, tenemos un encabezado y un cuerpo. El encabezado de los archivos PE se subdivide en, podríamos decir, cuatro subencabezados:
Veámos la organización en la siguiente tabla:
PE EXE (Windows 32Bit EXE, DLL, OCX, etc)
| Encabezado MZ EXE | Contiene información necesaria para ejecutar el DOS STUB. Conservado por compatibilidad | Encabezado DOS |
| Encabezado MZ extendido | El desplazamiento (OFFSET) 3Ch apunta al encabezado PE | |
| DOS STUB | Usualmente despliega 'Requires windows to run' o un mensaje similar | Agregado para avisar que el programa rueda en Windows |
| Encabezado PE | Contiene info necesaria para correr el programa en Win32 | Encabezados agregados por W32 |
| Encabezado opcional NT | Contiene info adicional necesaria para correr el programa en Win32 | |
| Tabla de Objetos o Secciones | Información sobre objetos o secciones en el archivo | |
| Objetos o secciones | Datos de las secciones | Cuerpo del archivo |
El cuerpo del archivo con formato PE se subdivide en un número no fijo de secciones, cada una de las cuales dividida también en una tabla de sección, que nos informa sobre el contenido de la sección, y el cuerpo de sus datos.
Veamos ahora con detenimiento el encabezado.
RASTROS ARCÁICOS: ENCABEZADO DOS MZ
=======================================
Los archivos ejecutables con formato PE inician con el encabezado DOS MZ, que no es más que el antiguo encabezado de los archivos EXE más algunos campos adicionales que se agregaron para posibilitar la transición de DOS a Windows.
El encabezado DOS, conservado por compatibilidad, es el mismo que empleaban los antiguos programas DOS de 16 bits, más unos campos adicionales. Su estructura (en ensamblador) es:
_IMAGE_DOS_HEADER STRUC
; /////////////////////////////////////////////////////
; CAMPOS TRADICIONALES
; ////////////////////////////////////////////////////
e_magic DW ?
e_cblp DW ?
e_cp DW ?
e_crlc DW ?
e_cparhdr DW ?
e_minalloc DW ?
e_maxalloc DW ?
e_ss DW ?
e_sp DW ?
e_csum DW ?
e_ip DW ?
e_cs DW ?
e_lfarlc DW ?
e_ovno DW ?
; ////////////////////////////////////////////
; CAMPOS ADICIONALES
; ////////////////////////////////////////////
e_res DW 4 DUP ( ? )
e_oemid DW ?
e_oeminfo DW ?
e_res2 DW 10 DUP ( ? )
e_lfanew DD ?
_IMAGE_DOS_HEADER ENDS
PIMAGE_DOS_HEADER TYPEDEF NEAR PTR _IMAGE_DOS_HEADER
Quien no entienda esta estructura puede orientarse por la siguiente tabla:
Encabezado EXE MZ
| 0000 | Word | ID 'MZ' - Etiqueta de archivo EXE |
| 0002 | Word | Número de bytes en la última página o bloque de 512 bytes del ejecutable |
| 0004 | Word | Número de todas las páginas de 512 bytes en el ejecutable (incluyendo la última) |
| 0006 | Word | Número de entradas de la tabla de relocalizaciones |
| 0008 | Word | Tamaño del encabezado en parágrafos (16 bytes) |
| 000A | Word | Tamaño mínimo de los parágrafos de memoria localizada por encima del final del programa ya cargado en RAM. |
| 000C | Word | Tamaño máximo de los parágrafos de memoria localizada por encima del final del programa ya cargado en RAM. |
| 000E | Word | SS (Stack Segment) relativo al inicio del ejecutable |
| 0010 | Word | SP (Stack Pointer) inicial |
| 0012 | Word | Checksum o 0. Valor de verificación de la suma de las palabras en el ejecutable, usado para verificar la validación por posibles datos perdidos. |
| 0014 | Dword | CS:IP relativo al inicio del ejecutable (Entry point = Punto de entrada) |
| 0018 | Word | Desplazamiento (offset) de la tabla de relocalización. 40h para los nuevos (NE, LE, LX, W3, PE, etc) ejecutables |
| 001A | Word | Número de traslape (0 = programa principal) |
El primer campo de esta estructura, en el desplazamiento 0000, hay dos caracteres: "MZ", que indican que se trata de un archivo ejecutable .EXE.
Si abrimos con HEX WORKSHOP u otro editor hexadecimal un archivo .EXE DOS, por ejemplo DEBUG.EXE, generalmente ubicado en el directorio C:\WINDOWS\COMMAND, veremos en la parte izquierda, en la ventana que despliega caracteres en ASCII, que en el desplazamiento 0000 hay dos caracteres: 'MZ'. Es el número mágico que identifica los archivos .EXE.
A este antiguo encabezado EXE MZ se le han agregado algunos campos que informan al cargador del Sistema Operativo (SO) dónde está el encabezado PE con información relevante para W32.
Encabezado MZ Extendido
| 001C | Dword | Tabla de relocalización con un número variable de reubicación de elementos. |
| 0020 | Dword | Identifuicador OEM |
| 0024 | Dword | Información OEM. |
| 0028 | 26Bytes | Reservado. |
| 003C | Dword | Desplazamiento del nuevo encabezado EXE desde el inicio del archivo o 0 si es un archivo MZ EXE |
El último campo de esta extensión, 'e_lfanew', indica la dirección donde está la signatura que identifica el formato del archivo. Si se trata de un archivo con un programa W32, este campo apunta a dos caracteres: "PE" (Portable Executable), el formato elegido por M$ para los archivos con programas W32.
Si abrimos NOTEPAD.EXE con HEX WORKSHOP y revisamos el campo e_lfanew en el desplazamiento 003Ch, veremos el número 8000h, que al revés es 0080h, el desplazamiento donde veremos los caracteres 'PE', que identifican el formato del archivo (Si trabajas con HEX WORKSHOP, no cierres todavía este archivo). Si ahora abrimos con HEX WORKSHOP el archivo WINFILE.EXE, generalmente ubicado en el directorio C:\WINDOWS, veremos que el desplazamiento 003Ch apunta al desplazamiento 0400h, donde encontramos los caracteres 'NE', que es el formato de los archivos W16.
Inmediatamente después de la signatura hay dos bytes o una palabra (WORD) con ceros, después de los cuales inicia el encabezado PE. En la actualidad, el formato NE está practicamente extinguido. Lo pasaré por alto y me concentraré en el formato PE.
Entre el encabezado MZ DOS y la signatura PE, está la seccuón
"STUB" del archivo, la cual se incluye para el despliegue de un mensaje
que indica que el programa sólo puede correr en Windows.
¿32 BITS?: MÁS CABEZAS
======================
Dos bytes delante de la signatura se inicia el encabezado PE, cuya estructura es:
_IMAGE_FILE_HEADER STRUC
Machine DW ?
NumberOfSections DW ? ; No. de secciones
TimeDateStamp DD ?
PointerToSymbolTable DD ? ; Dir. de la tabla de símbolos
NumberOfSymbols DD ? ; No. de simbolos
SizeOfOptionalHeader DW ? ; Tamaño del proximo encabezado
Characteristics DW ?
_IMAGE_FILE_HEADER ENDSPIMAGE_FILE_HEADER TYPEDEF NEAR PTR _IMAGE_FILE_HEADER
IMAGE_SIZEOF_FILE_HEADER EQU 20
Traduzcamos ésta a estructura a una tabla a desplazamientos (en hexadecimal):
Encabexado PE
| 0000 | Word | CPU_TYPE = Tipo de CPU 0000 - Desconocido 0162 - MIPS I 014c - 80386 0163 - MIPS II 014d - 80486 0166 - MIPS III 014e - 80586 |
| 0002 | Word | Número de objetos en la tabla de objetos |
| 0004 | Dword | Estampa Tiempo/Fecha |
| 0008 | 8Bytes | Puntero a la tabla de símbolos |
| 0010 | Word | Tamaño del encabezado siguiente, encabezado opcional NT |
| 0012 | Word | Banderas 0 - Imagen del Programa 2 - EXE 200 - Dirección fijada 2000 - Librería |
Uno de los campos más importantes de este encabezado es el segundo, en el desplazamiento 0004 desde el inicio del encabezado, que indica el número de secciones en el que se divide el ejecutable. Podemos ver en el volcado de NOTEPAD.EXE en HEX WORKSHOP, en el desplazamiento 0086h, el valor 0600h, que invertido es 0006h, el número de secciones que hay en el archivo.
Como dijimos al comienzo, el formato PE divide su contenido en varias secciones con información de tipo específico. El campo NumberOfSections indica este número.
Otro campo que puede ser de utilidad es SizeOfOptionalHeader, que indica el tamaño del encabezado opcional, inmediatamente después del encabezado PE. Este valor para NOTEPAD.EXE está en el desplazamiento 94h y es E000h, que invertido es 00E0h=224D, es decir, el encabezado opcional tiene un tamaño de 224 bytes.
OTRA CABEZA MÁS
=================
Inmediatamente después inicia el encabezado opcional NT. Randy Katz, en su clásico artículo de 1993, "The Portable Executable File Format from Top to Bottom", divide este encabezado opcional en dos partes, una con campos standard y otra con campos adicionales:
_IMAGE_OPTIONAL_HEADER STRUC
; //////////////////////////////////////
; CAMPOS STANDARD
; //////////////////////////////////////
Magic DW ?
MajorLinkerVersion DB ?
MinorLinkerVersion DB ?
SizeOfCode DD ? ; Tamaño del codigo
SizeOfInitializedData DD ? ; Tamaño de datos inicializados
SizeOfUninitializedData DD ? ; Tamaño de datos no inicializados
AddressOfEntryPoint DD ? ; Dir. virtual del punto de entrada del prog.
BaseOfCode DD ? ; Dir fisica de la base del cod.
BaseOfData DD ? ; Dir fisica de los datos
; //////////////////////////////////////////////////
; CAMPOS ADICIONALES NT
; //////////////////////////////////////////////////
ImageBase DD ? ; Dir virtual de la base de la img.
SectionAlignment DD ? , Alineamiento de secc.
FileAlignment DD ? ; Alinamiento de archivo
MajorOperatingSystemVersion DW ?
MinorOperatingSystemVersion DW ?
MajorImageVersion DW ?
MinorImageVersion DW ?
MajorSubsystemVersion DW ?
MinorSubsystemVersion DW ?
Reserved1 DD ?
SizeOfImage DD ? ; Espacio reservado en memoria para el archivo
SizeOfHeaders DD ? ; Tamaño del conjunto de los encabezados
CheckSum DD ?
Subsystem DW ?
DllCharacteristics DW ?
SizeOfStackReserve DD ?
SizeOfStackCommit DD ?
SizeOfHeapReserve DD ?
SizeOfHeapCommit DD ?
LoaderFlags DD ?
NumberOfRvaAndSizes DD ?
DataDirectory _IMAGE_DATA_DIRECTORY 16 DUP ( <> ) ; Tabla de directorios
_IMAGE_OPTIONAL_HEADER ENDS ; de secciones
PIMAGE_OPTIONAL_HEADER TYPEDEF NEAR PTR _IMAGE_OPTIONAL_HEADER
A continuación la tabla de desplazamientos equivalentes para esta estructura:
Encabezado opcional NT
| Campos Estandard | ||
| 0000 | Word | Reservado |
| 0002 | Word | Versión del enlazador (linker) |
| 0004 | Dword | Tamaño de la sección o segmento de código |
| 0008 | Dword | Tamaño de la sección de datos inicializados |
| 000C | Dword | Tamaño de la sección de datos no inicializados |
| 0010 | Dword | Dirección virtual del punto de entrada (RVA: Relative Virtual Address) - La ejecución comienza aquí. |
| 0014 | Dword | Base de la sección de código |
| 0018 | Dword | Base de la sección de datos |
| Campos Adicionales | ||
| 001C | Dword | Base de la Imagen - inicio de la imagen en la memoria virtual. |
| 0020 | Dword | Alineamiento de los Objetos (Potencia de 2 512-256M) |
| 0024 | Dword | Alineamiento del Archivo (Potencia de 2 512-64k) |
| 0028 | Dword | Versión requerida de sistema operativo |
| 002C | Dword | Versión de usuario |
| 0030 | Dword | Versión de subsistema |
| 0034 | Dword | Reservado |
| 0038 | Dword | Tamaño de la imagen: espacio reservado en memoria para el archivo. |
| 003C | Dword | Tamaño del encabezado |
| 0040 | Dword | Suma de chequeo del archivo |
| 0044 | Word | Subsistema 0 - Desconocido 1 - Nativo 2 - Win GUI 3 - Carácter Win |
| 0046 | Word | Banderas DLL |
| 0048 | Dword | Memoria reservada para la pila (stack) |
| 004C | Dword | Memoria comprometida para la pila |
| 0050 | Dword | Memoria reservada para el montículo (heap) |
| 0054 | Dword | Memoria comprometida para el montículo |
| 0058 | Dword | Reservado |
| 005C | Dword | Número de directorios RVA/Tamaño presentes Todas las entradas RVA tienen tamaño Dword |
| 0060 | 8Bytes | RVA/Tamaño de la tabla de Exportaciones |
| 0068 | 8Bytes | RVA/Tamaño de la tabla de Importaciones |
| 0070 | 8Bytes | RVA/Tamaño de la tabla de Recursos |
| 0078 | 8Bytes | RVA/Tamaño de la tabla de Excepciones |
| 0080 | 8Bytes | RVA/Tamaño de la tabla de la tabla de Seguridad |
| 0088 | 8Bytes | RVA/Tamaño de la tabla Fixup |
| 0090 | 8Bytes | RVA/Tamaño de la tabla Debug |
| 0098 | 8Bytes | RVA/Tamaño de la tabla de Description de la imagen |
| 0100 | 8Bytes | RVA/Tamaño de la tabla de Máquina específica |
| 0108 | 8Bytes | RVA/Tamaño dela tabla de Almacenamiento del hilo local |
CAMPOS CRÍTICOS DEL ENCABEZADO
=================================
- Tamaño de las secciones -
Para montar el ejecutable, W32 necesita reservar espacio en memoria. Como hemos adelantado, W32 no divide la memoria en segmentos de 64KB como lo hacía DOS sino en secciones de tamaño variable. Los campos SizeOfCode, SizeOfInitializedData y SizeOfUninitializedData, informan el espacio de memoria que el sistema debe reservar para cargar cada una de estas secciones.
Estos valores para NOTEPAD.EXE, tal como puede verse en los desplazamientos 009Ch, 00A0h y 00A4h, respectivamente son 0000 3A00h (tamaño de la sección de código), 0000 4600h (tamaño de la sección de datos inicializados) y 0000 0000h (no hay sección de datos no inicializados).
- Punto de entrada (RVA Entry Point) -
Otro dato que necesita el sistema para ejecutar el programa en el archivo es el punto de entrada, es decir, la dirección de la primera instrucción del programa. Esta información se encuentra en el campo AddressOfEntryPoint, que sigue al campo SizeOfUninitializedData del encabezado opcional NT. Para NOTEPAD.EXE este valor, en el desplazamiento 00A8h, es 0010 0000h, que invertido es 0000 1000h.
RVA es la abreviatura de Relative Virtual Address, que en español significa Dirección Virtual Relativa. Significa una dirección virtual, no real, relativa a la base del archivo. El valor de la base del archivo se encuentra en el campo ImageBase (Base de la Imagen).
- Bases de las secciones -
Los campos BaseOfCode y BaseOfData indican las direcciones virtuales relativas (RVAs) a la base del archivo de la sección de código y de la sección de datos, respectivamente. En NOTEPAD.EXE, estos valores se encuentran en los desplazamientos 00ACh y 00B0h y son, respectivamente, 0000 1000h para la sección de código, y 0000 5000h para la sección de datos.
Observa que, para NOTEPAD.EXE, la base del código (BaseOfCode) coincide con el punto de entrada del programa (EntryPoint). Quiere decir que el programa inicia en la primera instrucción de la sección de código.
- Base de la imagen (Image Base) -
Es la dirección virtual relativa a la base del archivo. Esta información la suministra el campo ImageBase del conjunto de campos opcionales del encabezado opcional NT (estructura _IMAGE_OPTIONAL_HEADER). Para NOTEPAD.EXE, este valor está en el desplazamiento 00B4h y es 0000 0040, invertido 0040 0000h. La experiencia dice que este es el valor por defecto elegido por los enlazadores como base para los archivos EXE con formato PE.
Entonces, en el caso de NOTEPAD.EXE, el programa inicia en la dirección virtual Base_de_la_Imagen + Punto_de_Entrada: 0040 0000h + 0000 1000h = 0040 1000h.
Generalmente, 0040 1000h es la dirección virtual de la entrada de los programas en archivos ejecutables con formato PE.
Esto lo podemos comprobar rápidamente ejecutando NOTEPAD.EXE con algún depurador (debugger) para programas que rueden en Windows (tales como SoftIce (el mejor), Cool Debugger, Win32 disassembler, etc.). Veremos que la primera instrucción en la que se detiene el depurador apunta a la dirección 00401000h.
- Alineamientos -
Para el archivo con formato PE se dan dos alineamientos. El alineamiento de las secciones, campo SectionAlignment, y alineamiento de archivo, campo FileAlignment. Estos campos son relevantes porque determinan cuanta memoria destina el sistema para las secciones y el archivo.
Los ejecutables W32 no están divididos en segmentos de hasta 64 KB, como en DOS, sino en secciones cuyo tamaño es el múltiplo de una página de memoria. El tamaño de una página de memoria es de 4 KB. Si una sección ocupa 8 páginas de memoria, su tamaño es de 8*4=32 KB.
Cada sección del archivo PE es cargada secuencialmente en el espacio de direcciones de un proceso, comenzando en ImageBase. El campo SectionAlignment dicta la cantidad mínima de espacio que una sección puede ocupar cuando es cargada --es decir, las secciones están alineadas sobre los límtes o fronteras de SectionAlignment.
El alineamiento de sección no puede ser menor al tamaño de una página (generalmente 4096 bytes en la plataforma x86) y debe ser, en todo caso, un múltiplo del tamaño de la página, tal como dicta el manejador de memoria virtual de Windows. El enlazador (LINKER) establece un valor de 4096 bytes por defecto, pero esto puede establecerse usando del conmutador de enlazador: -ALIGN.
Por ejemplo, si una sección del programa tiene un tamaño de 1200 bytes, el enlazador asignará a esta sección una página, es decir 4096 bytes. Entonces, cuando el programa sea cargado en RAM, el sistema comprometerá una página de memoria física para esta sección.
Para NOTEPAD.EXE, SectionAlignment se encuentra en el desplazamiento 0B8h y tiene un valor de 0000 1000h = 4096 bytes, que es el tamaño de una página.
El campo FileAlignment informa sobre la granularidad mínima de trozos (chunks) de información dentro de la imagen antes de ser cargada. Por ejemplo, el enlazador llena con ceros (zero-pads) un cuerpo de sección (datos brutos [raw data] para una sección) por encima del límite o frontera más cercana de FileAlignment en el archivo. Este valor, FileAlignment, está restringido a ser una potencia de 2 entre 512 y 65,535. En el archivo PE, los datos brutos que comprenden cada sección deben comenzar en un múltiplo de FileAlignment. El valor por defecto es 512 bytes, probablemente para asegurar que las secciones siempre inicien en el comienzo de un sector del disco (un sector de disco tiene un tamaño de 512).
En NOTEPAD.EXE, FileAlignmentse encuentra en el desplazamiento 0BCh y tiene un valor de 0000 0200h = 512 bytes.
- Tamaño de la imagen -
Otro campo indispensable es SizeOfImage, ya que para montar el archivo W32 necesita reservar espacio en memoria. Esta información se encuentra en el campo SizeOfImage. Este valor no es propiamente el tamaño del ejecutable sino la cantidad de espacio que se reserva en el espacio de direcciones para cargar el ejecutable. Este número depende bastante del valor SectionAlignment.
Si un archivo tiene seis secciones, alineadas sobre fronteras de 65,536 bytes, el campo SizeOfImage debería ser 6 * 65.536 = 393.216 bytes (96 páginas). El mismo archivo enlazado con un alineamiento de sección de 4096 bytes (1 página) debería dar 6 * 4096 = 24576 bytes (6 páginas) en el campo SizeOfImage. Pero esto sólo es así si todas las secciones tienen el mismo tamaño. Puede haber secciones con un tamaño mayor al de una página, lo cual cambia el valor de SizeOfImage.
Podemos calcular SizeOfImage para NOTEPAD.EXE. Tiene 6 secciones y su valor SectionAlignement es 1000h = 4096 bytes, entonces SizeOfImage debería ser 6 * 1000h = 6000h, sin embargo este no es el caso.
En NOTEPAD.EXE SizeOfImage está en el desplazamiento 00D0h, cuyo contenido es 00C0 0000h, que invertido es 0000 C000h. Esto es así porque seguramente hay secciones con un tamaño mayor a una página de memoria.
- Directorio de Datos -
Al final del encabezado opcional hay un directorio, que ocupa el campo DataDirectory. Se trata de un vector o arreglo (array) que guarda las direcciones donde se encuentran las tablas de datos de las secciones del archivo. Cada una de estas entradas tiene un tamaño de 8 bytes y se divide en dos campos. El primero indica la dirección virtual relativa a la base donde se encuentra la tabla con datos acerca de alguna sección. El otro campo dice el tamaño de la tabla. Cada entrada de este directorio tendría entonces la siguiente estructura:
_IMAGE_DATA_DIRECTORY STRUC
VirtualAddress DD ? ; Dirección virtual donde está la tabla
Size@ DD ? ; Tamaño de la tabla
_IMAGE_DATA_DIRECTORY ENDS
PIMAGE_DATA_DIRECTORY TYPEDEF NEAR PTR _IMAGE_DATA_DIRECTORY
El número de entradas del directorio está indicado en el campo NumberOfRvaAndSizes del encabezado opcional. Generalmente este valor es 0000 0010h=16D. El tamaño del directorio de datos sería el valor igual al número de entradas por ocho bytes de cada entrada. En este caso es 128 bytes. En NOTEPAD.EXE este valor está en el desplazamiento 00F4h y contiene 1000 0000, que invertido es 0000 0010h = 16d, es decir, 16 entradas en el directorio de datos.
De acuerdo al archivo WINNT.H, las siguientes son las entradas del direcorio de datos.
; Números correspondientes a las entradas del directorio de datos
IMAGE_DIRECTORY_ENTRY_EXPORT EQU 0
IMAGE_DIRECTORY_ENTRY_IMPORT EQU 1
IMAGE_DIRECTORY_ENTRY_RESOURCE EQU 2
IMAGE_DIRECTORY_ENTRY_EXCEPTION EQU 3
IMAGE_DIRECTORY_ENTRY_SECURITY EQU 4
IMAGE_DIRECTORY_ENTRY_BASERELOC EQU 5
IMAGE_DIRECTORY_ENTRY_DEBUG EQU 6
IMAGE_DIRECTORY_ENTRY_COPYRIGHT EQU 7
IMAGE_DIRECTORY_ENTRY_GLOBALPTR EQU 8
IMAGE_DIRECTORY_ENTRY_TLS EQU 9
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG EQU 10
El número que se asigna a cada entrada de directorio de datos se emplea para
facilitar las rutinas de búsqueda de información sobre las secciones.
Ahora cerremos HEX WORKSHOP. Tomemos nota de los valores que hemos encontrado. Abramos NOTEPAD.EXE en EXESCOPE y comparemos los valores desplegados con los que hemos obtenido hasta ahora. Para esta evaluación es mejor usar EXESCOPE ya que muestra el nombre de los encabezados y los desplazamientos donde están los datos.
Comparemos entonces... Nada mal ¿cierto?
Este proceso de ubicación manual, que puede ser tan engorroso hacerlo de esta manera, lo realiza el cargador de Windows de una manera muy rápida y automática. También es el mismo proceso seguido por los volcadores y editores de encabezados de archivos PE.
MÁS CABEZAS ¡HASTA CUÁNDO!: encabezados de
las secciones
=====================================================
La información sobre las secciones del archivo PE se encuentra en las tablas de secciones, después del encabezado opcional están, ordenadas secuencialmente. Cada una de estas tablas tiene la siguiente estructura:
_IMAGE_SECTION_HEADER STRUC
Name DB 8 DUP ( ? ) ; Cadena con el nombre de la sección
; ////
; Misc
; ////
tag$0 <>
VirtualAddress DD ?
SizeOfRawData DD ?
PointerToRawData DD ?
PointerToRelocations DD ?
PointerToLinenumbers DD ?
NumberOfRelocations DW ?
NumberOfLinenumbers DW ?
Characteristics DD ?
_IMAGE_SECTION_HEADER ENDS
PIMAGE_SECTION_HEADER TYPEDEF NEAR PTR _IMAGE_SECTION_HEADER
tag$0 UNION
PhysicalAddress DD ?
VirtualSize DD ?
tag$0 ENDS
Espero que ya no se necesite presentar esta estructura con una tabla de desplazamientos.
El primer campo de la tabla es una arreglo (array) de 8 bytes de largo donde se escribe una cadena de caracteres con el nombre de la sección. Sigue una unión con el tamaño virtual de la sección. Luego el campo VirtualAddress es la dirección virtual relativa a la base de la imagen donde se encuentra la sección.
SizeOfRawData es el tamaño del FileAlignment relativo al cuerpo de la sección. El tamaño actual del cuerpo de la sección será menor o igual al múltiplo de FileAlignment. Una vez que la imagen es cargada dentro del espacio de direcciones de un proceso, el tamaño del cuerpo de la sección llega a ser menor o igual a un multiplo de SectionAlignment. PROCDUMP despliega este campo bajo el nombre "RAW DATA".
PointerToRawData es el desplazamiento a la localización del cuerpo de la sección en el archivo. PROCDUMP llama a este campo "RAW OFFSET".
El campo Characteristics indica las propiedades de la sección, es decir, si
se trata de código objeto, de datos inicializados o no inicializados, si se
puede escribir y leer sobre la sección, si es
ejecutable, si es compartible, etc. PROCDUMP llama a este campo "CHARACTERISTICS".
A continuación las equivalencias:
· 000000020h __Código.
· 000000040h __Datos inicializados.
· 000000080h __Datos no inicializados.
· 040000000h __Sección cacheable.
· 080000000h __Sección paginable.
· 100000000h __Sección compartida.
· 200000000h __Ejecutable.
· 400000000h __Se puede leer.
· 800000000h __Se puede escribir en la sección.
Por ejemplo, si las características es E0000020H, entonces se trata de una sección en la que
1. se pueden escribir datos
80000000h
2. se pueden leer datos
40000000h
3. sección ejecutable
20000000h
4. hay código
00000020h
---------------
E0000020h
Se trata de una sección con código ejecutable. Esta comúnmente lleva como nombre .text.
Hay una tabla de estas para cada sección. NOTEPAD.EXE tiene seis secciones, por lo que tiene seis tablas de secciones.
Abramos de nuevo NOTEPAD.EXE con el editor hexadecimal.
En NOTEPAD.EXE la primera tabla se encuentra en el desplazamiento 0178h. Visualmente es simple ubicarla con el editor hexadecimal ya que cada tabla se inicia con el campo Name, que es un campo de ocho bytes con una cadena de caracteres ASCII con el nombre de la sección. El nombre de la primera sección en NOTEPAD.EXE es .text. Es el nombre que por defecto se asigna a la sección con código ejecutable.
Hay varias secciones predefinidas, cuyos nombres son:
· Sección de código ejecutable: .text
· Secciones de datos: .bss, .rdata, .data
· Sección de recursos: .rsrc
· Sección de datos exportados: .edata
· Sección de datos importados: .idata
· Sección de información para depuración: .debug
"Export" contiene información sobre los nombres que exporta el programa y pueden ser solicitados y usados por otros procesos.
"Import" indica las funciones que el programa solicita a otros; generalmente son funciones que se encuentran en las DLL del sistema, donde se encuentran las funciones de la API de Windows, que son incluidas en los prototipos que definen las funciones empleadas en el programa.
"Resources" contiene información sobre los recursos que contiene el programa: menúes, diálogos, iconos, bitmaps, cadenas de texto, etc.
"Debug" es la sección donde el enlazador, si así lo quizo el programador, guarda información para facilitar la depuración del programa, con un depurador (debugger).
El siguiente campo de una entrada del directorio de datos, después del nombre, es una unión con el tamaño de la sección. La dirección virtual donde se encuentra la sección está en el campo VirtualAddress. La dirección física de la sección está en PointerToRawData.
TABLAS DE DATOS
================
Cada sección tiene una tabla, generalmente al comienzo, con información particular sobre ella.
Para localizar una tabla de éstas, se determina su dirección virtual relativa (RVA: RELATIVA VIRTUAL ADDRESS). Este valor está en el primer campo de la entrada correspondiente en el directorio de datos. Luego se usa esta dirección virtual para determinar en cuál sección está.
La dirección física de la sección nos la dá el campo PointerToRawData de la tabla de sección. La tabla de datos de la sección está en un desplazamiento igual al dato en el campo VirtualAddress en la entrada del directorio de datos, al final del encabezado opcional, menos el dato del campo VirtualAddress en la tabla de sección correspondiente. Este desplazamiento es relativo a la dirección en el campo PointerToRawData.
Como vimos, las tablas de secciones se encuentran inmediatamente después del directorio de datos, es decir después del encabezado opcional.
Con la dirección física del desplazamiento a la primera tabla de las secciones
de la imagen, con el número de secciones de la imagen (en el campo NumberOfSection
del encabezado PE, la RVA donde inicia la sección, la RVA de la tabla de datos
de la sección y el puntero a los datos brutos de
la sección PointerToRawData, puedo localizar donde se hallan los datos que conforman
la tabla de datos de una sección.
Como ilustración localicemos la tabla de la sección de nombres importados .idata en NOTEPAD.EXE.
Primero buscamos en el directorio de datos la entrada que corresponde a .idata. Es la entrada número 1, es decir, la segunda porque la primera es la número 0 (véase los números correspondientes a las entradas del directorio de datos). En NOTEPAD.EXE localizamos esta entrada en el desplazamiento 0100h, que inicia con el campo VirtualAddress de la entrada del directorio que dice la dirección virtual donde se ubica la tabla de datos de la sección .idata. El valor es 0070 0000, que invertido es 0000 7000h.
Luego localizamos el segundo campo de la tabla de sección correspondiente a .idata. Este campo se llama también VirtualAddress e indica la dirección virtual donde inicia la sección. Para encontrarlo manualmente, buscamos la cadena ".idata"; la tabla de sección correspondiente a esta sección inicia en el deplazamiento donde hallamos esa cadena. Este campo se ubica, en NOTEPAD.EXE, en 01F0h. El campo VirtualAddres de esta sección estará entonces en 01FCh, ya que el primer campo, donde está la cadena con el nombre, tiene 8 bytes y el siguiente campo, con el tamaño de la sección, tiene 4 bytes. El valor en 01FCh es 0000 7000h. Quiere decir que la tabla de datos de la sección se encuentra en el inicio de ésta.
Ya que hemos localizado la tabla de seccción, obtenemos el valor del campo PointerToRawData. Este campo lo ubicamos en 0204, y contiene 0042 0000, que invertido es 0000 4200h. Es el desplazamiento del archivo donde están los datos brutos de la sección.
Con estos datos ya podemos localizar la tabla de datos de la sección .idata en NOTEPAD.EXE. Resto la dirección virtual de la sección (campo VirtualAddress de la tabla de sección) a la dirección de la tabla de datos de esa sección (campo VirtualAddress en el directorio de datos). En este caso, la diferencia es cero: la tabla de datos está al inicio de la sección. A la diferencia obtenida le sumo el valor del campo PointerToRawData y obtengo el desplazamiento donde se encuentra la tabla de datos de la sección .idata. Como la dirección virtual de la sección coincide con la dirección de su tabla de datos, el desplazamiento dentro del archivo, donde se encuentra la tabla de datos de la sección .idata, coincide el valor en PointerToRawData: 4200h.
Puede parecer un proceso muy complejo. Pero basta imaginar lo que significaría que el cargador del SO tuviera que buscar de manera secuencial en un ejecutable de 3.2 MB, por ejemplo, qué funciones el programa importa de las librerías DLL del sistema.
Bien, ahora que hemos localizado la tabla de datos de la sección, podemos buscar en ella los nombres importados.
La tabla de datos de la sección de datos importados tiene también una estructura de directorio, es decir, es una tabla con varias entradas con la misma estructura cada una:
IMAGE_IMPORT_DESCRIPTOR
dwRVAFunctionNameList DWORD ?
dwUseless1
DWORD ?
dwUseless2
DWORD ?
dwRVAModuleName DWORD
?
dwRVAFunctionAddressList DWORD ?
IMAGE_IMPORT_DESCRIPTOR ends
PIMAGE_IMPORT_DESCRIPTOR TYPEDEF PTR IMAGE_IMPORT_DESCRIPTOR
Uno de los campos de esta estructura es dwRVAModuleName, una dirección virtual relativa a la base que apunta al nombre del módulo importado. También hay dos parámetros dwUseless que sirven como relleno (padding) para mantener la estructura correctamente alineada dentro de la sección.
Si resto al valor de dwRVAModuleName la dirección virtual de la sección, obtengo el desplazamiento desde el inicio de la sección.
En NOTEPAD.EXE, el campo dwRVAModuleName está en el desplazamiento 4200h + 4 + 4 + 4 = 420Ch, y contiene E874 0000, que invertido es 0000 74E8h. La dirección virtual de la sección .idata es 7000h. Entonces el nombre del primer módulo importado se encuentra en:
0000 74E8h - 0000 70000h = 0000 04E8h + 0000 4200h = 0000 46E8h.
En el desplazamiento 46E8h encuentro una cadena con el primer módulo importado por NOTEPAD.EXE: Shell32.dll. Este nombre estará antecedido por el nombre de una de las funciones importadas del módulo y estará seguido por las demás funciones.
El campo dwRVAFunctionNameList apunta a la lista de punteros a los nombres de las funciones importadas en el módulo. Confirmemos esto en NOTEPAD.EXE.
El campo dwRVAFunctionNameList está en 4200h y contiene 0000 7160h, lo que indica que la lista de punteros a nombres importados del módulo correspondiente está en:
0000 7160h - 0000 70000h = 0000 0160h + 4200h = 0000 4360h.
El valor en este desplazamiento es 000 74D8. Es la dirección virtual de la lista de nombres:
0000 74D8 - 0000 7000 = 0000 04D8h + 4200h = 0000 46D8h.
Si vamos a esta dirección encontraremos que contiene el número 004Eh, seguido por la cadena 'ShellExecuteA'. El primer número es el ordinal de exportación de la función con que inicia la lista de nombres importados del módulo, y la cadena es el nombre de la función. El ordinal es un número de referencia que puedo emplear para llamar a la función importada si no poseo su nombre.
El campo dwRVAFunctionAddressList es sumamente importante. Apunta también a un arreglo (array) donde el cargador de W32 coloca las direcciones virtuales de los puntos de entrada de la funciones importadas. Esto es de sumo interés. Lo repito: cuando el cargador monta el programa en RAM llena este arreglo con las direcciones virtuales donde inician las funciones importadas. Luego, si el programa llama a una de estas funciones, lo hace indirectamente a través de una llamada como esta:
jmp [RVA donde W32 inserta la dir. de la función importada]
El campo dwRVAFunctionAddressList está, en NOTEPAD, en 0000 4210h, y contiene el valor 0000 7370h, ya invertido. Quiere decir que apunta al desplazamiento:
7370 - 7000 = 0370 + 4200 = 4570h <-- Function Address List File Offset
Anota este número.
Ahora fíjate. Desensambla NOTEPAD.EXE con W32DASM. Busca la cadena "ShellExecuteA" hasta llegar a:
* Reference To: SHELL32.ShellExecuteA, Ord:004Eh |
:00402DEE FF1570734000 Call dword ptr [00407370]
Es una típica llamada a una función API de W32. Tiene la forma "CALL DWORD PTR [THUNK RVA]. "THUNK RVA" es la RVA donde el cargador de W32 coloca la dirección virtual donde inicia la función importada en el espacio de direcciones del proceso. Si con un programa como OFFSET (de Iczelion) o OFFCAL (de MrCrimson) revisas el desplazamiento en el archivo que corresponde a la dirección de memoria 00407370, verás que esta dirección corresponde al desplazamiento 0000 4570h, el mismo apuntado por el campo dwRVAFunctionAddressList correspondiente a la información sobre "ShellExecuteA".
No olvides esto último, porque es muy útil para redirigir los llamados a funciones de la API de W32.
Tenemos ya una idea de como se estructura el encabezado significado de sus campos. Todavía queda analizar otras importantes secciones predefinidas, como la sección de recursos .rsrc. Es una de las secciones más atractivas de los archivos PE. Debido a la complejidad de su estructura, prefiero diferir por ahora su análisis, ya que implica un concepto fundamental para el programador: árbol de búsqueda. Se trata de un concepto de datos estructurados que merecería una atención especial.
ALGUNAS PREGUNTAS - ALGUNAS RESPUESTAS ==========================================
Los primeros bytes del archivo... ¿Corresponden siempre y únicamente a la cabecera?
Imagino que sí. El encabezado puede diividirse en cuatro grandes partes. La primera es el viejo encabezado DOS. El último campo de este encabezado apunta a un campo con un par de caracteres que dicen el formato del archivo ejecutable. Ahora, imaginate que este encabezado no esté aquí. El cargador no podría encontrar el encabezado PE ni tampoco podría determinar si se trata de un archivo PE (W32) o NE (W16) o LE (OS2). Hay algunas direcciones que varían pero cuando esto ocurre, algún campo del encabezado especifica dónde han sido relocalizados los datos.
El encabezado es una estructura de datos por la cual el cargador de W32 siempre se orienta para acelerar la carga del ejecutable en vez de implementar algún algorritmo inteligente de búsqueda que impicaría un mayor costo en cuanto tiempo y trabajo de programación.
¿Cuantos bytes tiene la cabecera?
Respecto al tamaño, hay un campo que especifica este valor y se llama SizeOfHeaders y está en el encabezado PE (estructura _IMAGE_FILE_HEADER). El valor varía de acuerdo al número de secciones u objetos del PE.
El valor decimal de "size of image" no corresponde al tamaño real del fichero. ¿Por qué?
Esto es consecuencia de los alineamientos. Realmente este campo indica la cantidad de espacio que se reserva en el espacio de direcciones para cargar el ejecutable. Una cosa es la memoria reservada o comprometida y otra el espacio que ocupa la imagen en el disco duro. Evidentemente, la memoria reservada deberá ser igual o mayor al tamaño del archivo en disco duro. El valor de "size of image" que vemos, por ejemplo, en un editor de archivos PE como ProcDump o Snippet Creator, depende del valor "SectionAlignement", es decir, del alineamiento de las secciones. Es uno de los campos opcionales de la estructura _IMAGE_FILE_HEADER.
Una página, en un sistema con procesador Intel x86, mide 4096 bytes. Si un PE tiene 3 secciones, todas de un tamaño menor a 4096 bytes, y si estas, de acuerdo a SectionAlignement" están alineadas en límites de 65.536 bytes, el tamaño de la imagen será 3 * 65.356 = 196.608 bytes.
Si yo estoy creando el programa puedo controlar el valor de "size of image" variando "SectionAlignement" en las opciones del enlazador (de TLINK o de LINK), pero esto no es necesario, porque el mismo enlazador determina el valor de "SectionAlignement" a partir del tamaño de cada sección.
Si la mayoría de los ejecutables tienen la misma dirección base 00400000h ¿como pueden proyectarse varios ejecutables a la vez? ¿Qué pasa cuando dos ejecutables tienen igual la: direccionbase + puntoentrada?
Esto se aclara viendo cómo W32 convierte direcciones virtuales en direcciones reales. Es una cuestión crucial en SOs multiprocesos. W32 es uno de estos SOs, también UNIX y LINUX. Para no enredar mucho las cosas, se puede decir que W32 crea en memoria física una tabla para cada proceso en ejecución. Esta tabla contiene direcciones de entradas de otra tabla que son punteros hacia las direcciones en memoria real donde se encuentran las instrucciones o los datos cargados en la RAM. La dirección física de la primera entrada de la tabla que transforma direcciones virtuales en reales está en un registro del CPU llamado CR3 (CONTROL REGISTER 3). Para cualquier aplicación puedes obtener el valor en este registro empleando SICE simplemente con el comando CPU, el cual muestra el valor en los diferentes registros del CPU. Hay que tener en cuenta que el valor de CR3 es sólo la dirección física de la primera entrada de una tabla de punteros a direcciones físicas de otra tabla. Se requiere todavía tener el número de la entrada de esta primera tabla para saber donde está en la memoria física la segunda tabla.
Fíjate que casi todas las direcciones virtuales de una aplicación comienzan con 0040XXXXh. No voy a explicarlo, pero esto indica la primera entrada de la tabla apuntada por CR3. Lo cierto es que cada tabla primaria es también una página de 4096 bytes, dividida en 1024 entradas de 32 bytes que son punteros a la base de otra tabla en memoria física.
Ahora, ¿por qué un ejecutable en memoria no interrumpe a otro, aunque tengan las mismas bases virtuales? Las posibles respuestas a esta pregunta me parece que ya se tienen de alguna manera. La dirección real o física de la página de directorio activa se almacena en el registro CR3 del CPU, el cual cambia cada vez que W32 pasa el control de un proceso a otro. Para cada proceso, el sistema crea un directorio de página particular. Para procesos distintos no deben coincidir los directorios de página, de lo contrario se producirá un error de protección, como a veces ocurre.
¿Qué utilidad tiene dividir los ejecutables en secciones?
Para facilitar la ubicación de los datos. Además porque, como expliqué, cada sección tiene diferente función y diferentes atributos. Imagina el enrredo si el enlazador al crear el programa pusiera el código junto a los datos, mezclados con los recursos y otras cosas: uff! Esto es así siempre, incluso en DOS. Lo que pasa es que en DOS se segmenta la memoria; W32 la pagina. También podemos encontrar otras razones que exigirían el manejo de conceptos que finalmente terminan enrredándonos más y haciéndonos parecer que se trata de una cosa muy compleja. Se trata de explicaciones que podemos encontrar en libros sobre arquitectura de computadoras. Hay varios en español, posiblemente en alguna biblioteca.
W32 puede manejar 4 Gb ffff:ffffffff que vemos en el sice. FFFFh x FFFFFFFFh = FFFF0001h = 4294901761 bytes = 4 Gb Lo cual quiere decir que las direcciones que siempre vemos en sice no son de la ram sino de la memoria virtual ¿no?
Muy cierto. Esto creo que cambiará el día que ya no necesitemos memoria virtual. De todos modos, si se tiene suficiente cantidad de RAM, en ocasiones podemos prescindir de ella. Yo nunca he probado, pero me han comentado que es más rápido.
Las paginas de memoria parecen importantes ¿qué son?
W32 divide la memoria física en "páginas" que tiene una longitud de 4096 bytes (4 KB). Una máquina con 8 MB de memoria tiene 2048 páginas. W32 mantiene una colección de tablas de página (también de 4 KB) para traducir direcciones virtuales a direcciones físicas. Cada proceso tiene su propia "página de directorio": una colección de hasta 1024 entradas de 32 bits almacenadas contiguamente. La dirección real o física de la página de directorio activa se almacena en el registro CR3 del CPU, el cual cambia cada vez que W32 conmuta el control entre procesos. Por eso, las direcciones virtuales iguales entre dos procesos diferentes no apuntan a la misma dirección física, a no ser que refieran a espacios de memoria compartidos.
Véase La Memoria en W32: intro.
¿Por qué necesitamos alineamientos? ¿ Por qué FileAligment usualmente usa límites en 512 bytes y SectionAlignment (después de que la imagen fue cargada en RAM) usualmente usa límites de 4096 bytes?
W32 trata con paginación de memoria física y usa memoria virtual. Entonces W32 tiene que manejar memoria virtual y debe tener una manera rápida de encontrar los datos del archivo. Estas son las razones de la existencia del encabezado PE.
W32 pagina la memoria física, la divide en regiones de tamaño fijo. En la arquitectura x86 cada página tiene un tamaño de 4096 bytes. Cualquier sección cargada en RAM debe tener un alineamiento para soportar paginación de memoria. Cuando el sistema reserva memoria, asegura que la región reservada sea un múltiplo par del tamaño de página. SectionAlignment garantiza que cada sección comience en una dirección virtual alineada a una página
FileAlignment es un valor que debe ser potencia de 2 entre 512 y 65,535. Es "la granularidad mínima de pedazos (chunks) de información dentro de la imagen antes de ser cargada" [Katz]. En el archivo PE, los datos brutos que comprenden cada sección deben comenzar en un múltiplo de FileAlignment. El valor por defecto es 512 bytes, probablemente para asegurar que las secciones siempre inicien en el comienzo de un sector del disco (el cual también tiene un tamaño de 512).
W32 llama proceso a un programa en ejecución. Un proceso posee un espacio de 4 GB con los datos y el código del archivo EXE de la aplicación. Un proceso es inerte, no hace nada. Para que haga algo debe poseer un hilo, el cual se responsabiliza de ejecutar el código presente en el espacio de direcciones del proceso. Incluso, un proceso puede tener más de un hilo en el mismo espacio de direcciones ejecutándose al mismo tiempo. Pero el proceso necesita por lo menos un hilo para ejecutar el código proyectado en su espacio de direcciones, en caso contrario el sistema destruye el proceso y su espacio de direcciones.
Es la descripción de la trayectoria que sigue la ejecución de un proceso. Cuando arranca un proceso, el sistema crea un hilo principal (llamando a CreateThread) el cual comienza llamando a la función WinMain, ejecutándola hasta alcanzar la función ExitProcess que culmina el proceso. La noción de hilo fue implementada por W32 para dar cuenta de la posiblidad de los procesos W32 de correr más de un subproceso al mismo tiempo. W32 llama hilos a los subprocesos.
Bueno. Lo visto hasta ahora puede servir como una introducción al conocimiento de los archivos con formato PE y para adquirir cierta conciencia de la importancia de este asunto. Realmente deberíamos profundizar más en este asunto.
Pero ¿qué sentido tiene conocer la estructura del encabezado de los archivos PE si se trata de una información inaccesible para el usuario, una información puesta ahí simplemente para uso del sistema operativo, que nisiquiera necesita ser conocida por el programador? ¿A quién puede interesar esta información?
Este conocimiento interesa a todo aquél que por una u otra circunstancia necesita o quiere modificar u optimizar el comportamiento de un programa contenido en un archivo con formato PE. Para esto, generalmente se necesita introducir código nuevo en el archivo, se necesita agregar o injertar recortes de código. En el siguiente tutorial avanzaremos todavía más en el análisis del encabezado de los PE, estudiando la sección de recursos de estos archivos, .rsrc y realizando un parche en notapad.exe:
Descabezando archivos ejecutables portables ( I I ): Sección de recursos .rsrc
GRACIAS A:
Observaciones y correcciones, comunicarse con: