Caríssimos leitores, neste artigo segue um esclarecimento sobre a estrutura e o funcionamento de um ficheiro executável (.exe). Estes têm a especial caraterística de apenas correr em sistemas operativos provenientes da Microsoft, desde o Windows 95 até à versão mais atual.
É natural que todos os utilizadores do Windows já tenham ouvido falar deste tipo de ficheiros, pois ele contém o código compilado do programa.
Por norma, os ficheiros executáveis sofrem também a denominação de PE Files, em português significa “Ficheiro Executavel Portatil“. Esta denominação é oriunda dos primórdios do Windows onde decidiram criar um formato binário capaz de correr em qualquer outra versão do Windows.
O grupo de ficheiros PE não é restrito apenas a .exe (executáveis), engloba por exemplo bibliotecas de linkagem (DLL), componentes ActiveX (OCX), entre outros tipos.
Em seguida, vamos dar uma maior atenção aos ficheiros EXE, pois além de serem os mais “famosos”, são os que levam o formato PE da forma mais abrangente possível.
Ferramentas de Trabalho para o Artigo
-Ficheiro de Teste (teste.exe).
Para o presente artigo foi criado um ficheiro executável, da forma mais pura possível. O seu código vem a seguir.
Este foi compilado usando o DevC++.
– WinHex
Um editor Hexadécimal. Este serve para identificar as classes do ficheiro de teste (teste.exe).
-LordPE
Uma aplicação para desmembrar executáveis. É possível explorar o formato dos ficheiros de uma forma fácil e simples.
Estrututura e Funcionamento
Um ficheiro PE possuí uma estrutura um tanto complicada e complexa, mas ao mesmo tempo organizada, o que facilita em muito a nossa vida.
Um ficheiro é composto pelas seguintes secções:
1. Cabeçalho DOS.
2. Cabeçalho Windows.
3. Tabela de Secções.
4. Secção 1.
5. Secção 2.
6. Secção N.. .
1. O cabeçalho DOS não tem utilidade prática para o sistema operativo Windows, este apenas tem a missão de avisar o utilizador que não pode ser visualizado/utilizado em modo de texto.
Observando com atenção a imagem acima, é possível verificar que o seu conteúdo é ilegível.
2. Por outro lado, já o cabeçalho Windows é de extrema importância. Neste estão embebidas todas as informações básicas e necessárias para que a aplicação / ficheiro funcione, como o número de secções, tamanho de cada secção e inicio das mesmas, onde iniciar a execução do código, entre outras dezenas de configurações.
3..6. O ficheiro é dividido em secções, que variam de acordo com o compilador usado, e que podem ser modificadas pelo utilizador. Cada secção é responsável por uma determinada caraterística no PE. As informações referentes a cada uma das secções estão armazenadas na “Tabela de Secções”.
Em seguida são apresentadas as secções mais comuns (e oficiais) de um binário para Win32.
a) Secção de código – Code Section ( .text ou .code ).
b) Secção de recursos – Resource Section ( .rsrc ).
c) Secção de dados – Data Section ( .data ).
d) Secção de exportação – Export data section ( .edata ).
e) Secção de importação – Import data section ( .idata ).
f) Informações de debug – Debug information ( .debug ).
Mais tarde será visto o que comporta cada uma destas secções.
Uma das caraterísticas sobre os ficheiros PE é que estes são armazenados em memória da mesma forma que ficam no disco, mantendo a estrutura do ficheiro praticamente idêntica nos dois casos.
Quando é feito o pedido de execução de um PE, o Windows Loader (parte do kernel do sistema operativo Windows responsável por iniciar e organizar o binário em memória) analisa o cabeçalho do PE. Feito isto, ele tem permissões e informações para copiar o executável do disco para a memória RAM, exatamente da forma que se encontra no disco. O Loader apenas necessita fazer alguns ajustes.
Estes ajustes são necessários devido à forma como o sistema operativo Windows gere a memória, fazendo uso de uma memória virtual paginada. Quando as secções são carregadas para a memória, ele alinha cada uma das secções para caber em páginas de 4KB. É como que a RAM fosse dividida em diversos pedaços de 4KB e criasse um índice de cada bloco.
Exemplo:
Supondo que é desejado alocar 5KB de dados na memória, neste caso o Windows (o Loader) verifica se existem páginas livres na memória para alocar os dados. Caso exista, ele vai alocar os primeiros 4KB em uma página, e os outros 1KB restantes na página seguinte. Nesta última, irão sobrar 3KB livres, que ficam inutilizáveis por outras aplicações. A seguinte imagem demonstra a situação.
Por sua vez o conceito por trás da memória virtual é que ao invés de deixar o software controlar e gerir diretamente a memória, o programa invoca o gestor do Windows, este consulta e analisa os pedidos da RAM. Todo este processo dinamiza e aumenta a segurança geral do sistema.
Uma das grandes vantagens é a possibilidade de criar diversos blocos de endereçamento, que consiste em restringir o acesso a determinado bloco da memória somente à aplicação legítima (aquela que o originou, isto é, a aplicação que deu origem à criação do mesmo). Através desta forma de acesso é possível evitar que um software corrompa a memória usada por outra aplicação.
Análise técnica do PE
-Cabeçalho DOS
O ficheiro PE começa com um cabeçalho DOS que ocupa os primeiros 64 bytes do ficheiro. A função deste cabeçalho é verificar se o executável (.exe) é ou não um ficheiro válido, e se este pode ser executado via MS-DOS ou no sistema operativo Windows.
A única função que esta função faz é exibir uma mensagem:
“This program must be run under Microsoft Windows”
Esta mensagem é armazenada logo após o cabeçalho DOS, numa área chamada “DOS Stub“. Esta tem como função o armazenamento dos dados que possam ser usados na execução do ficheiro. É nesta área que ficam também as instruções para imprimir o texto destacado acima.
Em seguida, será adicionada a estrutura oficial desse cabeçalho, que é usada pelos programadores.
IMAGE_DOS_HEADER STRUCT
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 WORD 4 dup(?)
e_oemid WORD ?
e_oeminfo WORD ?
e_res2 WORD 10 dup(?)
e_lfanew DWORD ?
IMAGE_DOS_HEADER ENDS
É possível entender que existem diversos itens com tamanhos WORD e DWORD, que se forem somados “fecham” os 64 byes iniciais do cabeçalho. Dos nomes anteriores destacam-se dois, os mais relevantes.
1. e_magic : É um valor de 2 bytes (WORD) que identifica um executável do DOS. Neles fica armazenada a sigla “MZ” (Mark Zbikowsky um dos idealizadores do MS-DOS). Esta sigla é imprescindível aquando a execução de um ficheiro (.exe), caso o Windows Loader não a detete o ficheiro deixa de ser reconhecido.
2. e_Ifanew : Esta armazena o offset (posição) no ficheiro onde está localizado o cabeçalho WIN.
Na imagem acima são notórios os dados discutidos anteriormente. Os dois primeiros bytes (4D5A) compõem o “e_magic“, contendo a siga MZ (valores ASCII para 4D e 5A). Já no final do cabeçalho DOS temos o “e_Ifanew“, que indica o local no ficheiro onde começa o cabeçalho PE.
Nota: É favor tomar nota que no ficheiro os bytes estão sempre na ordem inversa. Isto é, 80 00 00 00 na verdade representa 00 00 00 80.
-Cabeçalho Windows
O cabeçalho Windows (PE) , contém as informações cruciais para a aplicação. Nele estão presentes todas as caraterísticas do binário. Este é composto por um conjunto de estruturas, que variam de tamanho conforme a complexidade da aplicação e o número de secções nele armazenadas.
A primeira dessas estruturas é o cabeçalho NT.
IMAGE_NT_HEADERS STRUCT
Signature DWORD ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS
Como se pode perceber, ele é composto por três itens. O primeiro (Signature) possuí a mesma função do “e_magic“. Este apenas identifica o cabeçalho NT, e deve ser composto pela sigla PE, seguido por dois bytes nulos, fechando os 4 bytes da DWORD.
É possível verificar na imagem acima os 4 bytes que representam o cabeçalho PE, nomeadamente os 2 últimos bytes que estão a nulo.
Em seguida existe o “FileHeader“, ocupa os próximos 20 bytes do cabeçalho NT, tendo informações sobre a estrutura física do ficheiro executável. Abaixo é apresentada a estrutura.
IMAGE_FILE_HEADER STRUCT
Machine WORD ?
NumberOfSections WORD ?
TimeDateStamp DWORD ?
PointerToSymbolTable DWORD ?
NumberOfSymbols DWORD ?
SizeOfOptionalHeader WORD ?
Characteristics WORD ?
IMAGE_FILE_HEADER ENDS
Desta estrutura, os dados mais importantes são:
1. NumberOfSections: Indica o número de secções do ficheiro.
2. Characteristics: Informa se o ficheiro se trata de um .exe, .dll ou .ocx.
Voltando ao cabeçalho NT, temos por último a outra estrutura, apelidada de “OptionalHeader“. Apesar do nome dela ser curioso, ela é mesmo obrigatória. Esta estrutura possuí um tamanho de 224 bytes, sendo que os últimos 128 são reservados para o directório de dados.
É a maior estrutura, contendo também o maior número de valores. Em seguida é apresentada.
IMAGE_OPTIONAL_HEADER32 STRUCT
Magic WORD ?
MajorLinkerVersion BYTE ?
MinorLinkerVersion BYTE ?
SizeOfCode DWORD ?
SizeOfInitializedData DWORD ?
SizeOfUninitializedData DWORD ?
AddressOfEntryPoint DWORD ?
BaseOfCode DWORD ?
BaseOfData DWORD ?
ImageBase DWORD ?
SectionAlignment DWORD ?
FileAlignment DWORD ?
MajorOperatingSystemVersion WORD ?
MinorOperatingSystemVersion WORD ?
MajorImageVersion WORD ?
MinorImageVersion WORD ?
MajorSubsystemVersion WORD ?
MinorSubsystemVersion WORD ?
Win32VersionValue DWORD ?
SizeOfImage DWORD ?
SizeOfHeaders DWORD ?
CheckSum DWORD ?
Subsystem WORD ?
DllCharacteristics WORD ?
SizeOfStackReserve DWORD ?
SizeOfStackCommit DWORD ?
SizeOfHeapReserve DWORD ?
SizeOfHeapCommit DWORD ?
LoaderFlags DWORD ?
NumberOfRvaAndSizes DWORD ?
DataDirectory IMAGE_DATA_DIRECTORY
IMAGE_OPTIONAL_HEADER32 ENDS
Apesar do nome das variáveis falarem quase por si, vou explicar de uma forma mais detalhada as mais importantes, como fiz anteriormente.
1. AddressOfEntryPoint: Indica o endereço relativo (RVA – Relative Virtual Address) da primeira instrução a ser executada pelo ficheiro, assim que carregado na memória.
2. ImageBase: É a posição no espaço relativo da memória (restrita à aplicação) que o Windows carregará. Na maior parte dos casos é usado o VA (Virtual Address, endereço relativo) 400000h.
3. SectionAligment: É o alinhamento de cada uma das secções do executável na memória. Como mencionado no início do artigo, normalmente usa-se 4KB, logo isto corresponde a 4096 bytes. Por sua vez o valor do SectionAligment costuma ser 1000h (1000 em hexadecimal representa o valor 4096 no sistema decimal).
4. FileAligment: Semelhante ao anterior, mas representa o alinhamento das secções no ficheiro em disco, não na memória. Por sua vez, em disco não são armazenadas em blocos de 4KB mas sim de 512 bytes, o que daria um valor de 200h em hexadecimal.
5. SizeOfImage: Tamanho total do ficheiro PE após ser carregado na memória, incluindo os espaços vazios deixados pelo SectionAligment.
6. DataDirectory: Corresponde a 16 estruturas do tipo IMAGE_DATA_DIRECTORY. Estas contêm informações referentes às secções dentro do executável, como a tabela de Imports/Exports, Code, Data, etc.
Em seguida uma imagem do WinHex que ilustra o cabeçalho WIN (PE Header).

Como se pode observar estão divididas as partes do cabeçalho.
#Vermelho : Signature.
#Azul: File Header.
#Preto : Optional Header.
#Amarelo: Data Directory. Podendo tornar este processo quase automático é possível fazer a análise deste tipo de cabeçalhos em programas específicos, como é o caso do PeiD, LordPE ou até mesmo um debugger, como é o caso do OllyDbg.
(Consultar o separador “Tools” na página).
Usando o LordPE, como mencionado no inicio do artigo é possível obter todos o cabeçalho de uma forma automatizada.
Para finalizar o cabeçalho Windows, precisamos falar sobre o IMAGE_DATA_DIRECTORY. Como mencionado, ele compõe os últimos 128 bytes do PE Header, sendo uma estrutura importante contendo o endereço (RVA) e o tamanho dos directórios do executável.
IMAGE_DATA_DIRECTORY STRUCT
VirtualAddress DWORD ?
ISize DWORD ?
IMAGE_DATA_DIRECTORY ENDS
É possível verificar que se trata apenas de dois valores DWORD (cada um com 4 bytes, totalizando 8 por estrutura). Esta estrutura é usada pelos 16 directórios de dados, se são apresentados em seguida.
IMAGE_DIRECTORY_ENTRY_EXPORT equ 0
IMAGE_DIRECTORY_ENTRY_IMPORT equ 1
IMAGE_DIRECTORY_ENTRY_RESOURCE equ 2
IMAGE_DIRECTORY_ENTRY_EXCEPTION equ 3
IMAGE_DIRECTORY_ENTRY_SECURITY equ 4
IMAGE_DIRECTORY_ENTRY_BASERELOC equ 5
IMAGE_DIRECTORY_ENTRY_DEBUG equ 6
IMAGE_DIRECTORY_ENTRY_COPYRIGHT equ 7
IMAGE_DIRECTORY_ENTRY_GLOBALPTR equ 8
IMAGE_DIRECTORY_ENTRY_TLS equ 9
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG equ 10
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT equ 11
IMAGE_DIRECTORY_ENTRY_IAT equ 12
Para cada umas destas entradas existe uma estrutura do tipo IMAGE_DATA_DIRECTORY. Existem 16 entradas de directórios, e cada uma delas possuí 8 bytes, totalizando os 128 bytes finais do cabeçalho WIN. Ver na imagem do WinHex a marcação a amarelo.
-Tabela de Secções
A tabela de secções funciona de forma muito semalhante ao IMAGE_DATA_DIRECTORY. Nesta tabela estão contidas diversas informações referentes a cada uma das secções presente no executável. A quantidade de itens na tabela varia com o número de secções do executável. Essa informação poderá ser vista no NumberOfSections do cabeçalho WIN, neste caso 5 (ver na imagem do LordPE).
IMAGE_SECTION_HEADER STRUCT
Name1 BYTE IMAGE_SIZEOF_SHORT_NAME dup(?)
union Misc
PhysicalAddress DWORD ?
VirtualSize DWORD ?
ends
VirtualAddress DWORD ?
SizeOfRawData DWORD ?
PointerToRawData DWORD ?
PointerToRelocations DWORD ?
PointerToLinenumbers DWORD ?
NumberOfRelocations WORD ?
NumberOfLinenumbers WORD ?
Characteristics DWORD ?
IMAGE_SECTION_HEADER ENDS
IMAGE_SIZEOF_SHORT_NAME equ 8
1. Name1: Nome da secção, apenas serve para identificação, não tem efeito real sobre o executável.
2. VirtualAddress: RVA do início da sessão. O valor aqui contido é somado com o valor ImageBase do WIN para obter o endereço real da secção.
3. SizeOfRawData– Tamanho da secção em disco.
4. PointerToRawData: Posição da secção dentro do ficheiro (não na memória). Este valor fornece diretamente a posição da secção dentro do ficheiro, podendo ser facilmente encontrada num editor hexadecimal.
5. Characteristics: São as caraterísticas das secções.
As estruturas oficiais de um ficheiro PE são 4:
CODE (.text) – Contém as instruções e o código do programa.
RDATA(.rdata) – Dados gerais (incluindo tabela de secções).
DATA (.data) – Variáveis inicializadas.
RSRC(.rsrc) – Resources (textos e disposição dos itens na janela).
Verificando as secções no LordPE.
– Secções
Como dito, o PE pode ter bastantes secções. Das referidas acima não temos a RSRC, porque o executável não faz uso do ambiente gráfico GUI (janelas).
1. Secção de código (code/text)
Dentro desta secção fica armazenado o código compilado, contendo todas as instruções em binário para o funcionamento do programa. Qualquer alteração feita irá resultar numa modificação do ficheiro. É nesta secção que existem modificações por forma a facilitar o crack de programas e software.
2. Secção de dados (data)
Esta secção pode ser subdividida em três secções:
i) BSS : Contém todas as variáveis não inicializadas (sem um valor definido).
ii) RDATA: Dados apenas de leitura. Podem ser strings, constantes ou até mesmo dados da Import Table.
iii) DATA: Todas as outras variáveis que não encaixam em nenhuma das outras secções.
3. Secção de recursos (rsrc)
Esta é usada para armazenar qualquer tipo de dados dentro da aplicação, como por exemplo, ícones, imagens, disposição dos objectos na janela, menus, etc.
4. Secção de exportação (edata)
Armazena o diretório de exportação, contendo informações sobre os nomes e endereços das funções contidas em uma DLL. É uma forma também de camuflar as aplicações e enganar os anti-vírus em certas situações.
Os ficheiros DLL dão a possibilidade de optimização de código e não redundância, pois permitem obter funções para usar vezes sem contas em diversas aplicações.
5. Secção de importação (idata)
Esta secção tem o objectivo de criar uma base de dados de todas as funções usadas por um executável.
6. Secção de Debug (debug)
Esta secção está presente normalmente nas compilações de ficheiros, na fase de desenvolvimento. Porém, contém dados imensamente úteis aos programadores.
——————————————————————————
Informações Úteis:
O sistema operativo Windows trabalha com endereçamento virtual de memória. Isto quer dizer que as aplicações não trabalham com endereços absolutos baseados no ficheiro, mas sim na memória. É possível então citar três formas de endereçamento: Offset, VA e RVA.
1. Offset – RawOffset
Indica o posicionamento de algo dentro do ficheiro.
Por exemplo: PointerToRawData na tabela de secções trabalha com offsets, pois indica em qual byte (e não endereço de memória) no ficheiro executável se encontra determinada secção.
2. VA – Virtual Address
Endereço virtual absoluto na memória. É o endereço criado para a memória alocada para a aplicação e não para toda a memória, digamos que é o bloco disponibilizado pelo sistema operativo.
3. RVA – Relative Virtual Address
É o endereço relativo contado a partir do início do endereçamento de memória destinado à aplicação.
Tendo estas diferenciações, é possível formar pequenas equações para um maior esclarecimento.
RVA = VA – ImageBase
VA = RVA + ImageBase
Vamos analisar um exemplo. Supondo que uma aplicação qualquer possuí um ImageBase com valor 400000h.
O VA relativo ao início do espaço de memória destinado à aplicação passa a ser 400000h. Supondo agora que a aplicação inicia a execução no RVA 1000h.
Pela formula acima, podemos descobrir que o VA no início da execução do programa está no endereço 401000h (RVA + ImageBase = 400000h + 1000h).
Espero que este tutorial tenha sido claro e objectivo. É bastante importante a compreensão deste tipo de ficheiros PE, pois é sobre eles que iremos trabalhar no blog na secção de Engenharia Reversa.
Uma das vantagens de conhecer e dominar estes ficheiros são os skills que se adquirem por forma a “personalizar” o seu conteúdo, dando origem aos chamados cracks.
Qualquer dúvida, comente.
Boa Continuação!
One Reply to “Os segredos de um .EXE, PE File (Portable Executable)”