MS-DOS

Não estranhe o nome deste cabeçalho. Ele é parte do que chamamos de stub do MS-DOS: um executável completo de MS-DOS presente no início de todo executável PE, para fins de retrocompatibilidade.

Sendo assim, todo executável PE começa com este cabeçalho, que é definido pela seguinte estrutura:

typedef struct {
    uint16_t e_magic;
    uint16_t e_cblp;
    uint16_t e_cp;
    uint16_t e_crlc;
    uint16_t e_cparhdr;
    uint16_t e_minalloc;
    uint16_t e_maxalloc;
    uint16_t e_ss;
    uint16_t e_sp;
    uint16_t e_csum;
    uint16_t e_ip;
    uint16_t e_cs;
    uint16_t e_lfarlc;
    uint16_t e_ovno;
    uint16_t e_res[4];
    uint16_t e_oemid;
    uint16_t e_oeminfo;
    uint16_t e_res2[10];
    uint32_t e_lfanew;
} IMAGE_DOS_HEADER;

Este cabeçalho possui 64 bytes de tamanho. Para chegar a esta conclusão basta somar o tamanho de cada campo, onde uint16_t é um tipo na linguagem C que define uma variável de 16 bits ou 2 bytes. Os seguintes campos variam deste tamanho:

  • uint16_t e_res[4] que é um array de 4 campos de 16 bits, totalizando em 64 bits ou 8 bytes.

  • uint16_t e_res2[10] que é um array de 10 campos de 16 bits, totalizando em 160 bits ou 20 bytes.

  • uint32_t e_lfanew que é um campo de 32 bits ou 4 bytes.

Os outros 16 campos possuem o tamanho de um uint16_t (16 bits ou 2 bytes). Então somando os tamanhos de todos os campos, temos 64 bytes.

Por ser um cabeçalho ainda presente no formato PE somente por questões de compatibilidade com o MS-DOS, não entraremos em muitos detalhes, mas estudaremos alguns de seus campos a seguir.

e_magic

Este campo de 2 bytes sempre contém os valores 0x4d e 0x5a, que são os caracteres 'M' e 'Z' na tabela ASCII. Portanto é comum verificar que todo arquivo executável do Windows que segue o formato PE começa com tais valores, que representam as iniciais de Mark Zbikowski, um dos criadores deste formato para o MS-DOS.

Podemos utilizar um visualizador hexadecimal como o hexdump no Linux para verificar tal informação. Vamos pedir, por exemplo, os primeiros 16 bytes de um arquivo putty.exe:

$ hd -n16 putty.exe
00000000  4d 5a 90 00 03 00 00 00  04 00 00 00 ff ff 00 00  |MZ..............|

Perceba os bytes 0x4d e 0x5a logo no início do arquivo.

O hexdump exibe um caractere de ponto (.) na terceira coluna quando o byte não está na faixa ASCII imprimível, ao contrário do wxHexEditor que exibe um caractere de espaço em branco.

e_lfanew

O próximo campo importante para nós é o e_lfanew, um campo de 4 bytes cujo valor é a posição no arquivo do que é conhecido por assinatura PE, uma sequência fixa dos seguintes 4 bytes: 50 45 00 00.

Como o cabeçalho do DOS possui um tamanho fixo, seus campos estão sempre numa posição fixa no arquivo. Isso se refere aos campos e não a seus valores que, naturalmente, podem variar de arquivo para arquivo. No caso do e_lfanew, se fizermos as contas, veremos que ele sempre estará na posição 0x3c (ou 60 em decimal), já que ele é o último campo de 4 bytes de um cabeçalho de 64 bytes.

Para ver o valor deste campo rapidamente podemos pedir ao hexdump que pule (opção -s de skip) então 0x3c bytes antes de começar a imprimir o conteúdo do arquivo em hexadecimal. No comando a seguir também limitamos a saída em 16 bytes com a opção -n (number):

$ hd -s 0x3c -n16 putty.exe
0000003c  f8 00 00 00 0e 1f ba 0e  00 b4 09 cd 21 b8 01 4c  |............!..L|

O número de 32 bits na posição 0x3c é então 0x000000f8 ou simplesmente 0xf8 (lembre-se do little endian). Este é então o endereço da assinatura PE, que consiste numa sequência dos seguintes 4 bytes: 0x50 0x45 0x00 0x00.

Perceba que os dois primeiros bytes na assinatura PE possuem representação ASCII justamente das letras 'P' e 'E' maiúsculas. Sendo assim, essa assinatura pode ser escrita como "PE\0\0", no estilo C string.

Logo após o cabeçalho há o código do programa que vai imprimir na tela uma mensagem de erro caso um usuário tente rodar este arquivo PE no MS-DOS. Normalmente o texto impresso na tela é:

This program cannot be run in DOS mode.

Depois disso o programa sai. Mas este pequeno programa de MS-DOS é adicionado pelo compilador (pelo linker mais especificamente) e seu conteúdo pode variar, pois não há um padrão rígido a ser seguido.

Exercício

Para por em prática a análise desta primeira parte do arquivo PE, abra o executável da calculadora do Windows (normalmente em C:\Windows\System32\calc.exe) no HxD.

Note que:

  • Logo no início do arquivo, há o número mágico "MZ".

  • Na posição 0x3c, ou seja, no campo e_lfanew, há o endereço da assinatura PE (0xd8 no caso deste executável).

  • Logo após os 4 bytes do campo e_lfanew, começa o código do programa de MS-DOS, no offset 0x40, com uma sequência de bytes que não fazem sentido para nós por enquanto (veja que o texto impresso na tela pelo DOS stub é todavia bem visível).

  • Finalmente, na posição 0xd8 encontra-se a assinatura PE\0\0. Aqui sim, começa o formato PE propriamente dito.

Last updated