Assembly é uma linguagem de baixo nível que serve como ponte entre o código de máquina e as linguagens de alto nível, como [[C]] ou [[Python]]. Escrever em Assembly significa falar diretamente com o processador, controlando cada instrução executada, cada registrador utilizado, cada byte de memória manipulado. É aqui que o programador tem controle total — e também total responsabilidade.
Cada arquitetura de processador (como x86, ARM ou MIPS) possui seu próprio conjunto de instruções (ISA – Instruction Set Architecture), o que significa que o Assembly é intrinsecamente dependente da arquitetura para a qual se está programando. Por isso, o estudo de Assembly envolve não apenas aprender a sintaxe da linguagem, mas também compreender o funcionamento interno da CPU, registradores, pilha, alocação de memória e o ciclo de instrução.
Apesar de sua complexidade e verbosidade, Assembly é fundamental para áreas como engenharia reversa, segurança da informação, desenvolvimento de sistemas embarcados e otimização de desempenho. Conhecê-lo é desvendar os bastidores da computação — é enxergar como cada linha de código se transforma, passo a passo, em operações executadas pelo hardware.
## Seções do código
Os programas em assembly podem ter três seções:
- `section .data`: Área dos dados inicializados.
- `section .bss`: Área de dados não inicializados.
- `section .text`: O código do programa.
Obs.: A ordem das seções é indiferente.
### Estrutura de uma linha de código
Cada linha de código do NASM (a menos que seja uma macro, uma diretiva de pré-processamento ou uma diretiva de compilação) é composta pela combinação de quatro campos:
```shell
+---------+-----------+-----------+--------------+
| rótulo: | instrução | operandos | ; comentário |
+---------+-----------+-----------+--------------+
```
OBS: A presença ou ausência de qualquer um desses campos pode variar ou até ser opcional, mas este é o layout geral.
### O seu primeiro programa
A execução de programas em assembly é feita de cima para baixo e deve sempre conter um rótulo (_label_) para indicar o ponto de início do programa: seu _ponto de entrada_, o que nós definimos desta forma na seção `.text`.
Na seção `.data` nós definimos os identificadores de _dados inicializados_, algo análogo às variáveis e constantes inicializadas de linguagens de alto nível. É "algo análogo" porque, em linguagens de baixo nível, o conceito de "variáveis" é uma mera analogia com o que temos em linguagens de alto nível: na prática, esses identificadores de dados são rótulos (_labels_) e apenas nomeiam endereços na memória.
```shell
section .data
section .text
global _start ; A diretiva 'global' torna o rótulo '_start'
; visível de qualquer parte do programa.
_start: ; Aqui está o início do programa.
```
#### Diretivas
No contexto de Assembly, o termo **"diretiva"** é usado porque `global` não é uma instrução executável do processador, mas sim um **comando para o assembler** (o programa que converte código Assembly em código de máquina).
As diretivas são instruções que controlam como o assembler deve processar o código, não o que o processador deve executar. Elas servem para:
- **Configurar símbolos e rótulos** (`global`, `extern`)
- **Definir seções de memória** (`section .data`, `section .text`)
- **Reservar espaço** (`db`, `dw`, `dd`)
- **Incluir arquivos** (`include`)
A diretiva `global _start` informa ao assembler que o símbolo `_start` deve ser exportado, permitindo que o linker o encontre como ponto de entrada do programa.
**Diferença importante:**
- **Instruções**: `mov`, `add`, `call` → executadas pelo processador
- **Diretivas**: `global`, `section`, `db` → processadas pelo assembler
#### Rótulos
Os rótulos (_labels_) são identificadores de endereços e servem para que possamos fazer saltos (_jumps_) para diferentes partes do programa ou localizar dados na memória. Nós podemos criá-los livremente, mas o rótulo `_start:` normalmente é utilizado para definir o _ponto de entrada_ do programa.
### Escrevendo uma mensagem
```asm
section .data
msg db "Hello!",10 ; 'msg' - rótulo dos dados definidos
; 'db' - os dados são definidos como
; uma cadeia de bytes.
section .text
global _start ; A diretiva 'global' torna o rótulo '_start'
; visível de qualquer parte do código.
_start: ; Aqui está o início do programa.
```
#### As pseudos intruções
A pseudo-instrução `db` (_define bytes_) é utilizada para definir que os dados devem ser interpretados como uma cadeia de bytes. Contudo, nós ainda podemos definir dados como cadeias de palavras (`dw`), cadeias de palavras duplas (`dd`) ou cadeias de palavras quádruplas (`dq`), onde, em processadores x86_64, as palavras terão, 2, 4 ou 8 bytes de comprimento, respectivamente.
Isso afeta diretamente o espaço ocupado pelos dados na memória:
| Diretiva | Caracteres | Caracteres em hexa na memória | Tamanho |
| -------- | ---------- | ----------------------------------------- | ------------------------------ |
| `db` | `'ABCDE'` | `0x41 0x42 0x43 0x44 0x45` | 5 bytes |
| `dw` | `'ABCDE'` | `0x41 0x42 0x43 0x44 0x45 0x00` | 6 bytes, 3 palavras de 2 bytes |
| `dd` | `'ABCDE'` | `0x41 0x42 0x43 0x44 0x45 0x00 0x00 0x00` | 8 bytes, 2 palavras de 4 bytes |
| `dq` | `'ABCDE'` | `0x41 0x42 0x43 0x44 0x45 0x00 0x00 0x00` | 8 bytes, 1 palavra de 8 bytes |
Existem outros múltiplos de palavras após `dq`:
- `dt`: palavras de 10 bytes (_ten-word_: 80 bits).
- `do`: palavras de 16 bytes (_octo-word_: 128 bits).
- `dy`: palavras de 32 bytes (256 bits).
- `dz`: palavras de 64 bytes (512 bits).
> Nenhum deles permite a passagem de constantes numéricas inteiras como valor.
```
### Tabela de Registradores
| 64 bits | 32 bits | 16 bits | 8 bits Low | 8 bits High |
| :-----: | :-----: | :-----: | :--------: | :---------: |
| RAX | EAX | AX | AL | AH |
| RBX | EBX | BX | BL | BH |
| RCX | ECX | CX | CL | CH |
| RDX | EDX | DX | DL | DH |
| RSI | ESI | SI | — | — |
| RDI | EDI | DI | — | — |
| RSP | [[ESP]] | SP | — | — |
| RBP | [[EBP]] | BP | — | — |
| RIP | [[EIP]] | IP | — | — |
| R8–R15 | — | — | — | — |
- RBP/EBP/BP: é o **Base Pointer** (também chamado de _frame pointer_).
Ele serve como **referência estável** para acessar variáveis locais e parâmetros da função durante sua execução. Em resumo, durante a execução da função, o **RBP guarda o valor da RSP (stack pointer)** no exato momento em que a função começou. Ele serve como âncora fixa.
ESP -> Aponta para a topo da pilha.
EBP -> Aponta para a base da pilha.
EIP -> Aponta para o próximo endereço a ser executado.
#### O que é um **Stack Frame**?
Um **stack frame** (ou "quadro de pilha") é um **bloco de memória** alocado na pilha toda vez que uma função é chamada. Ele guarda **tudo o que a função precisa para funcionar isoladamente**:
- **Parâmetros da função** (caso não estejam em registradores)
- **Variáveis locais**
- **Valor antigo do RBP** (base pointer da função chamadora)
- **RIP - Endereço de retorno** (para onde a função deve voltar ao terminar)
```shell
Pilha de um binário de 64 bits
+-------------------------+
| Parâmetros adicionais | ← Caso sejam mais que RAX,RBX,etc.
+-------------------------+
| Variáveis locais | ← RSP aponta para esse local.
+-------------------------+
| Saved RBP | ← Base do Stack Frame.
+-------------------------+
| RIP | ← Para onde return vai pular.
+-------------------------+
```
### Estrutura de Memória de um Processo
| **Seção** | **Descrição** |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **Text** | Código do programa (instruções executáveis). |
| **Code** | Dados inicializados (variáveis com valor definido). |
| **BSS** | Dados não inicializados (variáveis globais sem valor inicial). |
| **Heap** | Alocação dinâmica (o programa pode requisitar mais espaço em tempo de execução). |
| **Unused Memory** | Espaço de memória ainda não utilizado pelo processo. |
| **Stack** | Pilha do programa. Funciona como LIFO (_Last In, First Out_), utilizada para armazenar funções, parâmetros e variáveis locais. |
### Assembler
O **Assembler** é um programa (ou ferramenta) que **converte código escrito em linguagem Assembly** para **código de máquina** (binário) que o processador pode entender e executar diretamente.
**Assembly → Assembler → Código de máquina (opcodes - arquivos .o)**
Os principais Assemblers do mercado são:
- **NASM** (Netwide Assembler) – muito usado em Linux.
- **MASM** (Microsoft Assembler) – comum em ambientes Windows.
- **GAS** (GNU Assembler) – usado com GCC no Linux.
- **FASM** (Flat Assembler) – leve, usado em projetos independentes.
Exemplo de código NASM:
```
section .text
global _start
_start:
mov eax, 1 ; syscall: exit
mov ebx, 0 ; exit code
int 0x80 ; chamada de sistema
```
Depois de compilado ele fica dessa forma:
```
B8 01 00 00 00 BB 00 00 00 00 CD 80
```
### Linker
O **Linker** (ligador) é uma ferramenta que **combina um ou mais arquivos objeto (.o ou .obj)** gerados pelo Assembler e **resolve todas as referências entre eles**, produzindo o **arquivo executável final** (como um `.exe` no Windows ou um binário ELF no Linux).
**Assembly → Assembler → Linker > Binário Executável**
Funções principais do Linker
- **Resolve símbolos**: Exemplo: Se você chama a função `printf`, o Linker encontra onde ela está (ex: libc) e conecta seu código com ela.
- **Combina arquivos objeto**: Permite modularidade. Você pode ter múltiplos `.o` e juntar tudo num `.exe`.
- **Aloca endereços**: Decide onde cada função/variável vai ficar na memória no binário final.
- **Inclui bibliotecas externas**: Está chamando funções do sistema? O Linker conecta seu programa com essas bibliotecas.
### Funções Assembly
```markdown
MOV - Define um valor para um registrador.
JMP - Pula para o local deixando de executar o que está abaixo do JUMPER.
CALL - Chama uma função em seguida, quando a função termina volta para o função chamadora.
LEA (Load Effective Address) - Seria uma chamada para ler a posição de memória do que é passado.
RET - Retorna para a posição antiga (Usada dentro de uma função)
PUSH -> O `PUSH` coloca um valor no topo da pilha (stack).
A pilha é uma estrutura LIFO (último a entrar, primeiro a sair), usada principalmente para:
- Passar argumentos para funções;
- Salvar valores temporários (como registradores);
- Controlar o fluxo da execução (retornos, etc.).
```
### Partes de um código Assembly
| Seção | Uso |
| ------- | ------------------------------------------------------- |
| `.data` | Variáveis **inicializadas** (strings, números, etc.) |
| `.bss` | Variáveis **não inicializadas** (buffers, arrays, etc.) |
| `.text` | Código executável (instruções) |
### Exemplos baseados nas funções:
Manipulação de registradores:
```
MOV EAX, 0x1337 ; Move valor 0x1337 para EAX
ADD EAX, 0x1 ; Soma 1 ao valor em EAX (incrementa)
SUB EBX, EAX ; Subtrai EAX de EBX
XOR EAX, EAX ; Zera EAX (técnica comum pra zerar registrador)
```
Stack (pilha):
```
PUSH EAX ; Empilha valor de EAX
POP EBX ; Desempilha para EBX
PUSH 0x41414141 ; Empilha valor AAAAAAAA (útil em fuzzing/shellcode)
```
Controle de fluxo:
```
CMP EAX, EBX ; Compara EAX com EBX
JNE not_equal ; Salta para "not_equal" se os valores forem diferentes
JMP exit ; Salta incondicionalmente para "exit"
CALL my_function ; Chama uma função
```
Interrupções e no-ops:
```
NOP ; No Operation (usado em NOP sleds)
INT3 ; Breakpoint para debugging (\xCC)
```
### Exemplos práticos para Windows
Gerando um alerta no Windows com uma caixa de mensagem:
```c
default rel
extern MessageBoxA
global main
section .data
texto db "www.ooclaar.com.br", 0
titulo db "Keep Learning", 0
section .text
main:
sub rsp, 40 ; espaço para alinhamento da pilha
mov rcx, 0 ; hWnd = NULL
mov rdx, texto ; lpText
mov r8, titulo ; lpCaption
mov r9d, 0x00000001 ; uType = MB_OKCANCEL
call MessageBoxA
add rsp, 40 ; limpa stack
ret
```
Compilando o código assembly em um arquivo .obj com NASM.
```markdown
# Para compilar em 32 bits
nasm.exe -f win32 -o $file_name.o $file_name.asm
# Para compilar em 64 bits.
nasm.exe -f win64 -o $file_name.o $file_name.asm
```
Realizando a ligações com LD ou GCC.
```markdown
# Compilando 32bits usando LD.
ld.exe -m i386pe -o .\exemplo1.exe .\exemplo1.obj -e _main
# Compilando 64bits usando LD.
ld -o exemplo.exe exemplo.obj -e main -luser32 -lkernel32
# Compitando para 32 bits usando GCC
gcc -m32 exemplo.obj -o exemplo.exe -luser32
# Compilando para 64bits usando GCC
gcc .\exemplo1.obj -o ./exemplo1.exe -luser32
```
*OBS: O parâmetro -luser32 incluir a **DLL do Windows chamada `user32.dll`**, que contém funções da interface gráfica, como: `MessageBoxA`, `CreateWindowEx`, etc.*
*OBS2: O código assembly pode mudar um pouco de acordo com a versão do sistema que irá compilar.*
*Referência a chamada de API:*
*https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxa*
Criando um código malicioso para funcionar de forma oculta:
```
default rel
extern ShellExecuteA
global main
section .data
tipo db "open", 0
cmd db "cmd", 0
param db "/c mkdir ola", 0
section .text
main:
sub rsp, 32 ; alinha a pilha (shadow space)
xor rcx, rcx ; HWND = 0
mov rdx, tipo ; lpOperation = "open"
mov r8, cmd ; lpFile = "cmd"
mov r9, param ; lpParameters = "/c mkdir ola"
mov qword [rsp + 32], 0 ; lpDirectory = NULL
mov qword [rsp + 40], 1 ; nShowCmd = 1 (SW_SHOWNORMAL)
call ShellExecuteA
add rsp, 32 ; restaura pilha
ret
```
*Referência a chamada de API:*
*https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea
*Referência aos conjuntos de SysCall Linux:*
*https://syscalls.w3challs.com/?arch=x86
*Localização dos arquivos syscall do Linux:*
*/usr/include/x86_64-linux-gnu/asm/\*
```markdown
# Analisando uma função de syscall usando man
man 2 white
```
## Exemplos práticos para Linux (i386)
```
global _main
section .data
curso: db 'Texto do ooclaar', 0xa
section .text
_main:
mov eax, 4
mov ebx, 1
mov ecx, curso
mov edx, 15
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
```
```markdown
# NASM de Output:
# - elf64: Linux 64 bits
# - elf32: Linux 32 bits
# - win64: Windows 64 bits
# - win32: Windows 32 bits
# - macho64: macOS 64 bits
# Compitando para 32 bits usando GCC
nasm -f elf32 arquivo.asm -o arquivo.o
# Compilando para 64bits usando GCC
nasm -f elf64 arquivo.asm -o arquivo.o
# Compilando para 32bits (Windows) usando LD.
x86_64-w64-mingw32-ld arquivo.o -o exemplo.exe
# Compilando para 64bits (Windows) usando LD.
i686-w64-mingw32-ld arquivo.o -o exemple.exe
# Compilando para 32bits (MacOS) usando LD.
ld -macosx_version_min 10.7.0 -lSystem arquivo.o -o executavel
# Compilando 32bits usando LD.
ld --entry _main -m elf_i386 ./exemplo.o -o exemplo
# Compilando 64bits usando LD.
ld --entry main -m elf_x86_64 ./exemplo.o -o exemplo
```