Assembly (x86)

Assembly vs Assembler

Antes de começarmos vamos esclarecer um ponto, a diferença entre Assembly e Assembler.

O Assembly é uma linguagem de programação que irá ser escrito de uma forma que seja entendível por humanos e, então, será convertido para linguagem de máquina através do Assembler. Esses arquivos desenvolvidos em Assembly irá conter a extensão .asm.

Já o Assembler tem um papel diferente, pois este converte código desenvolvido em linguagem Assembly para linguagem de máquina. Após utilizamos o Assembler, irá ser gerado um Object File, que é uma representação binária do programa. Feito, será preciso utilizar um linker para combinar (juntar) os arquivos necessários para gerar o executável final (isso é necesário quando temos chamadas externas, por exemplo, de arquivos .dll situadas na system32).

Compilando em Assembly

Primeiro precisamos escrever um código asm (Assembly), transformar em arquivo obj (linguagem de máquina) e por último fazer um linker (arquivo exe), para associar o código com as bibliotecas, como por exemplo, o arquivo User32.dll, presente na system32.

nasm -f win32 <file.asm> -o <file.obj>
golink /entry _main <file.obj>

Caso utilize algum recurso, como por exemplo o sleep (que utiliza Kernel32.dll) iremos ter que adicionar a algumas informações a mais no comando.

nasm -f win32 <file.asm> -o <file.obj>
golink /console /entry _main <file.obj> Kernel32.dll

Registradores

Os processadores têm seu próprio conjunto de variáveis especiais chamadas Registradores. A maioria das instruções usam esses Registros para ler ou gravar dados, portanto é essencial entender os Registradores de um processador para então entender as Instruções (mais detalhes logo abaixo).

REGISTRADOR

DESCRIÇÃO

EAX

Utilizado em operações aritméticas e guardar resultados. É o registrador acumulador

EBX

É um Registrador secundário, usado como "base" em operações de vetores

ECX

Contador utilizado em Loops

EDX

Guarda endereço de dados

ESP

Este fica sempre no topo da Stack e é conhecido como Stack Pointer, é chamado Ponteiro porque armazenam endereços de 32 bits, que apontam essencialmente para esse local na memória. Esse registro é bastante importante para a execução de programas e gerenciamento de memória

EBP

Conhecido como Base Pointer e assim como o ESP, este também é chamado Ponteiro porque armazenam endereços de 32 bits

ESI

Conhecido como Source Index, tecnicamente também é um Ponteiro que é comumente utilizado para apontar para a origem e o destino quando os dados precisam ser lidos ou gravados

EDI

Conhecido como Destination Index, tecnicamente também é um Ponteiro, pois trabalha de forma semelhante ao ESI.

EIP

Instruction Pointer (Program Counter), indica em qual instrução do código a execução se encontra no momento

EFLAGS

Guarda resultado de operações, por exemplo, condições feita em um if, onde o resultado dessa condição não é armazenada em nenhuma variável

Ao realizarmos um Disassembler, podemos ver os Registradores divididos em partes, ou seja, utilizando somente uma parte dele. Isso é feito por questões de performance do software. Podemos utilizar como exemplo, o Registrador rax (64 bits), que pode tem uma parte chamada eax (32 bits), que tem uma parte chamada ax (16 bits), que tem duas partes chamadas de ah (8 bits - memória alta) e al (8 bits - memória baixa).

Diferenças Entre Intel e AT&T

Ordem dos Parâmetros

Quando utilizamos o mov, para mover algo, temos uma pequena e importante diferença nessas sintaxes. No exemplo abaixo, ambos os casos ebp irá receber o valor de esp, porém note que eles estão escritos em ordem diferente:

# Intel
mov     ebp,esp

# AT&T
mov     %esp,%ebp

Tamanho dos Parâmetros

Na Intel, o Assembler identifica o tamanho do registrador que foi utilizado, já na AT&T podemos ver sufixos que indicam o tamanho dos operandos (note que é um pushl e não push).

# Intel
push    DWORD PTR [ecx-0x4]

# AT&T
pushl   -0x4(%ecx)

Valores Imediatos

Estes, são valores que não estão armazenadas em variáveis. Podemos usar um exemplo de Valor Imediato no seguinte trecho, onde a string está sendo definida sem passar por uma variável:

printf('Mysther');

Na Intel, o Assembler identiica o tipo automaticamente, enquanto na AT&T possui um $ como prefixo, enquanto os registradores possuem um %.

# Intel
sub     esp,0x10

# AT&T
sub     $0x10,%esp

Instruções

Movimentos

Antes de vermos as Instruções de fato, vamos entender quais são essas movimentação de dados.

MOVIMENTO

DESCRIÇÃO

Imediato para Registrador

Acontece quando setamos o valor de uma variável, exemplo: x = 10;

Registrador para Registrador

Quando passamos o valor de eax para ebx

Imediato para Memória

Quando defimos que vamos escrever em determinada posição da Memória RAM

Registrador para Memória / Memória para Regitrador

Quando movemos um valor da Registrador para a Memória RAM

Tipos de Instruções

Agora podemos ver e entender melhor sobre as instruções.

INSTRUÇÃO

EXEMPLO

DESCRIÇÃO

NOP

NOP

No Operation (\x90), o processador não faz nada. Útil pra utilizar antes da chamada de shellcode

INT3

Interrupção (Breakpoint)

MOV

mov ecx,eax

mov eax, [esp]

Define o valor de ecx com o mesmo valor de eax. No segundo exemplo, joga valor (e não o endereço de memória) de ESP para EAX

ADD

add eax,0x10

Incrementa um valor (imediato) hexadecimal de 10 em eax

SUB

sub eax,0x10

Subtrai 0x10 de eax e armazena o valor em eax

XOR

xor eax,ebx

xor eax,eax

Faz o xor entre eax e ebx e joga o valor em eax. No segundo exemplo, teremos eax com o valor zerado

AND

and eax,ebx

Faz um and entre eax e ebx e joga o valor em eax

CMP

cmp eax,ebx

Compara eax e ebx e seta flags

JE

je 0x40084

Jump Equal = Pula para o endereço informado se a eflag estiver setado como igual na instrução cmp

JNE

jne 0x40084

Jump Not Equal = Semelhante ao JE, porém este irá mudar de endereço caso a instrução cmp não seja igual

JMP

jmp 0x40084

Jump = Pula para o endereço informado, independente de condições em CMP

CALL

call 0x40084

Pula para o endereco definido e salva a sua localização atual

RET

ret

Retorna para o endereço salvo com a instrução call

LEA

lea eax,[esp+0x1c]

Semelhante ao mov, porém ao invés de mover o valor, esse move o endereço de um registrador para outro

PUSH

PUSH 41424344

Coloca no topo da Stack

POP

POP EBX

Remove do topo da stack e joga o valor para EBX

Compilando

Primeiro precisamos escrever um código asm (Assembly), transformar em arquivo obj (linguagem de máquina) e por último fazer um linker para gerar o arquivo exe e associar o código as devidas bibliotecas, como por exemplo, o arquivo User32.dll, presente no system32.

nasm -f win32 <file.asm> -o <file.obj>
golink /entry _main code.obj

No exemplo acima não utilizamos nenhuma biblioteca externa. No exemplo abaixo, estamos compilando um código que utiliza o Sleep, presente em Kernel32.dll, então o comando ficaria da seguinte forma.

nasm -f win32 <file.asm> -o <file.obj>
golink /console /entry _main <file.obj> Kernel32.dll

Permissões

Antes de começarmos, é sempre bom verificar as permissões que o binário possui

checksec <file>

Disassemble

Objdump

Por padrão, será utilizado o AT&T.

# AT&T
objdump -D <file>

# Intel
objdump -D -M intel <file>

Outra maneira

objdump -D Minte-l,i386 b binary -m i386 <file>
readelf -h <file>

arwin

Busca pelo endereço de memória que está sendo utilizado para chamar determinada DLL.

arwin <file.dll> <binary>

Sites

https://wiki.osdev.org/Calling_Conventions

# Ferramenta para monitorar as API's do Windows que estão em uso
http://www.rohitab.com/

Last updated