Descabezando archivos ejecutables portables


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

 

INTRO
======

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 ENDS

PIMAGE_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.

VERIFIQUEMOS
==============

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).

 

¿Qué es un proceso?

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.

 

¿Qué es un hilo (thread)?

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:

nuMIT_or@iname.com