Sobre Instruções e a Linguagem da Máquina
Um computador por padrão só aceita uma linguagem binária de 0 e 1, mas atualmente nós, inclusive os produtores de malware, escrevemos programas linguagens como Python e C, com termos em inglês. Como isso é possível?
Linguagem de Máquina
A CPU disponibiliza um conjunto finito bem específico de instruções que podem ser lidos e executados por ela. Essa linguagem é chamada de linguagem de máquina.
Um computador só entende lingaugem binária, a variação de tensão elétrica em seus componentes, bits. Portanto a linguagem de máquina em qualquer computador é sempre uma linguagem binária. Cada linguagem de máquina entretanto define sequências fixas de bits que podem significar alguma instrução para o computador. Uma instrução, como o nome já indica, é uma ordem que o computador entende e realiza. Chamamos de ISA (Instruction Set Architecture) o conjunto de instruções disponibilizados por um processador. A maioria dos computadores atualmente entendem instruções de 32 ou 64 bits.
Fazendo alusão a linguagem portuguesa, um computador de 32 bits não vai ver sentido em bits soltos, igual um brasileiro alfabetizado não vê sentido em letras soltas. Para fazer sentido, as letras precisam formar uma palavra e em computadores de 32 bits, só sequências exatas de 32 bits podem formar palavras. Inclusive, o termo palavra é de fato usado na computação com esse sentido. Computadores de 64 bits têm palavras de 64 bits.
Linguagem de Montagem
A linguagem de montagem é uma tradução literal da linguagem de máquina (em binário) para uma linguagem mais compreensível para leitura humana (com siglas e palavras em inglês). Por ser uma tradução literal, normalmente há pouca abstração, obrigando que quem leia uma linguagem de montagem ainda tenha que entender bastante do computador, binário e seu funcionamento. Em análise estática avançada de malware, haverá muita interação com Assembly.
Linguagens de montagem (e consequentemente linguagens de máquina) são dividas em duas categorias: - Complex Instruction Set Computer (CISC): abstrai mais operações a nível de hardware e portanto possui uma implementação mais complexa e por vezes uma execução mais lenta. Em troca, os programadores de CISC já possuem instruções complexas prontas. - Reduced Instruction Set Computer (RISC): possui menos opções de instruções complexas prontas em troca de mais velocidade e simplicidade na implementação do hardware. Em troca, deixa com que os programadores de RISC e compiladores tenham menos funções prontas disponíveis em seu arsenal.
Linguagens de Montagem famosas são: - Assembly x86: usadas em processadores Intel de arquitetura x86, ou seja, na maior parte dos computadores pessoais. É uma linguagem CISC e entende palavras 32 bits. Possui uma variação de 64 bits para processadores Intel x86_64 ou x64. - Assembly ARM: usadas em microprocessadores amplamente empregados em tecnologias embarcadas e celulares. É uma linguagem RISC que entende palavras de 32 bits. - Assembly RISC-V:
Montador e Desmontador
Como a transição entre linguagem de montagem para linguagem binária é uma tradução literal, o montador é um software até de simples implementação. Ele é equivalente a uma tabela de tradução. O mesmo funciona para o desmontador: funciona que nem uma tabela de tradução. Apesar de simples, desmontadores serão muito importantes na análise avançada de malware.
Linguagens de Alto Nível
É comum denotarmos a linguagem binária e de montagem como de baixo nível por estarem mais próximas da implementação em hardware. Linguagens de alto nível entretanto está mais próxima da lógica humana de se pensar. Linguagens como Python e C exigem que o usuário saiba pouco do funcionamento interno do computador e são amplamente utilizados atualmente.
Compilador
A transformação da lógica humana de uma linguagem de alto nível para o restrito conjunto de instruções computacionais da CPU não é simples. Compiladores realizam diversos processos e decisões para garantir uma maior otimização do código em sua implementação e linguagem de montagem.
Como a maioria dos programadores não escreve diretamente em linguagem de montagem ou binária, a grande maioria dos binários e executáveis maliciosos terão sido gerados por compiladores. E tentar "descompilar" muitas vezes não é uma opção, porque é um processo ainda mais complexo que depende muito do compilador utilizado e os parâmetros utilizados nele, elementos esses que na maioria das vezes não são conhecidos pelo analista. Não é indicado que nenhum analista de malware dependa de descompiladores com as limitações das tecnologias atuais, saibam assembly.
O estudo dos compiladores mais utilizados pelo mercado e seus padrões de implementação em assembly é uma atividade interessante para analistas mais experientes em análise estática avançada.
Os compiladores mais clássicos, como o o GCC (GNU C Compiler) costumam realizar os seguintes processos em ordem:
- Pré Compilação
- No GCC, use
gcc -E arquivo.cpara realizar somente a pré-compilação.
- No GCC, use
- Compilação:
- Processo complexo que envolve traduzir linguagens de alto nível para uma linguagem de baixo nível montadora.
- Resultado: arquivo em linguagem de montagem (entensão
.sou.asm). - No GCC, use
gcc -S arquivo.cpara realizar somente pré-compilação e compilação.
- Montagem:
- Processo de tradução da linguagem de montagem para a linguagem de máquina. É uma tradução literal e simples.
- Resultado: executável sem links de bibliotecas. Em Windows, é um
.exee em Linux é um.o. - No GCC, use
gcc -c arquivo.cpara realizar somente pré-compilação, compilação e montagem.
- "Carregamento de bibliotecas" / Linking
- Processo de carregar as bibliotecas utilizadas pelo programa.
- Resultado: executável. Em Windows, é um
.exee em Linux é um.o. - No GCC, use
gcc arquivo.cpara realizar todos as etapas.
Compiladores como o GCC seguem esse fluxo.
Hoje em dias, existem interpretadores e compiladores misturados com interpretadores, mas o entendimento do compilador clássico é o bastante para análise estática avançada.