Fundamentos de Engenharia Reversa
Apoie este trabalho
  • 🙌Apresentação
  • 👀Antes de Começar
  • 🥇Introdução
  • 🔢Números
    • Sistemas de Numeração
    • O Byte
    • Números Negativos
    • Cálculos com Binários
  • 🧵Cadeias de Texto
    • ASCII
    • Unicode
    • C Strings
  • 🗂️Arquivos
    • Formatos
  • 💼O formato PE
    • Cabeçalhos
      • MS-DOS
      • COFF
      • Opcional
      • Diretórios de Dados
      • Cabeçalhos das Seções
    • Seções
    • Import Table
    • Endereçamento
  • 🚗Execução de Programas
    • Executáveis
    • Bibliotecas
    • Processos
  • 🖼️Windows API
    • Caixas de Mensagens
    • Manipulação de Arquivos
    • Acesso ao Registro
  • ⚙️Assembly
    • Registradores
    • Instruções Básicas
    • Funções e Pilha
  • 🐞Depuração
    • O Debugger
    • Disassembly
    • Breakpoints
    • Manipulação do Fluxo
    • Patches
  • Apêndices
    • Tabela ASCII
    • Tabela ISO-8859-1/Latin-1
    • Exemplos de Código em Assembly
    • Funções da API do Windows
    • Ferramentas
    • Referências
  • Sobre o livro
    • 📝Registro de alterações
Powered by GitBook
On this page
  • UTF-8
  • UTF-16
  • Codepoints da ISO-8859-1 na UTF-16
  • UTF-32

Was this helpful?

Edit on GitHub
Export as PDF
  1. Cadeias de Texto

Unicode

À esta altura você já pode imaginar a dificuldade que programadores enfrentam em trabalhar com diferentes codificações de texto, mas existe um esforço chamado de Unicode mantido pelo Unicode Consortium que compreende várias codificações, que estudaremos a seguir. As strings neste formato são comumente chamadas de wide strings (largas, numa tradução livre).

UTF-8

O padrão UTF (Unicode Transformation Format) de 8 bits foi desenhado originalmente por Ken Thompson (o criador do Unix!) e Rob Pike (o criador da linguagem Go) para abranger todos os caracteres possíveis nos vários idiomas deste planeta.

Os primeiros 128 caracteres da tabela UTF-8 possuem exatamente os mesmos valores da tabela ASCII padrão e somente necessitam de 1 byte para serem representados. Chamamos estes números de codepoints. Os próximos caracteres utilizam 2 bytes e compreendem não só o alfabeto latino (como na ASCII estendida com codificação ISO-8859-1) mas também os caracteres gregos, árabes, hebraicos, dentre outros. Já para representar os caracteres de idiomas como o chinês e japonês, 3 bytes são necessários. Por fim, há os caracteres de antigos manuscritos, símbolos matemáticos e até emojis, que utilizam 4 bytes.

Concluímos que os caracteres UTF-8 variam de 1 a 4 bytes. Sendo assim, como ficaria o texto "mentebinária" numa sequência de bytes? Podemos ver novamente com o Python, mas dessa vez ao invés declarar um objeto do tipo bytes com aquele prefixo b, vamos converter um tipo str para bytes utilizando a função encode(). Isso é necessário porque queremos ver uma string UTF-8 e não ASCII:

>>> 'mentebinária'.encode('utf-8').hex(' ')
'6d 65 6e 74 65 62 69 6e c3 a1 72 69 61'

Como dito antes, os codepoints da tabela ASCII são os mesmos em UTF-8, mas o caractere 'á' (que não existe em ASCII puro) utiliza 2 bytes (no caso, C3 A1) para ser representado. Esta é uma string UTF-8 válida. Dizemos que seu tamanho é 11 porque ela contém 11 caracteres, mas em bytes seu tamanho é 12.

UTF-16

Também conhecido por UCS-2, este tipo de codificação é frequentemente encontrado em programas compilados para Windows, incluindo os escritos em .NET. É de extrema importância que você o conheça bem.

Representados em UTF-16, os caracteres da tabela ASCII possuem 2 bytes de tamanho, mesmo que não precisem. O byte adicional estará zerado. Vamos entender melhor com a ajuda do Python.

Primeiro, exibimos os bytes em hexa equivalentes de cada caractere da string:

>>> b'mente'.hex(' ')
'6d 65 6e 74 65'

Até aí nenhuma novidade, mas vejamos como essa string seria codificada em UTF-16:

>>> 'mente'.encode('utf-16').hex(' ')
'ff fe 6d 00 65 00 6e 00 74 00 65 00'

A primeira dupla de bytes é FF FE, mas de onde ela veio? Esta é a Byte Order Mark (BOM) ou Marca de Ordem de Byte, em português e define a ordem (ou endianness) dos bytes nos codepoints. Se for FF FE como neste caso, os bytes estão em little-endian, o que significa que o byte menos significativo está à esquerda. Em outras palavras, o número 0x0006d será representado como 6D 00. Se o bom fosse FE FF, então esse número seria representado como 00 6D.

Também é possível utilizar a codificação UTF-16-LE que já utiliza little-endian por padrão, sem precisar da BOM:

>>> 'mente'.encode('utf-16-le').hex(' ')
'6d 00 65 00 6e 00 74 00 65 00'

A codificação UTF-16-LE (lembre-se: sem BOM) é a utilizada pelo Visual Studio no Windows quando tipos WCHAR são usados, como nos argumentos das funções MessageBoxW() e CreateFileW(). Também é a codificação padrão para programas em .NET. Isto é importante de saber pois se você precisar alterar uma string UTF-16-LE durante a engenharia reversa, vai ter que respeitar essas regras.

Além da UTF-16-LE, temos a UTF-16-BE (Big Endian), onde os bytes estão em big-endian, ou seja, na ordem direta, com o byte mais significativo à esquerda:

>>> 'mente'.encode('utf-16-be').hex(' ')
'00 6d 00 65 00 6e 00 74 00 65'

Além disso, é importante ressaltar que em strings UTF-16 também há a possibilidade de caracteres de quatro bytes. Por exemplo, um emoji:

>>> '💚'.encode('utf-16-le').hex(' ')
'3d d8 9a dc'

Codepoints da ISO-8859-1 na UTF-16

Os números (codepoints) utilizados pela ISO-8859-1 para seus caracteres são também os números utilizados em strings UTF-16. No Windows, como já falado, o padrão é o UTF-16-LE. Para entender como isso funciona, observe primeiro os bytes da string "binária" na codificação ISO-8859-1:

>>> 'binária'.encode('iso-8859-1').hex(' ')
'62 69 6e e1 72 69 61'

Perceba que o byte referente ao "á" é o E1. Até aí nenhuma novidadade. Sabemos que é uma string ASCII estendida que usa a tabela ISO-8859-1, também conhecida por Latin-1. Agora, vejamos como ela fica em UTF-16-LE:

>>> 'binária'.encode('utf-16-le').hex(' ')
'62 00 69 00 6e 00 e1 00 72 00 69 00 61 00'

Nesse caso, "binária" é uma string UTF-16-LE sem BOM. Os bytes dos caracteres em si coincidem com os da ISO-8859-1. Doido né? Mas vamos em frente!

Perceba que o "á" em UTF-8 é C3 A1, mas em UTF-16 é E1 (precedido ou sucedido por zero), assim como na codificação ISO-8859-1.

UTF-32

Sendo pouco utilizado, este padrão utiliza 4 bytes para cada caractere. Vamos ver como fica a string "mb" em UTF-32 com BOM:

>>> 'mb'.encode('utf-32').hex(' ')
'ff fe 00 00 6d 00 00 00 62 00 00 00'

Perceba o BOM de quatro bytes ao invés de dois.

Agora em UTF-32-LE:

>>> 'mb'.encode('utf-32-le').hex(' ')
'6d 00 00 00 62 00 00 00'

E por fim em UTF-32-BE:

>>> 'mb'.encode('utf-32-be').hex(' ')
'00 00 00 6d 00 00 00 62'

É importante ressaltar que simplesmente dizer que uma string é Unicode não diz exatamente qual codificação ela está utilizando, fato que normalmente depende do sistema operacional, da pessoa que programou, do compilador utilizado, dentre outros fatores. Por exemplo, um programa feito em C no Windows e compilado com Visual Studio tem as wide strings em UTF-16-LE normalmente. Já no Linux, o tamanho do tipo wchar_t é de 32 bits, resultando em strings UTF-32.

Há muito mais sobre codificação de texto para ser dito, mas isso foge do escopo deste livro. Se desejar se aprofundar, basta consultar a documentação oficial dos grupos que especificam estes padrões. No entanto, cabe ressaltar que para a engenharia reversa, a prática de compilar programas e buscar como as strings são codificadas é a melhor escola.

PreviousASCIINextC Strings

Last updated 5 days ago

Was this helpful?

🧵