Conhecemos bem os dez sÃmbolos latinos utilizados no sistema de numeração decimal: 0, 1, 2, 3, 4, 5, 6, 7, 8 e 9. Neste sistema, o sÃmbolo 0 (zero) é utilizado para descrever uma quantidade nula, enquanto o sÃmbolo 1 (um) descreve uma quantidade, o 2 (dois) duas quantidades e assim sucessivamente, até que atinjamos a quantidade máxima representável com apenas um dÃgito, que é 9 (nove). Para representar uma quantidade a mais que essa, a regra é: pegamos o sÃmbolo que representa uma quantidade e colocamos à sua direita o que representa uma quantidade nula formando, assim, o 10 (dez). O mesmo processo ocorre com este zero à direita, até que os dÃgitos "acabem" novamente e aà incrementamos o 1 da esquerda em uma unidade, até que chegamos ao 20. Estudos futuros definiram este conjunto como números naturais e adicionaram outros: números inteiros (que contemplam os negativos), fracionários, complexos, etc.
Mas este não é o único - nem é o primeiro - sistema para representação de quantidades. Ou seja, não é o único sistema de numeração possÃvel. Os computadores, diferente dos humanos, são máquinas elétricas. Sendo assim, a maneira mais fácil de números fluÃrem por eles seria com um sistema que pudesse ser interpretado a partir de dois estados: ligado e desligado.
O sistema binário surgiu há muito tempo e não vou arriscar precisar quando ou onde, mas em 1703 o alemão Leibniz publicou um trabalho refinado, com tradução para o inglês disponÃvel em http://www.leibniz-translations.com/binary.htm, baseado na dualidade taoÃsta chinesa do yin e yan a qual descrevia o sistema binário moderno com dois sÃmbolos: 0 (nulo) e 1 (uma unidade). Por ter somente dois sÃmbolos, ficou conhecido como sistema binário, ou de base 2. A contagem segue a regra: depois de 0 e 1, pega-se o sÃmbolo que representa uma unidade e se insere, à sua direita, o que representa nulo, formando o número que representa duas unidades neste sistema: 10.
Daà vem a piada nerd que diz haver apenas 10 tipos de pessoas no mundo: as que entendem linguagem binária e as que não entendem.
Assim sendo, se formos contar até dez unidades, teremos: 0, 1, 10, 11, 100, 101, 110, 111, 1000, 1001 e 1010.
Perceba que a lógica de organização dos sÃmbolos no sistema binário é a mesma do sistema de numeração decimal. No entanto, em binário, como o próprio nome sugere, só temos dois sÃmbolos disponÃveis para representar todas as quantidades.
Por utilizar dois sÃmbolos que são idênticos aos do sistema decimal, num contexto genérico, números binários são normalmente precedidos com 0b para não haver confusão. Então para expressar dez quantidades farÃamos 0b1010. Por exemplo, a seguinte linha na console do Python imprime o valor 10 na tela:
Ou em linguagem C:
Na prática, os códigos acima funcionam como conversores de binário para decimal.
Como o próprio nome sugere, o sistema octal possui oito sÃmbolos: 0, 1, 2, 3, 4, 5, 6 e 7. À esta altura já dá pra sacar que para representar oito quantidades em octal o número é 10. Nove é 11, dez é 12 e assim sucessivamente.
O sistema octal é utilizado para as permissões de arquivo pelo comando chmod nos sistemas baseados em Linux e BSD. Os números 1, 2 e 4 representam permissão de execução, escrita e leitura, respectivamente. Para combiná-las, basta somar seus números correspondentes. Sendo assim, uma permissão 7 significa que se pode-se tudo (leitura, escrita e execução) enquanto uma permissão 6 permite somente escrita e leitura. Tais números foram escolhidos para não haver confusão. Se fossem os números 1, 2 e 3 a permissão 3 poderia significar tanto ela mesma quanto 1+2 (execução + escrita). Usando 1, 2 e 4 não há brechas para dúvidas. ;)
Veja um exemplo em Python (lembre-se: abra o Python e estude junto agora):
Finalmente o queridinho hexa; o sistema de numeração que mais vamos utilizar durante todo o livro.
O hexadecimal apresenta várias vantagens sobre seus colegas, a começar pelo número de sÃmbolos: 16. São eles: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E e F. Os números que eles formam são normalmente prefixados com 0x, embora alguns programas utilizem um sufixo h. Por exemplo: 0x1234 ou 1234h.
Perceba que todos os sistemas apresentados até agora utilizam os mesmos sÃmbolos latinos. Isso é só para facilitar mesmo.
Aqui cabe uma tabela comparativa, só para exercitar:
Existem algumas propriedades interessantes quando relacionamos os diferentes sistemas de numeração vistos aqui. São elas:
Quanto mais sÃmbolos existem no sistema, menos dÃgitos utilizamos para representar grandes quantidades.
0xF é igual a 0b1111, assim como 0xFF equivale a 0b11111111 e 0xFE é o mesmo que 0b11111110.
0x10 é 16. Então, 0x20 é 32 e 0x40 é 64.
Por isso podemos dizer que 0xB0B0CA é 0b101100001011000011001010.
Em hexadecimal, zeros à esquerda depois do prefixo e letras maiúsculas ou minúsculas não importam. Veja no Python:
Falaremos bastante em endereços de memória no conteúdo de engenharia reversa e todos estão em hexadecimal, por isso é importante "pensar em hexa" daqui para frente. Mas não se preocupe, se precisar calcular algo, sempre poderá recorrer à calculadora ou ao Python.
Um bom exercÃcio para fixar este conteúdo é criar o seu sistema de numeração com sÃmbolos à sua escolha. Por exemplo, inventarei agora um sistema ternário chamado Lulip's que possui os seguintes sÃmbolos para representar zero, uma e duas quantidades respectivamente: @, # e $. Olha só como ficaria a comparação dele com o sistema decimal:
É importante que você perceba a lógica utilizada para contar no sistema Lulip's. Apesar de não ser um sistema que exista por aÃ, ele serve de base para você entender como qualquer valor, em qualquer sistema, pode ser convertido para outro.
3
3
3
11
4
4
4
100
5
5
5
101
6
6
6
110
7
7
7
111
8
8
10
1000
9
9
11
1001
A
10
12
1010
B
11
13
1011
C
12
14
1100
D
13
15
1101
E
14
16
1110
F
15
17
1111
Em hexadecimal, 9 + 1 é A, então 19 + 1 é 1A.
Na arquitetura x86-64, os endereços são de 64-bits, ou seja, 8 bytes. Ao analisar a pilha de memória, por exemplo, você vai perceber que tais endereços sempre terminam em zero ou em oito. Isto porque, em hexa, 8 + 8 = 10. Somando mais oito, teremos 18. Se somarmos 8 novamente, teremos 20 e assim sucessivamente.
Na conversão de hexadecimal para binário, cada dÃgito hexa pode ser compreendido como quatro dÃgitos binários. Para exemplificar, tomemos o número 0xB0B0CA. Separando cada dÃgito hexa e convertendo-o para binário, temos:
6
$@
7
$#
8
\$$
9
#@@
10
#@#
11
#@$
0
0
0
0
1
1
1
1
2
2
2
0
@
1
\#
2
$
3
#@
4
##
5
#$
10
>>> 0b1010printf("%d\n", 0b1010);>>> 0o12
10 B 0 B 0 C A
1011 0000 1011 0000 1100 1010>>> 0xa
10
>>> 0x0A
10
>>> 0x000000000000000000000a
10
>>> 0xA
10