Sintaxe

Entendendo a sintaxe da linguagem Assembly no nasm

O Assembly da arquitetura x86 tem duas vers√Ķes diferentes de sintaxe: A sintaxe Intel e a sintaxe AT&T. A sintaxe Intel √© a que iremos usar neste livro j√° que, ao meu ver, ela √© mais intuitiva e leg√≠vel. Tamb√©m √© a sintaxe que o nasm usa, j√° o GAS suporta as duas por√©m usando sintaxe AT&T por padr√£o. √Č importante saber ler c√≥digo das duas sintaxes, mas por enquanto vamos aprender apenas a sintaxe do nasm.

Case Insensitive

As instru√ß√Ķes da linguagem Assembly, bem como tamb√©m as instru√ß√Ķes particulares do nasm, s√£o case-insensitive. O que significa que n√£o faz diferen√ßa se eu escrevo em caixa-alta, baixa ou mesclando os dois. Veja que cada linha abaixo o nasm ir√° compilar como a mesma instru√ß√£o:

mov eax, 777
Mov Eax, 777
MOV EAX, 777
mov EAX, 777
MoV EaX, 777

Coment√°rios

No nasm se pode usar o ponto-v√≠rgula ; para coment√°rios que √ļnica linha, equivalente ao // em C. Coment√°rios de m√ļltiplas linhas podem ser feitos usando a diretiva pr√©-processada %comment para iniciar o coment√°rio e %endcomment para finaliz√°-lo. Exemplo:

; Um exemplo
mov eax, 777 ; Outro exemplo

%comment
  Mais
  um
  exemplo
%endcomment

N√ļmeros

N√ļmeros literais podem ser escritos em base decimal, hexadecimal, octal e bin√°rio. Tamb√©m √© poss√≠vel escrever constantes num√©ricas de ponto flutuante no nasm, conforme exemplos:

Exemplo

Formato

0b0111

Bin√°rio

0o10

Octal

9

Decimal

0x0a

Hexadecimal

11.0

Ponto flutuante

Strings

Strings podem ser escritas no nasm de três formas diferentes:

Representação

Explicação

"String"

String normal

'String'

String normal, equivalente a usar "

`String\n`

String que aceita caracteres de escape no estilo da linguagem C.

Os dois primeiros s√£o equivalentes e n√£o tem nenhuma diferen√ßa para o nasm. O √ļltimo aceita caracteres de escape no mesmo estilo da linguagem C.

Formato das instru√ß√Ķes

As instru√ß√Ķes em Assembly seguem a premissa de especificar uma opera√ß√£o e seus operandos. Na arquitetura x86 uma instru√ß√£o pode n√£o ter operando algum e chegar at√© tr√™s operandos.

operação operando1, operando2, operando3

Algumas instru√ß√Ķes alteram o valor de um ou mais operandos, que pode ser um endere√ßamento na mem√≥ria ou um registrador. Nas instru√ß√Ķes que alteram o valor de apenas um operando ele sempre ser√° o operando mais √† esquerda. Um exemplo pr√°tico √© a instru√ß√£o mov:

mov eax, 777

O mov especifica a operação enquanto o eax e o 777 são os operandos. Essa instrução altera o valor do operando destino eax para 777. Exemplo de pseudo-código:

eax = 777;

Da mesma forma que não é possível fazer 777 = eaxem linguagens de alto nível, também não dá para passar um valor numérico como operando destino para mov. Ou seja, isto está errado: mov 777, eax

Endereçamento

O endereçamento em Assembly x86 é basicamente um cálculo para acessar determinado valor na memória. O resultado deste cálculo é o endereço na memória que o processador irá acessar, seja para ler ou escrever dados no mesmo. Usá-se os colchetes [] para denotar um endereçamento. Ao usar colchetes como operando você está basicamente acessando um valor na memória. Por exemplo poderíamos alterar o valor no endereço 0x100 usando a instrução mov para o valor contido no registrador eax.

mov [0x100], eax

Como eu já mencionei o valor contido dentro dos colchetes é um cálculo. Vamos aprender mais à respeito quando eu for falar de endereçamento na memória.

Você só pode usar um operando na memória por instrução. Então não é possível fazer algo como: mov [0x100], [0x200]

Tamanho do operando

Quando um dos operandos √© um endere√ßamento na mem√≥ria voc√™ precisa especificar o seu tamanho. Ao fazer isso voc√™ define o n√ļmero de bytes que ser√£o lidos ou escritos na mem√≥ria. A maioria das instru√ß√Ķes exigem que o operando destino tenha o mesmo tamanho do operando que ir√° definir o seu valor, salvo algumas exce√ß√Ķes. No nasm existem palavra-chaves (keywords) que voc√™ pode posicionar logo antes do operando para determinar o seu tamanho.

Nome

Nome estendido

Tamanho do operando (em bytes)

byte

1

word

2

dword

double word

4

qword

quad word

8

tword

ten word

10

oword

16

yword

32

zword

64

Exemplo:

mov dword [0x100], 777

Se voc√™ usar um dos operandos como um registrador o nasm ir√° automaticamente assumir o tamanho do operando como o mesmo tamanho do registrador. Esse √© o √ļnico caso onde voc√™ n√£o √© obrigado a especificar o tamanho por√©m em algumas instru√ß√Ķes o nasm n√£o consegue inferir o tamanho do operando.

Pseudo-instru√ß√Ķes

No nasm existem o que s√£o chamadas de "pseudo-instru√ß√Ķes", s√£o instru√ß√Ķes que n√£o s√£o de fato instru√ß√Ķes da arquitetura x86 mas sim instru√ß√Ķes que ser√£o interpretadas pelo nasm. Elas s√£o √ļteis para deixar o c√≥digo em Assembly mais vers√°til mas deixando claro que elas n√£o s√£o instru√ß√Ķes que ser√£o executadas pelo processador. Exemplo b√°sico √© a pseudo-instru√ß√£o db que serve para despejar bytes no correspondente local do arquivo bin√°rio de sa√≠da. Observe:

db 0x41, 0x42, 0x43, 0x44, "String", 0

D√° para especificar o byte como um n√ļmero ou ent√£o uma sequ√™ncia de bytes em formato de string. Essa pseudo-instru√ß√£o n√£o tem limite de valores separados por v√≠rgula. Veja a sa√≠da do exemplo acima no hexdump, um visualizador hexadecimal:

Rótulos

Os r√≥tulos, ou em ingl√™s labels, s√£o defini√ß√Ķes de s√≠mbolos usados para identificar determinados endere√ßos da mem√≥ria no c√≥digo fonte em Assembly. Podem ser usados de maneira bastante parecida com os r√≥tulos em C. O nome do r√≥tulo serve para pegar o endere√ßo da mem√≥ria do byte seguinte a posi√ß√£o do r√≥tulo, que pode ser uma instru√ß√£o ou um byte qualquer produzido por uma pseudo-instru√ß√£o. Para escrever um r√≥tulo basta digitar seu nome seguido de dois-pontos :

meu_rotulo: instrução/pseudo-instrução

Voc√™ pode inserir instru√ß√Ķes/pseudo-instru√ß√Ķes imediatamente ap√≥s o r√≥tulo ou ent√£o em qualquer linha seguinte, n√£o faz diferen√ßa no resultado final. Tamb√©m √© poss√≠vel adicionar um r√≥tulo no final do arquivo, o fazendo apontar para o byte seguinte ao conte√ļdo do arquivo na mem√≥ria. J√° vimos um exemplo pr√°tico de uso de r√≥tulo na nossa PoC:

bits 64

global assembly
assembly:
  mov eax, 777
  ret

Repare o r√≥tulo assembly na linha 4. Nesse caso o r√≥tulo est√° sendo usado para denotar o s√≠mbolo que aponta para a primeira instru√ß√£o da nossa fun√ß√£o hom√īnima.

Rótulos locais

Um rótulo local, em inglês local label, é basicamente um rótulo que hierarquicamente está abaixo de outro rótulo. Para definir um rótulo local podemos simplesmente adicionar um ponto . como primeiro caractere do nosso rótulo. Veja o exemplo:

meu_rotulo:
  mov eax, 777
.subrotulo:
  mov ebx, 555

Dessa forma o nome completo de .subrotulo √© na verdade meu_rotulo.subrotulo. As instru√ß√Ķes que estejam hierarquicamente dentro do r√≥tulo "pai" podem acessar o r√≥tulo local usando de sua nomenclatura com . no in√≠cio do nome ao inv√©s de citar o nome completo. Como no exemplo:

meu_rotulo:
  jmp .subrotulo
  mov eax, 777

.subrotulo:
  ret

Não se preocupe se não entendeu direito, isso aqui é apenas para ver a sintaxe. Vamos aprender mais sobre os rótulos e símbolos depois.

Diretivas

Parecido com as pseudo-instru√ß√Ķes, o nasm tamb√©m oferece as chamadas diretivas. A diferen√ßa √© que as pseudo-instru√ß√Ķes apresentam uma sa√≠da em bytes exatamente onde elas s√£o utilizadas, j√° as diretivas s√£o como comandos para modificar o comportamento do assembler.

Por exemplo a diretiva bits que serve para especificar se as instru√ß√Ķes seguintes s√£o de 64, 32 ou 16 bits. Podemos observar o uso desta diretiva na nossa PoC. Por padr√£o o nasm monta as instru√ß√Ķes como se fossem de 16 bits.

Last updated