Академический Документы
Профессиональный Документы
Культура Документы
Onde:
.DATA -> É responsável por salvar o endereço de retorno da subrotinas do programa, passar parâ-
metros para funções, criar variáveis locais, ou seja, ela guarda os dados do programa.
.BSS -> É para dados indefinidos, responsável por armazenar dados alocados dinamicamente e en-
dereços de variáveis estáticas.
.TEXT -> Aqui ficam as instruções assembly, onde será passado para o processador, e o mesmo as
executará.
Stack -> (ou pilha) é uma ára da memória responsável por armazenar o endereço das subrotinas,
passar argumentos (dados) para as funções, armazenar variáveis locais, etc. A Stack
usa o conceito LIFO (Last In, First Out), ou seja, pense numa pilha de livros, o últi-
mo que você colocar na pilha, será o primeiro a sair da mesma.
Heap -> (ou pilha) é uma área da memória que é utilizada por uma aplicação e é alocada dinami-
camente em tempo de execução. Aqui são armazenados variáveis estáticas (globais, cons-
tantes e variáveis definidas "static", em linguagem C), ou seja, variáveis que permane-
cem com o mesmo valor até o final do programa.
* O que são?
São blocos de memória do processador que são usados para armazenar informações. Antes
dos processadores i386, os registradores eram de 16 bits, agora com essa arquitetura,
foi criado novos registradores com maior capacidade, os de 32 bits.
- Esses registradores podem ser usados para armazenamento de valor, mas todos registradores tem
uma função específica, depende do seu uso.
- Esses registradores são de 32 bits, agora lhe pergunto: Como eu faço para criar um programa
de 16 bits ou 8 bits?
- É simples. Os registradores de 32 bits são feitos de dois registradores de 16 bits, e
esses de 16 bits são feitos de dois registradores de 8 bits, que são:
+ AX (16 bits, parte alta),
+ AX (16 bits, parte baixa),
+ AH (8 bits, parte alta - H de High) e
+ AL (8 bits, parte baixa - L de Low).
- Lembrando que somente a parte alta do AX é formada pelo AL e AH.
- Veja a ilustração:
* Essa ilustração vale para os outros 3
registradores, o EBX, ECX e EDX.
---------------------------------------------------------------------------------
| EAX |
---------------------------------------------------------------------------------
| | AX (parte alta) |
---------------------------------------------------------------------------------
| | AH (high) | AL (low) |
---------------------------------------------------------------------------------
EAX -> Pode ser usado para armazenar dados. É usado mais em operações aritméticas, operações de
entrada e saída de dados ou para chamadas de interrupção. Ele é indispensável quando te-
mos que realizar chamadas de sistema (system call).
EBX -> Pode ser usado para armazenar dados. É usado como offset (deslocamento daquele intervalo
de memória). Esses registradores são a base dos registradores e são usados como ponteiro
base para o acesso a memória. São usados para passar argumentos de chamada de sistema.
São usados também para guardar o valor de retorno de uma interrupção.
ECX -> Pode ser usado para armazenar dados e são contadores.
EDX -> Pode ser usado para armazenar dados. Eles podem ser usados para operações aritméticas,
chamadas de interrupção e algumas operações de I/O (Input/Output).
ESI -> (Extended Source Index) -> Usado para apontar para as instruções.
EDI -> (Extended Destiny Index) -> Pode ser usado como registrador de offset.
EIP -> (Extended Instruction Pointer) -> É um offset do segmento da próxima instrução a ser
executada (não pode ser acessado por qualquer instrução).
Não podem ser acessados diretamente, sendo usado mais como origem. Os registradores são:
- CS (Code Segment) -> Armazena os códigos, que corresponde a uma parte da memória, a-
pontando sempre para a próxima instrução.
- DS (Data Segment) -> Representa a parte da memória onde os dados são armazenados.
- ES (Extra Segment) -> Muito usado como segmento destino em operações de transferência
e movimentação de dados.
- SS (Stack Segment) -> É o segmento onde está o stack. A CPU usa ele para armazenar
endereços de retorno de sub-rotinas.
# -> Comentário.
% -> Indicador de registrador.
$ -> indicador de valor.
() -> Indica o endereço onde está armazenado a informação, exemplo: mov $0x42, (%EAX) # Endere-
ço armazenado em EAX <- 42, ou seja, não é o EAX que recebe o valor, mas sim o endereço
que está armazenado nele.
* Instruções de movimentação:
mov -> Move o valor para um endereço/registrador, ex: mov $0x00, %EDX # EDX <- 00.
lea -> Faz um add, e logo em seguida faz um mov. Ex: lea %EBX, %EDX # EDX <- EDX + EBX,
desmembrando essa operação, ficaria: add %EBX, %EDX, em seguida, mov %EBX, %EDX.
lds -> É usado para carregar dois registradores de uma vez. Ex: CS:EBX.
les -> É usado para carregar dois registradores com Dword (32 bits) para o endereço a-
pontado por DS:SI.
xchg -> Troca os valores entre dois registradores,
ex: xchg %EAX, %EBX # EBX <- EAX e EAX <- EBX (troca simultaneamente).
push -> Coloca determinado valor em cima da pilha. Ex: push $0xbf
pop -> Retira o valor que está em cima da pilha. Usando o exemplo de cima, pop $0xbf #
retirou da pilha o valor $0xbf.
* Instruções Aritméticas:
add -> Soma, ex: add $0x4, %EBX # <- EBX <- EBX + 4.
sub -> Subtração, e também pode ser usado para alocar espaço de memória no stack para
receber dados, ex: sub $0x3, %EDX # EDX <- EDX - 3 |
sub $0x5, %ESP # Reserva 5 bytes no Stack para receber dados.
mul -> Multiplicação, ex: mul $0x2, %EAX # EAX <- EAX * 2.
imul -> Multiplicação, porém leva em consideração o sinal.
div -> Divisão, ex: div $0x4, %EAX # EAX <- EAX / 4.
idiv -> Divisão, porém leva em consideração o sinal.
inc -> Incrementa, ou seja, soma um ao registrador, ex: inc %AL # AL <- AL + 1.
dec -> Decrementa, ou seja, subtrai um ao registrador, ex: dec %AH # AH <- AH -1.
* Instruções Lógicas:
Fazendo uma comparação das instruções lógicas entre C e ASM, teríamos a seguinte
tabela:
---------------------
| C | ASM |
---------------------
| && | AND |
| || | OR |
| ! | NOT |
| | XOR |
---------------------
Onde:
AND -> Significa "E" na tabela verdade. Ex: mov $0xa, %EAX # EAX <- 10, em seguida, and $4,
%EAX # Reseta o quarto bit do registrador EAX.
OR -> Significa "OU" na tabela verdade. Ex: or $0x0f, %EBX.
NOT -> Significa "NÃO" na tabela verdade. Pode ser usado para inverter os bits de um byte.
Ex: Esse byte, 01010101, negado, ficaria, 10101010. not %EAX # inverte seus bits.
XOR -> Detector de diferenças. Ex: xor %ECX.
* Instruções de Comparação:
test -> Verifica se está tudo ok com o registrador, ex: test %EAX, %EAX.
cmp -> Compara dados sendo usados. Ex: cmp $0x5, %EAX # Compara 5 com EAX. O cmp sempre
está junto com algum salto condicional, os JXX, onde X são opções de salto. Logo
abaixo temos os saltos.
ret (Return Address) -> É usado em sub-rotinas, para voltar a execução normal do pro-
grama.
NOP (NO Operation) -> Como o próprio nome já diz, não é feito nada. Ele é usado em ex-
ploits para encher o buffer de 'nada' até chegar ao shellcode.
Seu código em hexadecimal é 0x90.
Programas em ASM são divididos em sessões, onde assim, há uma certa organização do código, ar-
mazenamento de dados, etc.
As sessões são:
* Headers (cabeçalho):
- Assim como na linguagem C, o ASM também possui cabeçalho, podendo ser qual-
quer arquivo do sistema, sub-rotinas, etc.
Em linguagem C, seria "#include<define.h>", em ASM seria ".include define.h".
* Sessão de dados:
- Essa sessão acontece a declaração de variáveis. Para iniciar essa sessão,
basta colocar ".section .data".
- Para declarar variáveis, essa seria a sintaxe: "NOME: .TIPO VALOR" ou "NOME =
VALOR" (essa segunda sintaxe vale apenas para variáveis do tipo long), onde
"NOME" seria o nome da variável, o "TIPO" seria o tipo dela, e o "VALOR" se-
ria o valor a ser atribuído.
- Alguns exemplos de tipos de variáveis em ASM: ".long", ".string" e ".ascii"
(existem mais tipos), onde:
- .long é usado para números de até 32 bits, ex:
NUM: .long 10 # NUM é do tipo long de valor 10.
ou:
NUM = 0x7 # NUM é do tipo long de valor 0x7.
- .string é usado para variáveis de string e podem usar caracteres de
tabulação, como \t, \n, etc. Ex: STR: .string "OLÁ!\n".
Também é possível criar uma variável numérica que conterá o tamanho
da string já criada, ex: TAM: .long .-STR # Agora TAM tem a quantida-
de de caracteres de STR.
Isso sempre tem que ser feito quando for usado variáveis strings, já
que é necessário passar seu tamanho para o programa futuramente.
- .ascii é usado como o .string.
# Declaração de variáveis.
Nessa sessão é implementado o código do programa, informações de símbolos, etc. Aqui fica arma-
zenado o código e o binário. Sua declaração é feita da seguinte maneira: ".section .text" (ses-
são texto).
Assim como em linguagem C, o ASM também possui uma função principal (main). Para declará-la,
digite ".global _start", e para iniciá-la efetivamente, "_start".
Agora um exemplo dessa sessão agregada com o exemplo da sessão de dados dada anteriormente.
# Início da sessão de dados.
.section .data
# Declaração de variáveis.
num: .long 7 # num <- 7
str: .string "Isso é uma string!\n" # str <- "Isso é uma string!\n"
tam: .long .-str # tam <- tamanho de str
str2: .ascii "Outra string!\n" # str2 <- "Outra string!\n"
tam2: .long .-str2 # tam2 <- tamanho de str2
--- Labels.
É como se fosse uma função, um módulo, em linguagem C. Você pode criar um label e mandar o pro-
grama "pular" para aquele label, então ele executa as instruções daquele label e retorna para a
execução do programa.
Exemplo:
(...)
jmp label
(...) # pode ter mais instruções aqui ou não.
label:
movl $0x4,%eax
movl $0x0,%ecx
(...)
*******************************************************************************************************
# begin
# end
Para compilar:
# as -o imprime.o imprime.s # Cria o objective file do programa
# ld -o imprime imprime.o # Faz a linkagem do obj file, tornando-o executável
# ./imprime # Executa o programa
*******************************************************************************************************
Vamos ver outro exemplo, agora usando a syscall read, que capta o que o usuário digitar.
Primeiramente vamos verificar os parâmetros que ela precisa, para isso, "man 2 read", "ssize_t
read(int fd, void *buf, size_t count);". Apesar de necessitar de 3 parâmetros, precisa-se pas-
sar apenas o tamanho do que será lido pelo programa. Segue o exemplo:
*******************************************************************************************************
# begin
movl %eax, %edx # edx <- eax, ou seja, move o que o usuário digitou para %edx
# end
Para compilar:
# as -o leia.o leia.s # Cria o objective file do programa
# ld -o leia leia.o # Faz a linkagem do obj file, tornando-o executável
# ./leia # Executa o programa
*******************************************************************************************************
Como dito anteriormente, para passar os parâmetros da syscall, basta inserir os valores para os
registradores BX, CX, DX, SI e DI. Mas e se a syscall necessitar de mais que seis argumentos?
Então, temos que jogar os valores para a stack de traz para frente (lembre-se que seu método de
inserção é LIFO, Last In - First Out), sendo que em %ebx, ficará o endereço do primeiro argu-
mento. Também pode-se copiar os argumentos para um espaço de memória alocado e armazenar o en-
dereço do primeiro argumento em %ebx.
*******************************************************************************************************
# Estrutura de decisão (if else)
# begin
# Label IGUAL
IGUAL:
# syscall write (4)
movl $0x4, %eax # eax <- syscall write (4)
movl $0x1, %ebx # Onde será escrito (primeiro argumento)
leal str2, %ecx # O que será escrito (segundo argumento)
movl tam2, %edx # Tamanho do que será escrito (terceiro argumento)
int $0x80 # Executa o write
# Label DIFERENTE
DIFERENTE:
# syscall write (4)
movl $0x4, %eax # eax <- syscall write (4)
movl $0x1, %ebx # Onde será escrito (primeiro argumento)
leal str3, %ecx # O que será escrito (segundo argumento)
movl tam3, %edx # Tamanho do que será escrito (terceiro argumento)
int $0x80 # Executa o write
# Label EXIT
EXIT:
# syscall exit (1)
movl $0x1, %eax # eax <- syscall exit (1)
movl $0x0, %ebx # saída padrão
int $0x80 # Executa o exit
# end
Para compilar:
# as -o if.o if.s # Cria o objective file do programa
# ld -o if if.o # Faz a linkagem do obj file, tornando-o executável
# ./if # Executa o programa
*******************************************************************************************************
*******************************************************************************************************
# Estrutura de repetição
# begin
# Contador
cont: .long 0
_start:
# Label INICIO
INICIO:
# syscall write (4)
movl $0x4, %eax # eax <- syscall write (4)
movl $0x1, %ebx # onde será escrito (primeiro argumento)
leal str2, %ecx # o que será escrito (segundo argumento)
movl tam2, %edx # tamanho do que será escrito (terceiro argumento)
int $0x80 # Executa a syscall write (4)
# Label EXIT
EXIT:
# syscall write (4)
movl $0x4, %eax # eax <- syscall write (4)
movl $0x1, %ebx # onde será escrito (primeiro argumento)
leal str3, %ecx # o que será escrito (segundo argumento)
movl tam3, %edx # tamanho do que será escrito (terceiro argumento)
int $0x80 # Executa a syscall write (4)
# syscall exit (1)
movl $0x1, %eax # eax <- syscall exit (1)
movl $0x0, %ebx # saída padrão
int $0x80 # executa a syscall exit (1)
# end
Para compilar:
# as -o repetition.o repetition.s # Cria o objective file do programa
# ld -o repetition repetition.o # Faz a linkagem do obj file, tornando-o executável
# ./repetition # Executa o programa
*******************************************************************************************************