Saltar al contenido
AZ Tools

Inspector PE: visor de EXE y DLL de Windows

Lee una imagen PE de Windows como lo harían dumpbin o pefile, directamente desde los bytes y sin subir nada. La cabecera MS-DOS del principio es un fósil: el único campo que sigue importando es e_lfanew, en el desplazamiento 0x3C, que indica dónde empiezan la firma "PE\0\0" real y la cabecera COFF. A partir de ahí obtienes la máquina (x86, x86-64, ARM64, ARM64EC, Itanium, RISC-V y las demás), si la cabecera opcional es PE32 o PE32+, si la cabecera de archivo lleva la característica de DLL, y bajo qué subsistema lo arrancará el cargador: consola de Windows, GUI de Windows, nativo o alguna de las variantes EFI. La fila de endurecimiento es el equivalente de checksec en Windows y se lee de DllCharacteristics: ASLR (DYNAMIC_BASE), ASLR de alta entropía, que es lo que lleva una imagen de 64 bits de ocho bits de aleatoriedad en la reubicación a diecisiete o más, DEP/NX (NX_COMPAT), Control Flow Guard (GUARD_CF), si se suprimió el manejo estructurado de excepciones (NO_SEH), la comprobación de integridad forzada y si existe siquiera una tabla de certificados. Las secciones aparecen con dirección virtual, tamaño virtual y en disco, y características decodificadas; se marca la que sea a la vez escribible y ejecutable, porque fuera de un empaquetador, un instalador o código automodificable casi nunca hay motivo para ella. Las importaciones se agrupan por DLL con el nombre de cada función, o se muestran como #ordinal cuando el archivo importó por ordinal y por tanto nunca registró un nombre: esa tabla es la respuesta honesta a qué necesita realmente este binario. Las exportaciones dan el nombre con el que la biblioteca se publica, la base ordinal (muy a menudo distinta de 1) y todos los nombres exportados. Dos límites dichos con claridad. Un directorio de certificados no vacío significa que el archivo lleva una firma, no que la firma sea válida, vigente ni de alguien en quien confíes; verificar Authenticode requiere la cadena de certificados y una autoridad de sellado de tiempo, y queda fuera del alcance. Y la marca de tiempo de enlazado es solo un campo que escribe el enlazador, así que una compilación reproducible la habrá fijado a una constante o a un hash. Lo malformado se rechaza en lugar de leerse a medias: un magic MZ incorrecto, una cabecera DOS que no apunta a una firma PE, un magic de cabecera opcional desconocido y una sección que declara más datos de los que contiene el archivo producen cada uno un error con nombre.

Cómo usar

  1. Suelta el archivo en el recuadro. Un .exe, una .dll, un controlador .sys, un control .ocx o un binario .efi se analizan igual: la extensión no es lo que decide.
  2. Lee las tarjetas de cabecera para la arquitectura, PE32 frente a PE32+, EXE frente a DLL y el subsistema. La versión de consola y la de GUI del mismo programa solo se diferencian aquí.
  3. Revisa las etiquetas de endurecimiento. ASLR, DEP y CFG son lo que activa por defecto una cadena de herramientas actual de Windows, así que una etiqueta ámbar en un binario reciente merece explicación antes de distribuirlo.
  4. Busca en las importaciones la capacidad que te interesa: WS2_32 son sockets, WININET o WINHTTP significan que habla con la web, ADVAPI32 registro o servicios, CRYPT32 certificados.
  5. Mira en la tabla de secciones si hay alguna escribible y ejecutable, y en los directorios de datos si hay certificado, entrada de depuración o cabecera del runtime CLR, que cambia cómo debe leerse todo lo demás.

Preguntas frecuentes

El archivo muestra la etiqueta de firma. ¿Significa que es seguro?
No, y la distinción importa. La etiqueta solo dice que el directorio de datos de certificados no está vacío, es decir, que hay un bloque Authenticode añadido después de la última sección. No dice que ese bloque corresponda a los bytes que lo preceden, que el certificado firmante encadene con una raíz en la que Windows confíe, que el certificado fuera válido al firmar ni que quien firmó sea quien el archivo afirma. Cualquiera de esas cosas puede ser falsa con el directorio igualmente lleno: se puede refirmar con un certificado autoemitido, o modificar el contenido después de firmar para que el hash ya no cuadre. Verificarlo de verdad exige la cadena completa, datos de revocación y normalmente un sello de tiempo de confianza, y eso es una operación de red, deliberadamente fuera del alcance de una herramienta que promete no subir tu archivo.
¿Qué añade el ASLR de alta entropía sobre el ASLR normal?
El ASLR normal mueve la imagen a una base aleatoria al cargarla, pero en Windows de 32 bits solo unos ocho bits de la dirección son realmente aleatorios, pocos suficientes para probarlos por fuerza bruta en un bucle. HIGH_ENTROPY_VA le dice al cargador que la imagen puede colocarse en cualquier punto del espacio de direcciones de 64 bits, lo que sube los bits aleatorios por encima de diecisiete y hace impracticable adivinar. Solo significa algo en una imagen PE32+ que además tenga DYNAMIC_BASE, y exige que el programa nunca trunque un puntero a 32 bits: por eso los enlazadores lo dejan tras /HIGHENTROPYVA y por eso un binario de 64 bits heredado de código de la época de 32 bits puede llevarlo desactivado a propósito.
¿Por qué algunas funciones importadas aparecen como #12 en vez de con nombre?
Porque el archivo las importó por ordinal. Cada entrada de un array de thunks es una palabra de máquina cuyo bit más alto es una bandera: si está puesto, los 16 bits bajos son el ordinal que hay que buscar en la tabla de direcciones de la DLL exportadora, y en el archivo importador no se guarda ningún nombre. Si está a cero, el resto de la palabra es un RVA que apunta a un par pista/nombre y el nombre está ahí mismo. Importar por ordinal se resuelve algo más rápido y era común en código antiguo; también es lo que se ve cuando alguien quiere ocultar qué API se llama. Esta herramienta muestra el ordinal en bruto en lugar de adivinar, porque la correspondencia entre ordinal y nombre vive en la DLL exportadora y depende de la versión instalada.
La tabla muestra una sección escribible y ejecutable. ¿Es grave?
Es lo bastante raro como para mirarlo, pero por sí solo no prueba nada. Un compilador y un enlazador normales emiten .text como lectura más ejecución y .data como lectura más escritura, nunca ambas a la vez, porque DEP existe justamente para que esa combinación falle. Las secciones con IMAGE_SCN_MEM_WRITE e IMAGE_SCN_MEM_EXECUTE juntas suelen venir de un empaquetador en tiempo de ejecución que descomprime el código en su propia sección antes de saltar a ella (UPX llama a la suya UPX1), o de un instalador antiguo, un esquema de protección automodificable, o un JIT que reserva dentro de la imagen. Si no esperabas nada de eso, conviene entender qué hace el binario antes de ejecutarlo.
La hora de enlazado dice 1970, o una fecha futura. ¿Está corrupto?
Casi con seguridad no. TimeDateStamp es un campo de 32 bits con segundos desde 1970 que rellena el enlazador, y las cadenas de herramientas modernas dejaron de tratarlo como una fecha. Una compilación reproducible lo pone a cero o a una constante fija para que el mismo código fuente dé siempre una salida idéntica byte a byte, y MSVC con /Brepro escribe ahí un hash de las entradas, que cae en un punto arbitrario del calendario y a menudo muy futuro. La cabecera Rich y el directorio de depuración suelen ser mejor evidencia de la cadena de herramientas que este campo, y nada de ello prueba cuándo se distribuyó realmente el archivo. Tómalo como metadato con forma de fecha.
Es un ensamblado .NET. ¿Por qué las importaciones y el punto de entrada parecen casi vacíos?
Porque en un ensamblado gestionado la parte PE es una cáscara. Cuando el directorio de datos del runtime CLR (índice 14, el descriptor COM) no está vacío, el programa real es IL guardado en metadatos de .NET, y las cabeceras nativas existen sobre todo para satisfacer al cargador de Windows. Un ejecutable de C# clásico tiene exactamente una importación, _CorExeMain de mscoree.dll, un punto de entrada que es un salto de dos instrucciones hacia ella, y una sección .text que es más metadatos que código máquina. Por eso el campo de arquitectura puede decir Intel 386 en un programa que correrá tranquilamente en 64 bits, y los tamaños de sección no dicen nada del tamaño real del programa. Leerlo bien requiere un desensamblador de IL como ildasm o ILSpy.

Herramientas relacionadas