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.:
0x8864para AMD64,0x14cpara 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.