ESTUDIO COLECTIVO DE DESPROTECCIONES
WKT Tutorialz Site
WKT
Programa

Code Snippet Creator 1.05:
Descabezando archivos ejecutables portables

W95 / W98 / NT
Descripción Herramienta para crear recortes (snippets) de código a ser insertos en archivos ejecutables de formato PE y agregar funcionalidades.
Tipo Ejecutable empaquetado
Url http://www.wco.com/~micuan/Tools/snipc105.zip
Protección Empaquetamiento de ejecutable con formato PE
Dificultad 1) Principiante, 2) Amateur, 3) Aficionado, 4) Profesional, 5) Especialista
Herramientas Hex Workshop u otro buen editor hexdecimal.
ProcDump v1.5
W32DASM32 8.93 y/o IDA PRO
eXeScope v4.40
Opcional: NotePad SoftIce v3.0 o superior Oxígeno
Objetivo Comprender la estructura y el contenido del encabezado de los archivos con formato PE
Cracker nuMIT_or
Fecha Octubre de 1999

Descabezando archivos ejecutables portables

Introducci≤n

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.

 

ALGO SOBRE MEMORIA VIRTUAL
=============================

W32 implementa un manejo de memoria que supone paginaci≤n, dividide la memoria física 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, llamada "memoruia virtual". 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.

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.

El tema de la memoria virtual da para mßs y conviene tratarlo aparte. Ahora nos ocupan los PE.

Teoría y un poco de práctica..

 

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.

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 de 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 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:

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
0010 Dword Tamaño de la sección de datos no inicializados
0014 Dword Direcci≤n virtual del punto de entrada (RVA: Relative Virtual Address) - La ejecuci≤n comienza aquφ.
0018 Dword Base de la sección de código
001C Dword Base de la sección de datos
Campos Adicionales
0020 Dword Base de la Imagen - inicio de la imagen en la memoria virtual.
0024 Dword Alineamiento de los Objetos (Potencia de 2  512-256M)
0028 Dword Alineamiento del Archivo (Potencia de 2  512-64k)
0032 Dword Versi≤n requerida de sistema operativo
0036 Dword Versi≤n de usuario
003A Dword Versi≤n de subsistema
003E Dword Reservado
0044 Dword Tama±o de la imagen: espacio reservado en memoria para el archivo.
0048 Dword Tama±o del encabezado
005C Dword Suma de chequeo del archivo
005E Word Subsistema
0 - Desconocido      1 - Nativo
2 - Win GUI             3 - Carßcter Win
0060 Word Banderas DLL
0064 Dword Memoria reservada para la pila (stack)
0068 Dword Memoria comprometida para la pila
006C Dword Memoria reservada para el montφculo (heap)
0070 Dword Memoria comprometida para el montφculo
0074 Dword Reservado
0078 Dword N·mero de directorios RVA/Tama±o presentes
Todas las entradas RVA tienen tama±o Dword
0080 8Bytes RVA/Tama±o de la tabla de Exportaciones
0088 8Bytes RVA/Tama±o de la tabla de Importaciones
0090 8Bytes RVA/Tama±o de la tabla de Recursos
0098 8Bytes RVA/Tama±o de la tabla de Excepciones
00A0 8Bytes RVA/Tama±o de la tabla de la tabla de Seguridad
00A8 8Bytes RVA/Tama±o de la tabla Fixup
00B0 8Bytes RVA/Tama±o de la tabla Debug
00B8 8Bytes RVA/Tama±o de la tabla de description de la imagen
00C0 8Bytes RVA/Tama±o de la tabla de mßquina especφfica
00C8 8Bytes RVA/Tama±o de la 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 el cargador (LOADER) de SICE. Veremos en la ventana desplegada por SICE al detenerse en la primera instrucción que el depurador (DEBUGGER) 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 o en PROCDUMP 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!
============================

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. se pueden compartir datos              10000000h
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       01F0. El campo VirtualAddres de esta sección estará entonces en 01FC, 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 01FC 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. Por este motivo podemos llamar a esta tabla Directorio de Datos Importados, cuyas entradas tienen la siguiente estructura, que llamamos IMAGE_IMPORT_DESCRIPTOR:

 

IMAGE_IMPORT_DESCRIPTOR struc
dwRVAFunctionNameList DWORD ? ;
TimeDateStamp DWORD ? ;
ForwarderChain DWORD ? ;
dwRVAModuleName DWORD ? ;
dwRVAFunctionAddressList DWORD ? ;
IMAGE_IMPORT_DESCRIPTOR ends

PIMAGE_IMAGE_IMPORT_DESCRIPTOR TYPEDEF PTRIMAGE_IMPORT_DESCRIPTOR

Hay tantas entradas de este tipo como módulos importados sean importados. Es decir, hay una entrada ImportDirectory para cada módulo importado.

El primer campo, dwRVAFunctionNameList, apunta a un arreglo (array) de punteros a estructuras llamadsa IMAGE_IMPORT_BY_NAME, que son las listas con los nombres y ordinales de las funciones a usar del módulo importado.

TimeDateStamp indica cuando el archivo fue fabricado.

Uno de los campos importantes de esta estructura es dwRVAModuleName, una direcci≤n virtual relativa a la base que apunta al nombre del m≤dulo importado.

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 la dirección virtual del punto de entrada de la función importada. Esto es de sumo interés: cuando el cargador monta el programa en RAM llena este arreglo con las direcciones virtuales donde inician las funciones importadas. Cuando el programa llama a una de estas funciones lo hace indirectamente a través de una llamada como esta:

jmp [RVA del THUNK]

El campo dwRVAFunctionAddressList está, en NOTEPAD, en 0000 4210h, y contiene el valor 0000 7370h, ya invertido. Quiere decir que apunta a:

7370 - 7000 = 0370 + 4200 = 4570h          ;          <-- dwRVAFunctionAddressList

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 000 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 .rcrs. 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.

De todos modos hagamos un ejercicio para consolidar conocimientos.

 

Al Atake

 

DESEMPAQUETANDO EJECUTABLES
================================

Como ejercicio podemos tomar cualquier archivo ejecutable y examinar el encabezado con un editor hexadecimal, anotar los valores crφticos y despuΘs comprobar si hemos acertado con cualquier volcador (dumper) de archivos PE, como EXESCOPE o PROCDUMP.

Empleemos PROCDUMP y tomemos como vφctima SNIPPETCREATOR de Iczelion.
Se trata de un fabuloso programa ideado por un experto en W32 ASM. Ademßs de permitir editar los encabezados de los archivos PE, sirve para insertar SNIPPETS (recortes) en archivos PE. En otras palabras, este programa permite insertar c≤digo en ejecutables, no s≤lo cambiar unos cuantos bytes, sino rutinas enteras.

Para ver un poco la importancia de conocer los encabezados PE, este programa resulta ejemplar. Veamos.

Supongamos que queremos hacer una lista muerta de SNIPPETCREATOR. Lo abrimos con WDASM32 8.9 o con IDA. íQuΘ extra±o!. No vemos llamado a ninguna API W32.

Carguemos el programa con SICE íCßspita!, ┐quΘ pasa...?. No se despliega el programa en la ventana de SICE. Se abre directamente.

Ahora despleguemos SICE (ctrl+D). Instalemos un BRKP: BPX GetModuleHandleA. Ahora F5 y volvemos a Win. Iniciemos SNIPPETCREATOR. íAhora sφ! Se despliega SICE. Tecleamos F11 y ya estamos en SNIPPETCREATOR. Hacemos alt+C y bajamos por la ventana de c≤digo. Ahora si encontramos un mont≤n de llamadas a APIs W32. Esto no se entiende. ┐De d≤nde salieron estas
llamadas que no aparecieron en la lista muerta?

Tal vez si hacemos un volcado con un editor de archivos PE, como PROCDUMP, podamos encontrar alguna orientaci≤n. Abramos SNIPPETCREATOR con el editor de PROCDUMP.

┐QuΘ vemos en la ventana PE Structure Editor? El campo "Entry Point" (punto de entrada) tiene el valor 00018037h. QuΘ rareza. Generalmente este valor es 00001000h para los ejecutables PE.

Veamos ahora el editor de estructuras (Struct Editor): pulsemos el bot≤n "Sections". Caramba: ninguna secci≤n del archivo lleva nombre predefinido. Los nombres que encontramos son: UPX0, UPX1 y UPX3. El archivo tiene tres secciones con nombres no predefinidos.

Ahora resumamos las observaciones:
1. Encontramos diferencias entre la lista muerta generada por WDASM y el despliegue en memoria que nos presenta SICE.
2. El punto de entrada del programa no es el convencional.
3. Las secciones no tienen nombres predefinidos.
4. El programa no es desplegado inmediatamente en SICE con el LOADER

De 1 ya podemos deducir que el archivo estß encriptado o empaquetado. Cuando generamos la lista muerta, lo que conseguimos es el c≤digo empaquetado. Para ejecutar el programa, el sistema debe montarlo en RAM, entonces debe desempaquetarlo primero. Esta es la importancia de los cargadores (LOADERS), desempaquetan los archivos y despliegan su contenido desempaquetado en memoria. Una vez hecho esto, podemos ver su verdadero contenido, incluso hasta volcarlo (dumping) a un archivo en disco duro. Esto explica la diferencia entre la lista muerta y el despliegue de SICE.

El punto 2 apoya nuestra primera conclusi≤n. El programa inicia no en su primera instrucci≤n sino en otra, inicia en el c≤digo que va desempaquetando el programa original.

El punto 3 es interesante ya que comienza a mostrarnos la importancia de conocer sobre los encabezados PE. Los nombres de las secciones, aunque no son los predefinidos, comienzan todos por el mismo prefijo: UPX. Si el archivo estß empacado, el empacador utilizado cambia el nombre a las secciones y les asigna nuevos que inician con UPX. Veremos que con este dato es suficiente para desempacar SNIPPETCREATOR con PROCDUMP.

El punto 4 nos dice que la secci≤n con las instrucciones ejecutables del programa no tiene asignado los atributos correctos. Esto lo veremos luego.

Nuestro anßlisis del punto 3 nos permite que ya procedamos a desempaquetar nuestra vφctima. Pulsemos el bot≤n "UNPACK" de PROCDUMP. En la caja de dißlogo "Choose Unpacker" vemos una lista de desempacadores. Naveguemos por la lista. A·n sin saber mucho de desempacadores damos de inmediato con el desempacador correcto: UPX. Doble click sobre Θl, escogemos SNIPPETCREATOR, lo desempacamos y lo guardamos en disco duro.

Ahora abramos el archivo desempaquetado con el PE Editor de PROCDUMP. íWAO!: ha cambiado el valor del punto de entrada, ahora es 00001000h, el convencional. Ahora echemos un vistazo en "Sections". íDE NUEVO WAO!: Ahora tenemos cuatro secciones. A las anteriores tres UPXs se agrega .idata, es decir, la secci≤n con datos sobre nombres importados.

TambiΘn han cambiado las caracterφsticas (CHARACTERISTICS) y los desplazamientos virtuales (Virtual Offset) para las secciones. De esto hablaremos luego.

Ahora hagamos una lista muerta del archivo desempacado. Con WDASM aparecen las llamadas a APIs aunque por ordinales. Con IDA PRO sφ aparecen las llamadas a las APIs por nombre. El archivo ha sido desempaquetado. Ahora podemos sentarnos cómodamente a analizarlo y aprender como se hace un buen editor de archivos PE.

En realidad no necesitamos PROCDUMP para hacer el desempaquetado. S≤lo necesitamos hacer un volcado del encabezado PE y ver los nombres de las secciones para identificar el desempaquetador. TambiΘn, conociendo el formato del encabezado, es suficiente abrir el archivo con un buen editor hexadecimal y buscar "manualmente" el encabezado de tablas de secciones y veremos los nombres incriminadores que revelan el empacador empleado.

A prop≤sito, podemos encontrar UPX en http://www.nexus.hu/upx/. Es freeware. Para el momento en que escribo esto, la versi≤n disponible es la v0.84. Si comparamos la diferencia de tamaño entre el ejecutable empaquetado y el desempaquetado, nos daremos cuenta de la calidad de este empaquetador. No desempaca paquetes de versiones anteriores, asφ que no sirve para desempacar SNIPPETCREATOR v1.05.

Ahora hasta podemos renombrar las secciones con los nombres predefinidos correspondientes, si quisieramos. Esto puede ser ·til en ciertas ocasiones que por ahora no pienso mencionar.

Ahora, supongamos que no tenemos a la mano el desempaquetador preciso, ni tampoco lo suministra PROCDUMP. Entonces podemos recurrir a otro mΘtodo, el descrito por Volatily en su artφculo Manually Unpacking - ASPack v1.083, que se puede conseguir en el inmeso WEB site de fravia. Es un mΘtodo un tanto engorroso, pero funciona. Lo ·nico es que no desempaca la secci≤n .idata, entonces cuando lo desensamblamos no aparecen los nombres de las funciones API W32 en sus llamadas.

Resumo los pasos de este procedimiento para desempacar manualmente un archivo ejecutable:

1. Abrimos la vφctima (SNIPPETCREATOR) con el PE Editor de PROCDUMP.
2. Anotamos el punto de entrada (Entry Point): 00018037h
3. En el editor de estructuras (Structures Editor) pulso "Sections".
4. Anotamos los datos de la ventana Sections Editor:

5. Buscamos una secci≤n ejecutable con la bandera de datos inicializados.
Es la secci≤n UPX0:

 

000000040h datos inicializados.
200000000h ejecutable.
400000000h se puede leer.
800000000h se puede escribir en la secci≤n.
--------------
E00000040h

7. Para que el programa pueda ser desplegado por SICE al iniciar, cambiamos las caracterφsticas de la secci≤n UPX0 para hacerla una secci≤n de c≤digo: reemplazamos E00000040h por E00000020h. Para hacer esto pulsamos sobre el nombre de la secci≤n con el bot≤n derecho del rat≤n y elegimos Edit Section del men· despegado. Se despliega ahora la caja de dißlogo Modify section value. Reemplazamos el valor en el campo "Section Characteristics". Ahora podemos desplegar el ejecutable con el LOADER de SICE y desplegarlos en la pantalla del depurador. Anotamos tambiΘn el desplazamiento (Virtual Offset) de esta secci≤n: 1000h. Seguramente serß la direcci≤n donde inicia el programa original.

8. Cargamos la vφctima con SICE. Bajamos por el c≤digo del programa empleando la tecla F10, teniendo cuidado de colocar puntos de ruptura (BREACKPOINTS) o trampas en las instrucciones inmediatamente despuΘs de aquellas donde hay saltos hacia arriba. Por ejemplo:
en la instrucci≤n 0041806F hay un salto hacia arriba: JB 00418060, lo que indica que se trata de un bucle de miles vueltas. Entonces hacemos establecemos una trampa en la instrucción siguiente: BPX 00418073, y tecleamos F5 para pasar por alto este bucle. Este procedimiento debe repetirse para cada bucle encontrado, si no queremos tardar a±os.

9. En cualquier momento llegaremos a las instrucciones:

004181CA          61                        POPPAD
004181CB           E9308EfEFF       JMP 00401000

Es un salto a la direcci≤n 00401000h, que es el desplazamiento donde suponemos que inicia el c≤digo original del programa. Basamos esta suposición en el hecho de que esta es la dirección que generalmente establece por defecto el enlazador como punto de entrada. también en el hecho de que una de las secciones, seguramente la de código (.text) inicia aquí. Para asegurarnos que este es el caso, hacemos BPX 004181CB y seguimos (F10). Cuando arribamos a la direcci≤n 00401000h ya podemos ver llamadas a funciones W32 API. Quiere decir que estamos en la sección de c≤digo.

10. F5 para salir de SICE. Cerramos la vφctima y la corremos de nuevo. Seguramente se desplegarß SICE en algunos de los BREAKPOINTS que dejamos cuando saltßbamos bucles grandes.Debemos pulsar F5 hasta llegar a 004181CB.

11. Cuando llegamos aquφ, cambiamos la instrucci≤n:
004181CB          E9308EfEFF       JMP 00401000

por:
004181CB          E9308EfEFF       JMP 004181CB

Lo hacemos asφ:
a 004181CB jmp 004181CB

12. F5 para volver a Win. El programa se ha quedado en un bucle infinito en la memoria. Abrimos PROCDUMP. Buscamos en la ventana de tareas (Tasks) de PROCDUMP a la vφctima. Pulsamos sobre ella con el bot≤n derecho del rat≤n. Elegimos "Dump (Full)" en el men· desplegado y guardamos el archivo desempacado en disco.

13. Matamos la tarea vφctima.

14. Cambiamos el punto de entrada: abrimos el archivo desplegado con el PE Editor de PROCDUMP, y reemplazamos el valor en Entry Point por 00001000.

15. Abrimos el archivo ya desempaquetado. Perfecto: funciona. Como vemos su tama±o se ha incrementado una barbaridad, lo que indica la potencia del empaquetador UPX.

Si desensamblamos este desempaquetado con WDASM o con IDA, veremos los problemas de este mΘtodo de desempaquetado: no se desempaqueta la secci≤n de importaciones .idata, por lo tanto, no se despliegan los nombres de las funciones W32 API.

 

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" en ProcDump 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 virtual reservada y otra la memoria fφsica comprometida. Yo puedo reservar 10 MB e ir comprometiendo bloques de 300 KB seg·n la necesidad que tenga de ello. Evidentemente, la memoria reservada deberß ser igual o mayor al tama±o de la imagen. El valor de "size of image"que vemos, por ejemplo, en ProcDump 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 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 conmuta el control entre procesos. 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? ┐QuΘ relaci≤n tienen con las direcciones que vemos en SICE?

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.

Las direcciones que despliega SICE son virtuales y a partir de ellas se puede determinar la direcci≤n de memoria fφsica donde estßn los datos o instrucciones correspondientes. Para ello se puede emplear el comando PHYS, que busca la tabla de pßginas y el Directorio de pßginas asociado con el contexto de direcciones actual desplegado por SICE. Es decir, PHYS despliega todas las direcciones virtuales para direcciones fφsicas. Puede emplearse PEEK para leer desde la memoria fφsica, ya que muestra el byte, word, o dword en una direcci≤n fφsica dada. PEEK es ·til para leer registros de entrada o salida proyectados en memoria. TambiΘn es ·til el comando PAGE, el cual permite ver la proyecci≤n de todo el rango de direcciones lineales. Esto permite obtener la direcci≤n fφsica del directorio de pßgina y verificar si las tablas de pßginas del sistema operativo estßn proyectadas en la direcci≤n lineal 0xC0000000. 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.

Como vieron he escogido como vφctima a SNIPPETCREATOR. No es casual. Se trata de un programa muy poderoso, pero que requiere conocimiento de programaci≤n en ASM W32 y conocer el formato PE. Si tienes a la mano MASM 6.X o TASM 5.X, entonces podemos avanzar ya al estudio de SNIPPETCREATOR, lo cual nos darß mayor conocimiento sobre los archivos PE:

snippetcreator: tutorial (en elaboración)

 

Si no tienes conocimiento del lenguaje ensamblador para W32, pero todavía te interesa el tema de los PE, entonces pasemos a estudiar la sección de recursos de estos archivos.

Y tú, cabeza de recursos (en elaboración)

 

 


GRACIAS A:

Observaciones y correcciones, comunicarse con:

nuMIT_or@iname.com



[ Entrada | Documentoz GenΘricoz | WKT TEAM Main Site ]
[ Todo el ECD | x Tipo de Protecci≤n | x Fecha de Publicaci≤n | x orden AlfabΘtico ]