Professional Documents
Culture Documents
Assembly 1
Assembly 1
Bom...antes de começarmos com o nosso "breve" tutorial de assembly, preciso fazer algumas considerações.
Assembly é uma linguagem de baixo nível (só lembrando: quanto mais ALTO for o nível de uma linguagem, mais
perto da escrita humana, portanto, mais FÁCIL de entende-la e utiliza-la). Portanto, talvez você tenha que ler
várias vezes esse tópico pra entender. Mas acreditem, Assembly é MUITO útil.
Outra coisa que gostaria de esclarecer é que algumas pessoas escrevem (e falam) o nome da linguagem de
maneira errada, o nome da linguagem é ASSEMBLY, muitas pessoas a chamam de Assembler. Assembler é o
"compilador" e não a linguagem. :)
>>>Conhecendo a linguagem<<<
Assembly é uma linguagem de programação extremamente poderosa e relativamente de difícil implementação,
pois seus comandos e suas estruturas são bem mais próximos da linguagem computacional. Raramente
encontramos programas puramente feitos em Assembly, uma vez que tal linguagem exige tempo e muito
raciocínio. Geralmente essa poderosa linguagem é implementada junto a alguma outra, funciona como auxiliar.
Por exemplo, em programas que exigem uma interação mais profunda com o hardware de uma máquina,
podemos implementar códigos Assembly junto ao código em C, C++ ou qualquer que seja a linguagem utilizada.
Outro exemplo prático do uso de Assembly são os famosos ShellCodes presentes nos exploits. E ainda, como
último exemplo, temos os nossos amigos “Reversers” (popularmente chamados de “crackers”, pois crackeiam
programas) que usam muito o Assembly. Um bom “reverser” (“cracker”) sabe profundamente Assembly ;)
Todos os processadores possuem uma "linguagem" própria embutida em cada um deles e cada instrução
(comando) dessa linguagem produz um efeito diferente no processador, através de seus registradores. Essas
instruções são representadas por números como, por exemplo, a famosa instrução MOV DL é representada pelo
número B2 em hexadecimal (178 em decimal). Esses números, em Assembly, são trasnformados nos chamados
mnemônicos para uma maior facilidade. Por exemplo, ao invés de você escrever B2 e ter de decorar o que é cada
número, você vai escrever o mnemônico dele que, nesse caso, é o MOV DL. Onde MOV significa MOVer e DL é o
chamado byte baixo de um outro registrador (DX).
"Meu Deus! Fiquei espantado com essa introdução!!! Assembly é difícil demais!!"
Tenha calma...Assembly não é aquele bicho de sete cabeças como todos dizem(e como parece ser também)...no
começo parece que você não vai entender nada, mas depois as coisas começam clarear e você vai entendendo
cada vez mais, até que começa a realmente utilizá-lo em seus projetos.
Bom...uma das maneiras mais fáceis de explicar um registrador é compará-lo a uma variável, eles são como
"espacinhos" reservados dentro do processador para guardar os códigos operacionais (MOV, JMP, NOP...), são
uma espécie de "memória" temporária do processador.
Existem vários registradores, deles, os mais usados são 8, que são denominados Registradores de Uso Geral.
Mas desses 8, os que realmente se utiliza bastante são apenas 4, são eles: AX, BX, CX e DX.
Um registrador armazena 16 bits, o que é correspondente a 2 bytes. Nas máquinas de 32 bits, temos o EAX, o
EBX, o ECX e o EDX que são registradores que podem guardar 32 bits (segundo a matemática avançada, o
equivalente a 4 bytes, pois 32/8 = 4 uahSIUHauhs).
Os nomes dos registradores sugerem pra que servem, o AX é o Acumulador, o BX é a Base, o CX é o Contador e
o DX possui um D que deriva de Dados. Esses nomes surgiram devido ao uso mais comum deles, o AX é mais
utilizado como acumulador, BX como base e etc...
"Aff Black-Hat, não aguento mais ler..." ... Calma, tá acabando (a parte de registradores ;P)
Bom...como eu havia dito, os registradores armazenam 2 bytes, que são chamados de Byte Alto e Byte Baixo,
cada registrador possui o seu BA e o seu BB. Se quisermos acessar o byte alto do registrador AX, por exemplo,
vamos utilizar o AH e observem que, mais uma vez o nome não está aí a toa...Alto em inglês é High e por isso
AH. Se quisermos acessar o byte baixo de AX, utilizamos o AL (baixo em inglês é Low).
Pronto!! Agora SÓ falta vermos as instruções, as variáveis, saltos...e mais umas coisinhas :D
Prepare-se psicologicamente, vá beber uma água ou namorar um pouco...porque agora começa a parte mais
importante (e mais legal tbm xD).
Simples... =)
O que esse programa faz? Bom...ele simplesmente termina (não, eu não estou bebado...ele nao faz nada alem
de terminar)
Bom pessoal...vocês devem estar se perguntando porque eu chamei a interrupção 21 e não a 55, por exemplo.
Bom...pra explicar isso vou precisar da ajuda de um "livrinho virtual", uma referência, que se chama HelpPC...
Vou deixar o link de download pra vcs baixarem, ok? (não vou anexar porque o limite maximo de anexo Zip é
menor do que o tamanho do arquivo)
Assim podemos continuar...
Continuando....
Ao abrirmos o HelpPC, caimos em uma tela inicial, chamada de Main Topic Menu. Vá até o item "Interrupt
Services DOS-BIOS-EMS-Mouse" e de um enter nele. Agora vá em "DOS Functions" e aperte enter novamente.
Procure pela função que utilizamos acima, a "INT 21,4C".
Bom...agora estamos na tela que nos mostra informações detalhadas sobre tal função, podemos observar que
essa função recebe o nome de "Terminate Process With Return Code", traduzindo: Terminar Processo Com
Código de Retorno. Olhando melhor na tela, percebemos que o HelpPC nos indica que o byte alto de AX deve ter
o valor 4C e que o valor baixo deve ter o código de retorno do nosso programa. Quem programa em C/C++ já
sabe o que é o código de retorno do programa...é um código, um número, que o programa retorn a ao ser
finalizado. Observamos também qeu a função 21 não retorna nenhum valor (o que seria chamado de rotina em
outras linguagens).
Resumindo...para usar essa função, basta você colocar os valores que são exijidos no registrador AX e depois
disso é só chamar uma interrupção de software 21 (INT 0x21).
Caso você esteja super curioso pra ver o nosso "programa" funcionar, baixe o NASM e compile-o, como?
simples...
Salve o código fonte dele como Terminator.asm no bloco de notas ou com qualquer outro nome que queira...
Depois vá no DOS e digite:
nasm -o Terminator.com Terminator.asm
e então você obterá o seu super programa compilado no arquivo Terminator.com =)
Se procurarmos no HelpPC a função que escreve um caractere na tela vamos achar a INT 21,2.
Observem que precisamos do valor 02 no byte alto de AX (AH) e precisamos que o caractere a ser mostrado
esteja no byte baixo de DX (DL). Pronto! Agora é só fazer isso...Let's Go
Bom...nosso programa precisa terminar ... basta adicionar o código do programa anterior...Ficando assim:
Obs.: Caso você tenha algum problema com a compilação, retire os comentários do código-fonte.
MOV DX, strg ; DX = Coloca em DX o valor de "strg"(na verdade DX se torna um ponteiro para strg, cada vez
que nos referimos a ele, ele aponta pra strg)
MOV AL, [DX] ; AL = Primeiro byte em "strg" = 'a'
Essa parte parece perda de tempo, mas não é, ela será essencial para a próxima parte....portanto, vamos
escrever a nossa string na tela xD
Se procurarmos no HelpPC a função que imprime caracteres na tela, vamos encontrar a INT 21,9 (serviço 9 da
interrupção 21).
Então, de repente, você olha pra tela e ve que o DSX tem que apontar para uma string terminada em $ (MEU
DEUSSS!!! O QUE É ISSO??). Bom...vamos ignorar o tal do DSX por enquanto, nao se assuste, eu avisei que ia
complicar um pouquinho...mas nada que um cérebro não resolva...
Bom...o DX precisa conter o endereço da string que vamos exibir(precisa ser um ponteiro para a string).
Bom...como vocês sabem...esse programa é um programa DOS, roda no "console" do windows...entao temos
algumas coisas que devemos lembrar:
Todo programa DOS é carregado no endereço 0x100. O NASM precisa de um endereço para calcular o valor dos
ponteiros no programa, entao temos que dizer ao NASM que o nosso endereço base (endereço onde o programa
será carregado) é o 0x100, como? usando o comando ORG (derivado de ORiGem), assim:
[ORG 0x100]
pronto...agora o NASM sabe o endereço base e pode calcular o valor dos ponteiros =)
>>>Entrada do teclado<<<
Abrimos o HelpPC felizes e contentes, procuramos por uma funçao que pegue as teclas digitadas...e então
encontramos a função DOS 21,A. Oba! vamos entrar nela...PELAS BARBAS DO PROFETA!!!(expressão de véio) O
QUE SÃO ESSES "BAGULHOS" ESCRITOS AQUI???
Haha...contenha-se...eu sei seu sei...parece complicado, mas você é um hacker, você vai entender...
Bom...olhando para a tela de informações sobre a função, DSX deve ser um ponteiro para um buffer com 3
partes:
-MAX : o numero maximo de caracteres que devem ser lidos
-COUNT : o número de caracteres retornados
-BUFFER : a parte aonde os dados serão guardados
; E finalmente o buffer
buff:
max DB 20
count DB 0
data DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Na área max definimos o número máximo de teclas que o buffer pode guardar, que no nosso caso é 20. E como
colocamos 20 como tamanho máximo, colocamos também 20 zeros na área data. Para o programa parar, aperte
Enter.
Bom...para fazer as comparações, precisamos utilizar uma outra instrução, a instrução CMP (derivada de
CoMPare). Nada como um exemplo para entendermos melhor a integração do CMP com o JMP.
[ORG 0x100]
Escreve: ; marcador chamado Escreve, para o programa saber para onde direcionar o salto
MOV AH, 9 ; Escrever mensagem
MOV DX, msg
INT 0x21
msg DB 'BELEZA!',13,10,'$' ; A string contém o código 13,10, que faz com que o console passe para a próxima
linha
Se você pensar e observar um pouco, você descobre o que o programa faz...Vamos analisar juntos...
Primeiramente o programa escreve a string msg na tela, em seguida adiciona 1 no valor do contador CX e então,
compara o valor de CX com 5, caso não seja 5, escreve a string de novo e adiciona mais 1 ao contador CX...E
assim vai até que isso tenha sido feito 5 vezes, daí o contador terá valor 5 e o salto não será executado, o
programa continuará executando o código normalmente, que no caso, fecha o programa.
[ORG 0x100]
CALL funcao ; Chama a função
Pode até parecer complicado, mas é fácil. Conforme você for praticando, você vai "pegando o jeito" de trabalhar
com funções, comparações, saltos...
=)
Ei!! Está pensando o mesmo que eu?? Podemos melhorar aquele nosso programa que grava as teclas digitadas,
utilizando jumps e comparações...
Vamos lá...vou colocar o código comentado aqui, analise-o com calma e entenda o que cada instrução faz, não
será preciso eu ficar explicando pra que serve cada instrução...você já as conhece...
[ORG 0x100]
comeco:
MOV AH, 9 ; Serviço 9 (de impressão) da INT 21
MOV DX, msg1 ; Coloca o DX como ponteiro para a mensagem "Digite Algo: "
INT 0x21 ; Chama a interrupção e escreve na tela
teclas:
MOV AH, 0x1 ; Serviço 1 da função 21, para rastrear uma tecla
INT 0x21
final:
MOV AL, '$' ; Põe o caractere terminador '$' em AL
MOV BL, [count] ; Põe número de teclas digitadas em BL
MOV BH, 0 ; Zera o byte alto de BX
MOV [data+bx], AL ; Adiciona o '$' no final do buffer
limpar:
MOV [data+bx], AL ; Põe 0 na posição início do buffer + BL
DEC BL ; Decrementa BL
CMP BL, 0 ; Compara BL com 0
JA limpar ; Se for maior que 0 continua limpando o buffer
JMP comeco ; Se BL for 0, volta para "comeco" e reescreve "Digite Algo: "
fechar:
MOV AX, 0x4c00 ; terminar programa
INT 0x21
O código está bem comentado e acho que vocês não terão problemas em interpreta-lo...Vamos ao nosso próximo
assunto...
Observe que colocamos o valor 456 e, em cima, colocamos o valor 789... na hora que retiramos da pilha,
primeiro retiramos o valor 789(ques estava no topo da pilha) e dps tiramos o que estava embaixo, o 456, que
havia sido o primeiro a ser colocado.
A memória tem suas posições, mas desde que as memórias melhoraram e ficaram capazes de armazenar muitos
MB, surgiu um problema...como fazer com as posições que ficam acima dos 64 Kb, isto é, o que fazer com as
posições acima da posição 65535?
A solução foi dividir a memória em "lotes" (segmentos) de 65536 bytes (de 0 a 65535).
Primeiramente os segmentos foram colocados um após o outro: o Segmento 1 começa na posição 0 e vai até a
65535, o Segmento 1 começa em 65536 e vai até a 131071...e assim por diante...
Quando quisessemos acessar uma posição, utilizariamos 2 números, um para indicarmos o segmento que iriamos
acessar e o outro para indicarmos a posição dentro desse segmento...
Só que houve um segundo problema...ao colocarmos os segmentos um após o outro, haviam programas que
quando utilizavam a memória, não utilizavam todo o segmento...então a memória ficava cheia de "buracos" sem
dados gravados...
Foi aí que surgiu uma nova idéia...colocar os segmenos um em cima do outro, dando um espaço de 16 bits entre
o começo de um e o começo do outro...
Então agora o Segmento 0 começa na posição 0 e vai até 65535, O Segmento 1 começa na posição 16 e vai até
65551...
Pronto...agora você sabe(ou não IUaHSIUAHS) como a memória do seu pc é dividida...não é fantástico? ;P
O segmento começa na posição 37520 (2345*16) da memória e o nosso deslocamento dentro do segmento é de
6789, ou seja, 6789 posições à frente do começo do segmento. Se o segmento começa em 37520, a posição que
queremos (6789 posições à frente) fica no endereço 44309 da memória... xDD
Bom...o seu processador, muito esperto, possui registradores especiais para lidar com os segmentos, o principais
são: CS(Code Segment) , DS(Data Segment) , ES(Extra Segment)...O CS gerencia aonde está o código do
programa, o DS gerencia aonde estão os dados do programa e o ES, como o nome diz, é um adicional.
Também há um registrador para gerenciar o deslocamento...o SI(Segment Index)
Quando queremos acessar um dado do programa, utilizamos a dupla DS:SI, onde DS diz o segmento que está o
dado e o SI diz o deslocamento dentro do segmento em questão....
Não há instruções que alterem diretamente esses registradores...se um programador distraído bagunçar esses
registradores ou se um programador inexperiente resolver "xeretar" eles e alterar de forma indevida, os
resultados serão EXTREMAMENTE DANOSOS...entao é justamente por isso que, caso o programador queira
altera-los, terá de fazer de forma indireta, oq eu certamente vai exigir mais atenção e evitar erros acidentais...
Vamos ao exemplo de como alterar os valores... (lembre-se: nunca brinque com assembly por conta própria,
principalmente quando se trata de operações com memória)
INC AX ; Incrementa AX
MOV [ds:32], AX ; Põe valor de AX no endereço 1000:0032 - endereço 16032
Bom...acho que depois de ter tido paciência (e muita!) e lido esse artigo, você agora viu que Assembly não é
algo de outro mundo...obviamente é uma linguagem difícil, mas não é impossível de se aprender.
Aqui coloquei as coisas básicas de Assembly, o necessário para você começar...a partir de agora a
responsabilidade de aprender mais é sua, tente fazer programas com outras funções, para isso use SEMPRE o
HelpPC...
Depois que você dominar BEM esses conceitos básicos, passe para uma parte mais avançada da linguagem, com
intruções e interrupções mais complicadas e até tente aprender a programar ShellCodes...Mas tenha calma, o
segredo de um bom programador é fazer as coisas em passos para alcançar seus objetivos...não se atropele,
siga o seu ritmo e chegue aonde você quer!
Espero que gostem do meu artigo, deu um trabalho legal para escrever ele, mas espero que seja de grande
utilidade para vocês. :D