A Aldeia Numaboa ancestral ainda está disponível para visitação. É a versão mais antiga da Aldeia que eu não quis simplesmente descartar depois de mais de 10 milhões de pageviews. Como diz a Sirley, nossa cozinheira e filósofa de plantão: "Misericórdia, ai que dó!"

Se você tiver curiosidade, o endereço é numaboa.net.br.

Leia mais...

Informática Numaboa - Referências

O formato PE

Sab

11

Abr

2009


11:01

(10 votos, média 5.00 de 5) 


Cabeçalho do arquivo

Cabeçalho MZ do DOS
Fragmento (stub) do DOS
Cabeçalho do Arquivo
Cabeçalho Opcional
Diretório de Dados
Cabeçalhos das Seções
Seção 1
Seção 2
...
Seção n

Para obter o IMAGE_FILE_HEADER é preciso validar o "MZ" do cabeçalho do DOS (os primeiros 2 bytes), depois encontrar o membro 'e_lfanew' do cabeçalho do fragmento do DOS (offset 3C) e avançar o número indicado de bytes a partir do início do arquivo. Resumindo: na posição 003C encontra-se o 'e_lfanew' cujo valor, de 32 bits, nos indica a posição do início do cabeçalho do arquivo, ou seja, a IMAGE_NT_SIGNATURE, cujo valor é sempre 00004550 (veja na página anterior se tiver dúvidas).

Os componentes do cabeçalho do arquivo são os seguintes:

  • Assinatura do cabeçalho PE
  • Tipo de máquina previsto para rodar o executável
  • Número de seções
  • TimeDateStamp
  • Ponteiro para Tabela de Símbolos e Número de Símbolos
  • Tamanho do Cabeçalho Opcional
  • Características

Assinatura PE e tipo de máquina

O cabeçalho do arquivo, uma estrutura do tipo IMAGE_FILE_HEADER, tem como primeiro componente a assinatura PE e, logo a seguir, um dos seguintes elementos:

Nome #define em hexa Significado
IMAGE_FILE_MACHINE_I386 014C processador Intel 80386 ou melhor
  014D processador Intel 80486 ou melhor
  014E processador Intel Pentium ou melhor
  0160 E3000 (MIPS), big endian
IMAGE_FILE_MACHINE_R3000 0162 R3000 (MIPS), little endian
IMAGE_FILE_MACHINE_R4000 0166 R4000 (MIPS), little endian
IMAGE_FILE_MACHINE_R10000 0168 R10000 (MIPS), little endian
IMAGE_FILE_MACHINE_ALPHA 0184 DEC Alpha AXP
IMAGE_FILE_MACHINE_POWERPC 01F0 IBM Power PC, little endian
Offset 01234 56789 ABCDE F
...                    
0000 00C0 50 45 00 00 4C 01             

No nosso exemplo ficou determinado que a assinatura PE se encontra no offset 00C0, ocupando 4 bytes. Os dois bytes seguintes, nas posições 00C4 e 00C5, indicam que o executável foi previsto para rodar num processador Intel 80386 ou melhor: IMAGE_FILE_MACHINE_I386 de valor 014C (não esqueça de inverter os bytes, conforme explicado anteriormente).

Número de seções

O terceiro componente do cabeçalho do arquivo é um valor de 16 bits que indica o número de seções após o cabeçalho. As seções serão discutidas em detalhe adiante. No nosso exemplo, são 4 (04 00 invertido = 00 04).

Offset 01234 56789 ABCDE F
...                    
0000 00C0 50 45 00 00 4C 01 04 00          

Carimbo de data e hora

Logo após o número de seções encontra-se um valor de 32 bits, o "TimeDateStamp" (carimbo de data e hora), referente ao momento da criação do arquivo. Pode-se distinguir as diversas versões de um mesmo arquivo através deste valor, mesmo que o valor "oficial" da versão não tenha sido alterado. O formato deste carimbo não está documentado, exceto de que deveria ser único entre as versões do mesmo arquivo. Aparentemente corresponde ao número de segundos decorridos a partir de 1 de Janeiro de 1970 00:00:00, em UTC - o formato utilizado pela maioria dos compiladores C para time_t. Este carimbo é utilizado para a união de diretórios de importação, os quais serão abordados adiante.

info alguns linkers costumam atribuir valores absurdos ao carimbo, fora dos padrões time_t descritos acima.

Offset 01234 56789 ABCDE F
...                    
0000 00C0 50 45 00 00 4C 01 04 00 A3 77 55 3C     

Invertendo os bytes, o TimeDateStamp do nosso executável exemplo mostra um valor hexadecimal de 3C55 77A3 que corresponde a 1.012.234.147 decimal. Sabendo que este valor é o número de segundos decorridos a partir de 01.01.70 e que cada dia possui 86.400 segundos, podemos calcular que o binário em questão foi criado 11.716 dias (um pouquinho menos) após o início de 1970. Isto corresponde a mais ou menos 32 anos ... UAU! O executável foi criado no ano de 2002 (1970 + 32). Realmente, se você observar a data do arquivo exemplo (o tutNB03.exe), verá que o executável foi criado em 28 de Janeiro de 2002 às 14:12 horas.

PointerToSymbolTable e NumberOfSymbols

Os componentes "PointerToSymbolTable" (ponteiro para a tabela de símbolos) e "NumberOfSymbols" (número de símbolos), ambos de 32 bits, são utilizados para fins de debug. Não sei como decifrá-los e, geralmente, estão zerados.

Offset 01234 56789 ABCDE F
...                    
0000 00C0 50 45 00 00 4C 01 04 00 A3 77 55 3C 00000000
0000 00D0 00 000000               

SizeOfOptionalHeader

O "SizeOfOptionalHeader" (tamanho do cabeçalho opcional), de 16 bits, é simplesmente o tamanho do IMAGE_OPTIONAL_HEADER. Pode ser utilizado para verificar se a estrutura do arquivo PE está correta. No nosso exemplo, o tamanho do cabeçalho opcional é 224 (E000 invertido = 00E0 hexa = 224 decimal).

Offset 01234 56789 ABCDE F
...                    
0000 00C0 50 45 00 00 4C 01 04 00 A3 77 55 3C 00000000
0000 00D0 00 000000 E0 00             

Características

"Characteristics" (características) tem 16 bits e consiste num conjunto de flags, a maioria válida apenas para arquivos objeto e bibliotecas:

BitNomeSetado (valor 1)
0 IMAGE_FILE_RELOCS_STRIPPED Se não houver informações de remanejamento no arquivo. Isto se refere a informações de remanejamento por seção nas próprias seções. Não é utilizado em executáveis, os quais possuem informações de remanejamento no diretório "base relocation" descrito abaixo.
1 IMAGE_FILE_EXECUTABLE_IMAGE Se o arquivo for um executável, isto é, não é nem um arquivo objeto nem uma biblioteca. Esta flag também pode estar setada se a tentativa do linker em criar um executável tenha falhado por algum motivo - a imagem é mantida para facilitar uma linkagem incremental numa próxima tentativa.
2 IMAGE_FILE_LINE_NUMS_STRIPPED Se a informação do número de linha tiver sido eliminada. Não é usado em executáveis.
3 IMAGE_FILE_LOCAL_SYMS_STRIPPED Se não houver informação sobre símbolos locais. Não é usado em executáveis.
4 IMAGE_FILE_AGGRESIVE_WS_TRIM Se o sistema operacional deve cortar agressivamente o conjunto de trabalho do processo em andamento (a quantidade de RAM que o processo utiliza) através de um 'paging out'. Este bit deve ser setado apenas em aplicativos tipo demon que, na maior parte do tempo, ficam em estado de espera.
7 IMAGE_FILE_BYTES_REVERSED_LO Assim como seu par, o bit 15, está setado se o endian do arquivo não corresponder ao esperado pela máquina, de modo que precisa haver uma troca de bytes antes que uma leitura seja efetuada. Esta flag não é confiável para arquivos executáveis - o sistema operacional conta com os bytes devidamente ordenados nos executáveis.
8 IMAGE_FILE_32BIT_MACHINE Se for para rodar numa máquina de 32 bits.
9 IMAGE_FILE_DEBUG_STRIPPED Se não houver informação de debug no arquivo. Não é utilizado para executáveis. De acordo com outras informações ([6]), este bit é denominado de "fixo" e é setado se a imagem só puder rodar se estiver mapeada no endereço preferido (ou seja, não permite remanejamento).
10 IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP Se o aplicativo não puder rodar a partir de um meio removível, como um disquete ou CD-ROM. Neste caso, o sistema operacional é informado para copiar o arquivo para um arquivo temporário ("swapfile") e executá-lo a partir da cópia.
11 IMAGE_FILE_NET_RUN_FROM_SWAP Se o aplicativo não puder ser executado em rede. Neste caso, o sistema operacional é informado para copiar o arquivo para um arquivo temporário local ("swapfile") e executá-lo a partir da cópia.
12 IMAGE_FILE_SYSTEM Se o arquivo for um arquivo de sistema, por exemplo, um driver. Não é utilizado em executáveis. Também não é usado em todos os drivers NT.
13 IMAGE_FILE_DLL Se o arquivo for uma DLL.
14 IMAGE_FILE_UP_SYSTEM_ONLY Se o arquivo não tiver sido projetado para rodar em sistemas multiprocessados, ou seja, travará porque depende, de algum modo, de um único processador.
15 IMAGE_FILE_BYTES_REVERSED_HI Veja Bit 7.

Agora a coisa complicou um pouquinho. Precisamos "abrir" o hexadecimal de 16 bits destacado em rosa para sua notação binária e analisar cada um dos bits:

Offset 01234 56789 ABCDE F
...                    
0000 00C0 50 45 00 00 4C 01 04 00 A3 77 55 3C 00000000
0000 00D0 00 000000 E0 00 0F 01          

Transformando os valores hexadecimais em binários, temos o que precisamos. Observe que tanto os bytes quanto a numeração dos bits, para variar, precisa ser considerada no sentido inverso.

Hexa010F
Binário 0000 0001 0000 1111
Bits 15141312 111098 7654 3210

Agora, de posse dos bits, podemos analisar a informação transmitida por cada um deles:

bitbináriocaracterística
01Há informações de remanejamento
11É um arquivo executável
21A numeração de linhas foi eliminada
31Há informações sobre símbolos locais
40 
50 
60 
70O endian corresponde
81É para rodar numa máquina de 32 bits
90 
100 
110 
120 
130 
140 
150O endian corresponde

Algumas considerações sobre RVA

RVA é o endereço virtual relativo. O formato PE faz uso intensivo dos assim chamados RVAs. Um RVA ("relative virtual address") é utilizado para definir um endereço de MEMÓRIA caso se desconheça o endereço base ("base address"). É o valor que, adicionado ao endereço base, fornece o endereço linear.

O endereço base é o endereço a partir do qual a imagem PE é carregada na memória e pode mudar de uma execução para outra. Por exemplo, rodando duas instâncias de um mesmo executável, cada um deles terá um endereço base diferente, ou seja, foram mapeados para localizações diferentes na memória. Outro exemplo: imagine um executável mapeado para o endereço 0x400000 e que a execução se inicie no RVA 0x1560. O endereço de memória que contém o início efetivo será 0x401560. Se o executável tivesse sido mapeado a partir de 0x100000, o início de execução estaria em 0x101560.

As coisas começam a ficar um pouco mais complexas porque algumas partes do arquivo PE (as seções) não estão necessariamente alinhadas da mesma forma que a imagem mapeada. Por exemplo, as seções do arquivo em disco geralmente estão alinhadas em limites de 512 bytes, enquanto que a imagem mapeada na memória provavelmente esteja alinhada em limites de 4096 bytes. Veja 'SectionAlignment' e 'FileAlignment' adiante.

Deste modo, para localizar um bloco de informações num arquivo PE para um RVA específico, há a necessidade de se calcular os deslocamentos (offsets) como se o arquivo estivesse mapeado, porém saltar de acordo com os deslocamentos do arquivo. Como exemplo, suponha que você saiba que a execução se inicia no RVA 0x1560 e pretende desassemblar o código a partir deste ponto. Para achar o endereço no arquivo (disco), você precisará descobrir que as seções na RAM estão alinhadas em 4096 bytes, que a seção ".code" começa no RVA 0x1000 da RAM e tem o comprimento de 16384 bytes. Só então você pode determinar que o RVA 0x1560 está deslocado (offset) 0x560 nesta seção. Agora, verificando também que as seções do arquivo em disco estão alinhadas no limite de 512 bytes e que a seção ".code" começa no deslocamento 0x800, com alguns cálculos, torna-se possível localizar o início da execução em disco: 0x800 + 0x560 = 0xD60.

Desassemblando, você encontra uma variável no endereço linear 0x1051D0. O endereço linear será remanejado após o mapeamento do executável e, supostamente, o endereço preferencial será usado. Você consegue determinar que o endereço preferencial é 0x100000, portanto, o RVA é 0x51D0. Isto ocorre na seção de dados que começa no RVA 0x5000 e tem 2048 bytes de comprimento. Ela tem seu início no deslocamento 0x4800 do arquivo em disco, portanto a variável pode ser encontrada no deslocamento 0x4800 + 0x51D0 - 0x5000 = 0x49D0.

:anota: Exercícios propostos

Para este módulo, tente o seguinte:

  1. Continue fuçando os executáveis que você encontrar no seu HD. Qualquer um serve, contanto que esteja no formato PE. Localize o cabeçalho do arquivo e analise principalmente o campo número de seções. Vai ser útil logo adiante.
  2. Faça alguns exercícios de transformação de hexa para binário. Se estiver com preguiça, use a calculadora do windows na opção "científica" - as trasnformações estão a um clique de mouse. Caso você queira se aprofundar mais no assunto, dê uma olhada em Sistemas de Notação.
  3. Tente desenhar um mapa de memória e um mapa de disco para um executável imaginário. Tente entender o que esse negócio de RVA tem a ver com isso wink

Informações adicionais