Estrutura de Arquivos PE: Cabeçalhos NT

Introdução

Em artigos anteriores, examinamos a estrutura do DOS Header e realizamos engenharia reversa no DOS stub. Agora, vamos discutir os cabeçalhos NT (NT Headers) dentro da estrutura de um arquivo PE. Antes de prosseguir, é importante entender um conceito fundamental usado com frequência: o Endereço Virtual Relativo (RVA, do inglês Relative Virtual Address). Um RVA é um deslocamento em relação ao endereço base (Image Base) onde o executável é carregado na memória. Para converter um RVA em um endereço virtual absoluto, basta somar o RVA ao Image Base. Esse conceito aparecerá em várias partes do PE.

NT Headers (IMAGE_NT_HEADERS)

O NT Headers é uma estrutura definida no arquivo winnt.h com o nome IMAGE_NT_HEADERS. Possui três membros: um DWORD chamado Signature, um IMAGE_FILE_HEADER chamado FileHeader e um IMAGE_OPTIONAL_HEADER chamado OptionalHeader. Existem duas versões dessa estrutura: uma para 32 bits (IMAGE_NT_HEADERS32) e outra para 64 bits (IMAGE_NT_HEADERS64). Ambas diferem principalmente no tipo do campo OptionalHeader.

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef struct _IMAGE_NT_HEADERS32 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Assinatura (Signature)

O primeiro membro do NT Headers é a assinatura PE, um DWORD de 4 bytes. Seu valor é sempre 0x50450000 (ASCII: "PE\0\0"). Ferramentas como PE-Bear mostram esse campo claramente.

Cabeçalho do Arquivo (IMAGE_FILE_HEADER)

Também chamado de "COFF File Header", o IMAGE_FILE_HEADER contém informações básicas sobre o arquivo PE. Sua definição:

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Os campos são:

  • Machine: Indica a arquitetura alvo (ex.: 0x8864 para AMD64, 0x14c para i386). Consulte a documentação da Microsoft para a lista completa.
  • NumberOfSections: Número de seções (e de cabeçalhos de seção) no arquivo.
  • TimeDateStamp: Timestamp Unix de quando o arquivo foi criado.
  • PointerToSymbolTable e NumberOfSymbols: Usados para depuração COFF; geralmente zero quando as informações de símbolo são descartadas.
  • SizeOfOptionalHeader: Tamanho do Optional Header em bytes.
  • Characteristics: Flags que descrevem propriedades do arquivo (executável, sistema, etc.). Veja a documentação oficial para detalhes.

Em um PE real, é possível visualizar eses valores com ferramentas como PE-Bear ou CFF Explorer.

Cabeçalho Opcional (IMAGE_OPTIONAL_HEADER)

O Optional Header é a parte mais importante dos NT Headers. O carregador do Windows o utiliza para obter informações essenciais para carregar e executar o arquivo. Ele é chamado de "opcional" porque não é necessário para arquivos objeto (.obj), mas é obrigatório para arquivos de imagem (executáveis e DLLs). Seu tamanho não é fixo – o campo SizeOfOptionalHeader do File Header indica seu tamanho real.

Os primeiros 8 campos seguem um padrão COFF padrão; o restante é uma extensão da Microsoft usada pelo carregador e pelo vinculador do Windows.

Existem duas versões: IMAGE_OPTIONAL_HEADER32 (31 campos) e IMAGE_OPTIONAL_HEADER64 (30 campos). A versão 32 bits posssui um campo extra chamado BaseOfData (DWORD), que armazena o RVA do início da seção de dados. Além disso, cinco campos (ImageBase, SizeOfStackReserve, SizeOfStackCommit, SizeOfHeapReserve, SizeOfHeapCommit) são DWORD em 32 bits e ULONGLONG em 64 bits.

// Estrutura para 32 bits (com BaseOfData)
typedef struct _IMAGE_OPTIONAL_HEADER32 {
    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;               // Apenas em 32 bits
    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

Esses campos podem ser inspecionados em um executável real. Por exemplo, o campo Magic com valor 0x20B indica um binário PE64. O AddressOfEntryPoint (RVA) aponta para o código de entrada. O SectionAlignment e FileAlignment definem o alinhamento das seções na memória e no disco, respectivamente. O SizeOfImage é o tamanho total da imagem quando carregada. O Subsystem identifica o ambiente (ex.: 3 para console, 2 para GUI). A DataDirectory será abordada em artigo futuro.

Tags: PE NT Headers IMAGE_NT_HEADERS IMAGE_FILE_HEADER IMAGE_OPTIONAL_HEADER

Publicado em 9-16 18:52