domingo, 8 de marzo de 2015

Introducción X86 Asembler

Introducción X86 Asembler


En este curso usaremos el asembler nasm porque usa la misma sintaxis que intel.
Asembler a diferencia del compilador traslada un lenguaje a código de maquina.
El asembler de Intel se llama MASM pero es propietario.
En primer lugar debemos instalar al nasm.

sudo apt-get nasm install


unc@HP:~$ sudo apt-get install nasm<
[sudo] password for dbritos: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
nasm is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 11 not upgraded.

Directivas


El nasm tiene directivas parecidas al compilador de Intel, directivas son sentencias que las interpreta el asembler pero no son convertidas en codigo.

symbol equ value

A "symbol" le asocia el valor "value". Ojo symbol no es un lugar de memoria

%

Es muy similar a ¨C¨ #define y es usada para constantes en macros

Directivas de datos


Realizan una reserva de memoria, el lugar de memoria queda etiquetado.


M1              db      23        ;etiqueta el lugar de memoria y lo
                                   inicializa con 23 en decimal.
Ml  times 11    db      23        ;repite la anterior 11 veces
M2              dw    5698        ;reserva un word y lo inicializa 
                                   a 5698 decimal
L1              dw   5A9Dh        ;reserva un word y lo inicializa a 
                                   5A9D hexa
CASA            dd  23456o        ;reserva un doble word y lo 
                                   inicializa a 23456 en octal
pi              dq 314159265359   ;reserva un cuadruple word
PPbuf           resb   512        ;reserva 512 bytes de memoria.
PPbuf           resw   256        ;igual que la anterior
LTable          db     "A"        ;reserva un byte con valor ASCII
                                  ; 65
Varios          db 0,1,5,7        ;reserva 4 baytes
Hello           db 'Hello world,0 ;reserva una cadena inicializada 
                                   Hello world.


Algunas diferencias con MASM


Los parentices "[]" indican la direccion apuntada por.

mov    al, [M2]     ; copy el valor apuntado por la dirección M2 a al registro al

Primer programa


Vamos a escribir el programa básico para escribir en pantalla " Hello World", usando las interrupciones 80 hex[1] del kernel de linux.
Para escribir el programa se puede usar cualquier editor vi, gedit o sasm.



;  hello.asm  a first program for nasm for Linux, Intel, gcc
;
; assemble: nasm -f elf -l hello.lst  hello.asm
; link:  gcc -o hello  hello.o
; run:         hello 
; output is: Hello World 

 SECTION .data  ; seccion de datos
msg: db "Hello World",10 ;la cadena a imprimir, 10=retorno de carro cr
len: equ $-msg           ; "$" significa esta direccion
                         ;len es un valor numerico

 SECTION .text           ;seccion de codigo
        global main      ;main es un rotulo disponible para el 
                         ;linkeador
main:                    ;entrada estandar para gcc 
 
 mov edx,len             ;arg3, longitud de la cadena a escribir
 mov ecx,msg             ;arg2, puntero a la cadena
 mov ebx,1               ;arg1, dispositivo a escribir panatalla
 mov eax,4               ;write sysout comando a la int 80 hex
 int 0x80                ;interrupcion 80 hex, llamodo al kernel 
                         ;de linux
 
 mov ebx,0               ;codigo de salida, 0=normal
 mov eax,1               ;comando de salida al sistema 
 int 0x80                ;interrupcion 80 hex,llamodo al kernel 
                         ;de linux 


Luego se lo asembla con nasm y se lo linkea con ld o gcc en este caso usamos la llave m32 porque el codigo es de 32 bit y lo debemos linkear para una arquitectura de 64 bits.


dbritos@HP:~/Dropbox/nasm$ nasm -f elf -l hello.lst  hello.asm
dbritos@HP:~/Dropbox/nasm$  gcc -m32 -o hello  hello.o
dbritos@HP:~/Dropbox/nasm$ ./hello
Hello World
dbritos@HP:~/Dropbox/nasm$ 


En el programa anterior si estamos es un sistema x64 podemos llamar a "syscall" en vez de "int 0x80"

Debuggin


Instalamos el debugger para linux "gdb"


dbritos@HP:~/Dropbox/nasm$ sudo apt-get install gdb
[sudo] password for dbritos: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
gdb is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 11 not upgraded.
dbritos@HP:~/Dropbox/nasm$ 


Para debugear el programa al asemblarlo y linkearlo debemos agregar la llave  -g y lanzamos el debugger con gdb y la llave -quiet



dbritos@HP:~/Dropbox/nasm$ nasm -g -f elf -l hello.lst  hello.asm
dbritos@HP:~/Dropbox/nasm$ gcc -m32 -g -o hello  hello.o
dbritos@HP:~/Dropbox/nasm$ gdb -quiet hello
Reading symbols from hello...done.


Instalamos un breakpoint en la etiqueta "main" sino al correr el programa iria hasta el final.

(gdb) break main
Breakpoint 1 at 0x80483f0
(gdb) 

Corremos el programa mediante el comando "run" y este se detiene en el breackpoint de la etiqueta "main"


(gdb) run
Starting program: /home/dbritos/Dropbox/nasm/hello 
Breakpoint 1, 0x080483f0 in main ()
(gdb) 

Para ver el programa primero le debemos decir en que formato lo queremos ver con set dissaembly-flavor intel le decimos que el formato es "INTEL" luego ejecutamos el comando "disassembly main" con esto le decimos que va a comenzar en la etiqueta "main" con el símbolo "=>" nos señala la próxima instrucción a ejecutar.


(gdb) set disassembly-flavor intel
(gdb) 




(gdb) disassemble main
Dump of assembler code for function main:
=> 0x080483f0 <+0> : mov    edx,0xc
   0x080483f5 <+5> : mov    ecx,0x804a01c
   0x080483fa <+10>: mov    ebx,0x1
   0x080483ff <+15>: mov    eax,0x4
   0x08048404 <+20>: int    0x80
   0x08048406 <+22>: mov    ebx,0x0
   0x0804840b <+27>: mov    eax,0x1
   0x08048410 <+32>: int    0x80
   0x08048412 <+34>: xchg   ax,ax
   0x08048414 <+36>: xchg   ax,ax
   0x08048416 <+38>: xchg   ax,ax
   0x08048418 <+40>: xchg   ax,ax
   0x0804841a <+42>: xchg   ax,ax
   0x0804841c <+44>: xchg   ax,ax
   0x0804841e <+46>: xchg   ax,ax
End of assembler dump.
(gdb) 

Si queremos ver los bytes el comando "x/40xb main" nos muestra 40 bytes de la memoria en formato hexadecimal a partir de la etiqueta "main"


(gdb) x/40xb main
0x80483f0 <main>   : 0xba 0x0c 0x00 0x00 0x00 0xb9 0x1c 0xa0
0x80483f8 <main+8> : 0x04 0x08 0xbb 0x01 0x00 0x00 0x00 0xb8
0x8048400 <main+16>: 0x04 0x00 0x00 0x00 0xcd 0x80 0xbb 0x00
0x8048408 <main+24>: 0x00 0x00 0x00 0xb8 0x01 0x00 0x00 0x00
0x8048410 <main+32>: 0xcd 0x80 0x66 0x90 0x66 0x90 0x66 0x90
(gdb) 

Para observar el contenido de los registros ejecutamos "info registers"


(gdb) info registers
eax            0x1 1
ecx            0x9458ca20 -1806120416
edx            0xffffd584 -10876
ebx            0xf7faa000 -134569984
esp            0xffffd55c 0xffffd55c
ebp            0x0 0x0
esi            0x0 0
edi            0x0 0
eip            0x80483f0 0x80483f0 
eflags 0x246 [ PF ZF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99 (gdb)


Si queremos ver solo un registro en hexadecimal ejecutamos "print/x $nombre del registro"

(gdb) print/x $esp
$1 = 0xffffd55c
(gdb) 

Para ejecutar una sola instrucción "nexti"


(gdb) nexti
0x080483f5 in main ()
(gdb) 


Alli ejecutamos las instrucciones para que carge los registros ecx y edx.

(gdb) nexti
0x080483f5 in main ()
(gdb) nexti
0x080483fa in main ()
(gdb) print/x $ecx
$2 = 0x804a01c
(gdb) print/x $edx
$3 = 0xc
(gdb) 

gdb GUI


Si iniciamos el debugaer con la opcion gdb --tui arranca en modo GUI como puede verse en la siguiente figura.


Otra version de "GUI" para "gdb" es el "ddd"

1 - http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html



Un ejemplo para averiguar el nombre del procesador usando el comando "CPUID"


section .text

GLOBAL main
main:
        xor eax,eax       ; place 0x0 in EAX for getting the name of the processor 
        cpuid
        shl  rdx,0x20     ; shifting lower 32-bits into upper 32-bit of RDX
        xor  rdx,rbx      ; moving EBX into EDX
        push rcx          ; push the string on the stack
        push rdx
        mov  rdx, 0x10    ; since we are pushing 2 registers, the length is not more than 16 bytes.
        mov  rsi, rsp     ; The address of the string is RSP because the string is on the stack
        push 0x1          ; The system call write() has the value 0x1 in the sytem call table
        pop  rax
        mov  rdi, rax     ; Since we are printing to stdout, the value of the file descriptor is also 0x1
        syscall           ; make the system call
        mov  rax, 0x3c    ; We now make the exit() system call here.
        xor  rdi, rdi     ; the argument is 0x0
        syscall           ; this exits the application and gives control back to the shell or the Operating system

No hay comentarios:

Publicar un comentario