Os arquivos de texto, independentemente da codificação (ASCII, ISO-8859-1, UTF-8, etc) são tipos de arquivos bem simples. Começaremos agora a avaliar tipos de arquivos mais complexos. Acontece que para um programa salvar ou abrir um determinado tipo de arquivo, é essencial que este conheça seu formato. Tomemos por exemplo o formato GIF. Para inspecionar seu conteúdo, utilizaremos, no Windows, um editor hexadecimal gráfico chamado HxD.
Analise o seguinte arquivo GIF aberto no HxD:
O HxD é um editor hexadecimal. Para entender estes editores funcionam, vamos começar pelas colunas onde os bytes estão organizados.
Também chamado de offset no arquivo ou offset em disco, esta coluna exibe a posição do conteúdo no arquivo, em bytes. Na imagem, o primeiro byte (no offset 0) é o 0x47. O segundo é o 0x49 e assim por diante. Já o byte no offset 0x10 é o 0x06.
Identifique na imagem as afirmações a seguir:
O byte no offset 0x53 é o 0xc8.
O byte no offset 0x7c é 0xc0.
O byte no offset 0x90 é 0x6c.
A próxima coluna exibe os bytes em si. Sem segredos. Por serem editores hexadecimais, programas como o HxD exibem o conteúdo do arquivo em bytes hexadecimais separados por espaços, mas é importante lembrar que o conteúdo de um arquivo é uma sequência de bits em disco ou em outro dispositivo de armazenamento que, quando aberto num editor, tem seu conteúdo copiado para a memória. A maneira como estes bytes serão visualizados fica a cargo do programa que o abre. Por exemplo, se o editor exibisse os bytes em decimal, os primeiros dois bytes (0x47 e 0x49) seriam 71 e 73. Se fosse em binário, seria 1000111 e 1001001.
A esta altura o leitor já deve ter percebido que um número pode ser expresso de várias maneiras, no entanto, o sistema hexadecimal é bem otimizado para fins de engenheiria reversa.
Na terceira coluna o HxD nos apresenta a interpretação textual em ASCII de cada byte exibido na segunda coluna, ou seja, se o byte em questão estiver dentro da faixa de caracteres ASCII imprimíveis (de 0x20 à 0x7e), sua representação é exibida. Para valores fora desta faixa, o HxD imprime um ponto.
Há dezenas de outros editores hexadecimais disponíveis, inclusive alguns visualizadores de linha de comando. Vale consultar o apêndice Ferramentas e testar alguns. Se você tiver curiosidade de saber como funciona um visualizador hexadecimal, recomendo olhar o código do hdump, disponível em https://github.com/merces/hdump, um visualizador para linha de comando que implementei em C para funcionar em Windows, Linux e macOS.
De volta ao formato, é importante ressaltar que tanto quem programa o software que salva um determinado tipo de arquivo quanto quem programa o software que visualiza tal formato precisa conhecê-lo bem. Vejamos então como o formato GIF é definido.
Em geral, os formatos são definidos por campos (faixas de bytes) de tamanho fixo ou variável, que podem assumir determinados valores. Para entendê-los, precisamos da documentação deste formato (no caso, do GIF) que está disponível livremente na internet. Conforme sua especificação, o formato GIF segue, dentre outras, as seguintes regras:
Seguindo esta tabela fornecida por quem desenhou o formato GIF e olhando o conteúdo do arquivo de exemplo na imagem anterior, podemos verificar que o primeiro campo, de 6 bytes, casa exatamente com o que está definido no padrão. Os bytes são a sequência 0x47, 0x49, 0x46, 0x38, 0x39 e 0x61 que representam a sequência em ASCII GIF89a. É bem comum ao definir formatos de arquivo que o primeiro campo, normalmente chamado de cabeçalho (header) ou número mágico (magic number) admita como valor uma representação ASCII que dê alguma indicação de que tipo de arquivo se trata. Por exemplo, os tipos de arquivo ZIP possuem o magic number equivalente ao texto PK. Já o tipo de arquivo RAR começa com os bytes equivalentes ao texto Rar!. Não é uma regra, mas é comum.
No exemplo do formato GIF o tamanho do primeiro campo é de 6 bytes, mas nem todo magic number possui este tamanho. Na verdade, não há regra.
Logo após o primeiro campo, temos o segundo campo, que define a largura em pixels da imagem GIF, segundo sua documentação. Este campo possui 2 bytes e, na imagem de exemplo, são os bytes 0x30 e 0x00. Aqui cabe explicar um conceito valioso que é o endianess. Acontece que na arquitetura Intel os bytes de um número inteiro são armazenados de trás para frente (chamado de little endian). Sendo assim a leitura correta da largura em pixels deste GIF é 0x0030, ou simplesmente 0x30 (já que zero à esquerda não conta), que é 48 em decimal.
O próximo campo, também de 2 bytes, diz respeito a altura em pixels da imagem GIF e também possui o valor 0x30 (já lendo os bytes de trás para frente conforme explicado). É correto dizer então que esta é uma imagem de 48 x 48 pixels.
É por isso que alguns sistemas operacionais, com o GNU/Linux, não consideram a extensão de arquivo como sendo algo importante para definir seu tipo. Na verdade, o conteúdo do arquivo o define.
Não seguiremos com toda a interpretação do formato GIF pois este foge ao escopo de estudo de engenharia reversa, mas vamos seguir a mesma lógica para entender o formato de arquivos executáveis do sistema Windows, objeto de estudo do próximo capítulo.
Byte offset (posição no arquivo) | Tamanho do campo em bytes | Valor em hexadecimal | Descrição |
---|---|---|---|
0
6
47 49 46 38 39 61
Cabeçalho
6
2
<variável>
Largura em pixels
8
2
<variável>
Altura em pixels