O assunto dessa vez será o formato de arquivos objetos e executáveis voltado para Windows 32bits (originado do Unix COFF) o PE (Portable Executable). Dando atenção ao fato que não sou nenhum expert no assunto, portanto pode haver inconsistência ou invalidez no texto redigito, portanto leia por conta e risco.
P: Gilson, porque seu interesse nesse assunto?
R: Eu sempre aprendi de acordo com a necessidade, nunca precisei saber do formato PE para executar minhas aplicações ou aplicar patchs aos softwares. Mas dessa vez, ao fazer um speed hack para um jogo procurei uma alternativa de sempre procurar os endereços das funções para substituir (como o jogo sofre constantes atualizações), assim teria uma dll estável e sem a necessidade de atualizá-la cada vez que o jogo passasse por um update.
Vamos lá.
Um arquivo PE é formado por blocos de códigos, abaixo mostrei os blocos, suas estruturas e tentarei descrever cada parte para um melhor entendimento.
O Windows tem seu system loader que é encarregado de pôr o arquivo na memória, verificar os blocos, resolver endereços, importar e exportar funções caso os tenha. HMODULE é o nome dado quando o arquivo vai pra memória nossos módulos.
Primeiro temos o DOS Header (IMAGE_DOS_HEADER) constituído de 64 bytes, segue a estrutura:
struct _IMAGE_DOS_HEADER { WORD e_magic; WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; DWORD e_lfanew; };
e_magic – assinatura MZ (0x4D5A)
e_cblp – tamanho da última página
e_cp – total de página
e_crlc – itens de realocação
e_cparhdr – tamanho do cabeçalho
e_minalloc – tamanho mínimo de memória
e_maxalloc – tamanho máximo de memória
e_ss – valor inicial do registrador SS (Stack Segment)
e_sp – valor inicial do registrador SP (Stack Pointer)
e_csum – checksum do cabeçalho
e_ip – valor inicial do registrador IP (Instruction Pointer)
e_cs – valor inicial do registrador CS (Code Segment)
e_lfarlc – offset (distância) do fragmento stub
e_ovno – overlay
e_res[4] - bytes reservados
e_oemid – identificador OEM
e_oeminfo – informações OEM
e_res2[10] - bytes reservados
e_lfanew – offset do cabeçalho do arquivo PE
O e_lfarlc, esse fragmento é executado caso arquivo PE não possa ser executado. Consiste num número muito pequeno de bytes com instruções, assim ao carregar o arquivo na memória é verificado se é compatível com o sistema, caso não seja essa sessão é executada.
Continuando temos o bloco NT Header (IMAGE_NT_HEADERS) contendo File Header (IMAGE_FILE_HEADER) e Optional Header (IMAGE_OPTIONAL_HEADER).
struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS;
Signature – Assinatura do cabeçalho PE
FileHeader – Estrutura IMAGE_FILE_HEADER
OptionalHeader – Estrutura IMAGE_OPTIONAL_HEADER32
struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER;
Machine – Tipo de plataforma prevista pra rodar o executável
NumberOfSections – Número de seções apos o cabeçalho
TimeDateStamp – Data e hora de criação do arquivo
PointerToSymbolTable – Ponteiro para tabela de símbolos
NumberOfSymbols – Número de símbolos
SizeOfOptionalHeader – Tamanho do cabeçalho opcional
Characteristics – Características (Flags) do arquivo.
struct _IMAGE_OPTIONAL_HEADER { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; 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;
Magic – Especifica o tipo de arquivo (0x010B executável, 0x0107 imagem rom)
MajorLinkerVersion – versão maior do linker
MinorLinkerVersion – versão menor do linker
SizeOfCode – tamanho do código executável
SizeOfInitializedData – tamanho do bloco de dados de inicialização (Data Segment)
SizeOfUninitializedData – tamanho do bloco de dados não inicializados (BSS Segment)
AddressOfEntryPoint – endereço do ponto de inicialização (RVA)
BaseOfCode – base de código
BaseOfData – base de dados
ImageBase – endereço de mapeamento (preferencial)
SectionAlignment – alinhamento da seção na memória RAM
FileAlignment – alinhamento da seção do arquivo em disco
MajorOperatingSystemVersion – versão maior esperada do sistema operacional
MinorOperatingSystemVersion – versão mínima esperada do sistema operacional
MajorImageVersion – versão máxima do arquivo
MinorImageVersion – versão mínima do arquivo
MajorSubsystemVersion – versão máxima do subsistema esperado
MinorSubsystemVersion – versão mínima do subsistema esperado
Win32VersionValue – versão do win32
SizeOfImage – tamanho da imagem somando os cabeçalhos e seções
SizeOfHeaders – tamanho dos cabeçalhos
CheckSum – checksum do arquivo
Subsystem – subsistema requerido
DllCharacteristics – Características (Flags) das DLLs
SizeOfStackReserve – tamanho reservado do da pilha stack
SizeOfStackCommit – tamanho inicial da pilha salva
SizeOfHeapReserve – tamanho reservado aos heaps
SizeOfHeapCommit – tamanho inicial do heap salvo
LoaderFlags – Parâmetros (Flags) para o carregador do sistema
NumberOfRvaAndSizes – Número e tamanho de RVA
DataDirectory – Diretório de dados constituído de uma matriz de 16 índices (com localização RVA e tamanho de cada peça de informação)
Seguindo temos a estrutura de cada índice do DataDirectory
struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY;
VirtualAddress – endereço da seção
Size – tamanho da seção
Começando do índice 0 temos as seguintes estruturas em DataDirectory:
enum E_DIRECTORY_ENTRY { IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_DIRECTORY_ENTRY_IMPORT, IMAGE_DIRECTORY_ENTRY_RESOURCE, IMAGE_DIRECTORY_ENTRY_EXCEPTION, IMAGE_DIRECTORY_ENTRY_SECURITY, IMAGE_DIRECTORY_ENTRY_BASERELOC, IMAGE_DIRECTORY_ENTRY_DEBUG, IMAGE_DIRECTORY_ENTRY_COPYRIGHT, // IMAGE_DIRECTORY_ENTRY_ARCHITECTURE IMAGE_DIRECTORY_ENTRY_GLOBALPTR, IMAGE_DIRECTORY_ENTRY_TLS, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT, IMAGE_DIRECTORY_ENTRY_IAT, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT, IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTION, };
Agora temos o bloco de código _IMAGE_SECTION_HEADER onde a quantidade é definida em _IMAGE_FILE_HEADER.NumberOfSections
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // IMAGE_SIZEOF_SHORT_NAME 8 union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;
Name – Nome da seção tendo no máximo 8 caracteres
Misc.PhysicalAddress, Misc.VirtualSize – dividem o mesmo valor, sendo o endereço físico ou o tamanho virtual.
VirtualAddress – endereço virtual
SizeOfRawData – tamanho
PointerToRawData – ponteiro do inicio do arquivo até os dados da seção
PointerToRelocations – ponteiro para remanejamento para arquivos objetos
PointerToLinenumbers – ponteiro para o número de linhas para arquivos objetos
NumberOfRelocations – número de remanejamentos para arquivos objetos
NumberOfLinenumbers – quantidade de números de linhas para arquivos objetos;
Characteristics – características que descrevem como a memória da seção deve ser trata
Estrutura dos DataDirectory começando pelo IMAGE_DIRECTORY_ENTRY_EXPORT
struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; DWORD AddressOfNames; DWORD AddressOfNameOrdinals; } IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;
Characteristics – Características de exportação. Atualmente não são usadas
TimeDateStamp – Data/hora em que as exportações foram criadas
MajorVersion – número de versão maior das exportações, não utilizado e setado como 0
MinorVersion – número de versão menor das exportações, não utilizado e setado como 0
Name – endereço relativo para o nome associado as exportações.
Base – valor ordinal inicial a ser utilizado para exportação do executável.
NumberOfFunctions – número de funções na tabela de exportação
NumberOfNames – número de nomes na tabela de exportação, esse valor vai ser sempre menor ou igual ao NumberOfFunctions
AddressOfFunctions – endereço relativo da tabela de exportação em uma matriz, cada índice diferente de 0 corresponde a um símbolo exportado.
AddressOfNames – endereço relativo da tabela de exportação nomes em uma matriz, cada índice corresponde a um símbolo exportado pelo nome.
AddressOfNameOrdinals - endereço relativo da tabela ordinal de exportação. Essa tabela é um conjunto de palavras.
Estrutura IMAGE_IMPORT_DESCRIPTOR
typedef struct _IMAGE_IMPORT_DESCRIPTOR { _ANONYMOUS_UNION union { DWORD Characteristics; PIMAGE_THUNK_DATA OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; PIMAGE_THUNK_DATA FirstThunk; } IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
OriginalFirstThunk – esse campo é mal nomeado, ele contém um endereço relativo da tabela de nomes de importação. Isto é uma matriz de IMAGE_THUNK_DATA. Esse campo é 0 para indicar o final da matriz.
TimeDateStamp - recebe o valor 0 caso o executável não esteja vinculado a DLL importada. Quando tem a ligação é definido a data/hora quando a ligação ocorreu.
ForwarderChain – este é o índice da primeira API encaminhada. Definido como -1 se não tiver encaminhadores.
Name – endereço relativo para o nome da dll importada.
FirstThunk - endereço relativo a tabela de endereço de importação. É uma matriz de estruturas IMAGE_THUNK_DATA.
Estrutura IMAGE_THUNK_DATA
typedef struct _IMAGE_THUNK_DATA { union { LPBYTE ForwarderString; PDWORD Function; DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; } u1; } IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;
u1.ForwarderString – endereço relativo para um texto transitário
u1.Function – endereço da função importada
u1.Ordinal – valor ordinal da função importada
u1.AddressOfData – endereço relativo para um IMAGE_IMPORT_BY_NAME com o nome da função importada
Estrutura IMAGE_IMPORT_BY_NAME
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
Hint – Dica para o carregador com o ordinal
Name – Nome da função importada
Estrutura IMAGE_RESOURCE_DIRECTORY
typedef struct _IMAGE_RESOURCE_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; WORD NumberOfNamedEntries; WORD NumberOfIdEntries; } IMAGE_RESOURCE_DIRECTORY,*PIMAGE_RESOURCE_DIRECTORY;
Characteristics – Não usado
TimeDateStamp – Não usado
MajorVersion – Não usado
MinorVersion – Não usado
NumberOfNamedEntries – Número de entradas com nome
NumberOfIdEntries – Número de entrada com id
Estrutura IMAGE_DEBUG_DIRECTORY
typedef struct _IMAGE_DEBUG_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Type; DWORD SizeOfData; DWORD AddressOfRawData; DWORD PointerToRawData; } IMAGE_DEBUG_DIRECTORY,*PIMAGE_DEBUG_DIRECTORY;
Characteristics – Não usado e setado como 0
TimeDateStamp – Data/hora desta informação de depuração
MajorVersion - A versão maior desta informação de depuração. Não usado
MinorVersion – a versão mínima desta informação de depuração. Não usado.
Type - O tipo de informação de depuração. Os tipos são IMAGE_DEBUG_TYPE_COFF, IMAGE_DEBUG_TYPE_CODEVIEW, MAGE_DEBUG_TYPE_FPO, IMAGE_DEBUG_TYPE_MISC, IMAGE_DEBUG_TYPE_OMAP_TO_SRC, IMAGE_DEBUG_TYPE_OMAP_FROM_SRC, IMAGE_DEBUG_TYPE_BORLAND
SizeOfData - O tamanho dos dados de depuração neste arquivo. Não inclui o tamanho dos arquivos de depuração externo como .PDBs
AddressOfRawData – endereço relativo dos dados de depuração, quando mapeado na memória. Setado como 0 caso não seja mapeado.
PointerToRawData – Ponteiro para o dado de depuração. Não é endereço relativo.
Estrutura _IMAGE_TLS_DIRECTORY, IMAGE_TLS_DIRECTORY32
typedef struct _IMAGE_TLS_DIRECTORY32 { DWORD StartAddressOfRawData; DWORD EndAddressOfRawData; DWORD AddressOfIndex; DWORD AddressOfCallBacks; DWORD SizeOfZeroFill; DWORD Characteristics; } IMAGE_TLS_DIRECTORY32,*PIMAGE_TLS_DIRECTORY32;
StartAddressOfRawData – endereço inicial de um intervalo de memória usado para iniciar novos segmentos TLS na memória
EndAddressOfRawData – endereço final do intervalo na memória usada para inciar novos segmentos TLS na memória.
AddressOfIndex – endereço do índice que localiza o segmento de dados local. Quando um executável é trazido pra memória e uma seção .tls está presente, o carregador aloca um controlador TLS via TlsAlloc. Isto armazena o endereço fornecido nesse campo.
AddressOfCallBacks - endereço de uma matriz de ponteiros funções PIMAGE_TLS_CALLBACK. Quando um thread é criado ou destruído, cada função na lista é chamada. O final da lista é indicado por um ponteiro nulo. Em executáveis normais em visual c++ essa lista é vazia
SizeOfZeroFill - tamanho em bytes dos dados de inicialização, além dos dados iniciados serem delimitados pelos campos StartAddressOfRawData e EndAddressOfRawData. Todos os dados de threads iniciados depois desse intervalo é iniciado em 0.
Characteristics – Reservado, definido como 0
Estrutura IMAGE_DELAY_IMPORT_DESCRIPTOR
typedef struct _IMAGE_DELAY_IMPORT_DESCRIPTOR { DWORD grAttrs; DWORD szName; DWORD phmod; DWORD pIAT; DWORD pINT; DWORD pBoundIAT; DWORD pUnloadIAT; DWORD dwTimeStamp; } IMAGE_DELAY_IMPORT_DESCRIPTOR, *LPIMAGE_DELAY_IMPORT_DESCRIPTOR;
grAttrs - Os atributos para essa estrutura. Atualmente, a única bandeira é definida dlattrRva (1) indicando que os campos de endereço na estrutura deve ser tratado como endereços relativos, em vez de endereços virtuais.
szName – endereço relativo com o nome da dll importada. esse valor é passado para o LoadLibrary
phmod – endereço relativo para uma localização na memória para um HMODULE. Quando a dll é trazida para a memória ela é armazenada a partir do HMODULE.
pIAT – endereço relativo para a tabela de endereços de importação para essa dll. Este é o mesmo formato de um IAT regular
pINT – endereço relativo para o nome da tabela de importação para essa dll. Este é o mesmo formato como um INT regular
pBoundIAT – endereço relativo do limite IAT opcional. um endereço relativo a uma cópia encadeada de uma tabela de endereços de importação para essa dll. Este é o mesmo formato de um IAT regular. Atualmente, está cópia do IAT não é realmente ligado, mas esse recurso pode ser adicionado em futuras versões do programa BIND.
pUnloadIAT – endereço relativo de uma cópia opcional o IAT original. Um endereço relativo para uma cópia não ligada de uma tabela de endereços de importação para essa DLL. Este é o mesmo formato de um IAT regular. Atualmente é definido como 0
dwTimeStamp – Data/hora que a dll foi importada. Normalmente definido como 0.
Referências
PE Format (PDF) 373kb
An In-Depth Look into the Win32 Portable Executable File Format Part 1 - http://msdn.microsoft.com/en-us/magazine/bb985996.aspx
An In-Depth Look into the Win32 Portable Executable File Format Part 2 - http://msdn.microsoft.com/en-us/magazine/cc301808.aspx
http://msdn.microsoft.com/en-us/magazine/ms809762.aspx