Les instructions - index

publicité
TITRE : nasm en résumé
CHAPEAU :
Cet article va peut être vous perturber un peu, vous allez me dire: <<tu répètes souvent les mêmes
choses!>> He bien , c'est voulu ! Ce mag à pour but de vous donner des notions en assembleur, les
bases pour que vous puissiez débuter sainement.
FIN CHAPEAU:
INTRODUCTION:
Si vous lisez cet article en vous disant : «tout ça je connais » alors vous pouvez continuer la suite.
Dans le cas contraire , relisez les articles précédents jusqu'à ce que vous puissiez vous aussi dire
: «tout ça je connais » .
Les instructions:
La directive %define
Cette directive est semblable à celle de la directive #define du C.
%define SIZE 100
%define ch1 dword [ebp+8]
%define ch2 dword [ebp+12]
Nous venons ici, de definir trois variables SIZE, ch1 et ch2 qui vont prendre des valeurs pour tout le
programme.
Directives de données
L1
db
0 ; octet libellé L1 avec une valeur initiale de 0
L2
dw
1000;mot libellé L2 avec une valeur initiale de 1000
L3
db
110101b;octet initialise à la valeur binaire 110101
L4
db
12h;octet initialise à la valeur hexa 12
L5
db
17o;octet initialise à la valeur octale 17
L6
dd
1A92h;double mot initialise à la valeur hexa 1A92
L7
resb
1 ; un octet non initialise
L8
db
''A''; octet initialise avec le code ASCII du A
L9
db
0,1,2,3 ; definit 4 octets
L10
db
''s'',''a'',''l'',''u'',''t'',0; définit une chaîne ''salut''
L11
db
'salut',0 ; idem L10
L12
temps 100 db 0 ; équivalent à 100 (db 0)
L13
resw
100 ; réserve de la place pour 100 mots
Exemples d'utilisation :
mov al,[L1] ; copie l'octet situé en L1 dans al
mov eax,L1 ; EAX=adresse de l'octet en L1
mov [L1],ah ; copie ah dans l'octet en L1
mov dword [L6],1 ; stocke 1 en L6
Cela indique à l'assembleur de stocker un 1 dans le double mot qui commence en L6. Les autres
spécificateurs de taille sont : BYTE, WORD,QWORD et TWORD
Entrées – sorties :
Les langages assembleur n'ont pas de bibliothèques standards. Ils doivent accéder directement au
matériel ou utiliser des routines de bas niveau éventuellement fournies par le systéme
d'exploitation.
Il est très courant pour les routines assembleur d'être interfacées avec du C. Un des avantages de
cela est que le code assembleur peut utiliser les routines d' E/S de la bibliothéque standard du C.
Pour utiliser ces routines, il faut inclure un fichier contenant les informations dont l'assembleur à
besoin pour les utiliser.
%include
''asm_io.inc''
pour utiliser une de ces routines d'affichage, il faut charger EAX avec la valeur correcte et utiliser
une instruction CALL pour l'invoquer
Nous pourrons donc utiliser :
print_int
: affiche à l'écran la valeur d'un entier stocké dans EAX
print_char : pareil pour un code ASCII, stocké dans AL
print_string : pareil pour une chaîne de caractères (stocké dans EAX)
print_nl : aller à la ligne
read_int : lit un entier au clavier et le stocke dans le registre EAX
read_char : lit un caractère au clavier et stocke son code ASCII dans le registre EAX
Cette bibliothèque contient également quelques routines de débogage :
dump_regs : affiche les valeurs des registres, des flags
dump_mem : affiche les valeurs d'une certaine région de la mémoire. Elle prend trois arguments
séparés par des virgules ( un entier utilisé pour étiqueter la sortie, l'adresse à afficher et le nombre
de paragraphes de 16 octets à afficher aprés l'adresse)
dump_stack : affiche les valeurs de la pile du processeur
dump_math : affiche les valeurs des registres du coprocesseur mathématique
Nous lancerons notre programme assembleur à partir d'un programme en C ( progC.c )
ENCADRE: progC.c
int main()
{
int ret_status;
ret_status=asm_main();
return ret_status;
}
FIN ENCADRE
Le squelette de notre programme en assembleur sera de cette forme :( essai.asm)
ENCADRE :essai.asm
%include "asm_io.inc"
segment .data
segment .bss
segment .text
global
asm_main:
enter
pusha
popa
mov
leave
ret
FIN ENCADRE
asm_main
0,0
; setup routine
eax, 0
; return back to C
Dans le segment .data doivent se trouver seulement les données initialisées.
Les données non initialisées se trouveront dans .bss
le segment de code est appelé .text
Compiler
Pour compiler le programme , vous utiliserez cette instruction :
nasm – f
elf
essai.asm
puis
gcc -0 premier progC.c essai.o asm_io.o
pour essayer votre programme :
[FaSm]# chmod u+x premier
[FaSm]# ./premier
Exemple de programme :
%include "asm_io.inc"
segment .data
prompt1 db "Entrez un nombre :",0
prompt2 db "Entrez un deuxieme nombre : ",0
prompt3 db "le résultat est : ",0
segment .bss
nbr
resb 1
segment .text
global _asm_main
_asm_main:
enter 0,0
pusha
mov EAX,prompt1
call print_string
call read_int
mov [nbr],EAX
mov EAX,prompt2
call print_string
call read_int
add EAX,[nbr]
mov EBX,EAX
mov EAX,prompt3
mov EAX,EBX
call print_int
call print_nl
dump_regs 1
dump_mem 2,prompt3,2
popa
mov EAX,0
leave
ret
LES INTERRUPTIONS
Les interruptions sous linux (int 0x80) sont détaillées sur le site cité en prérequis.
http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html
ILLUSTRATION: interruptions.jpg
légende : les interruptions
Voyons par l'exemple :
ENCADRE : bonjour tout le monde en C
Int main() {
char *string='bonjour, tout le monde! ';
write(1,string,23);
}
FIN ENCADRE
Nous voulons en assembleur, obtenir la même chose que le programme en C (cf encadré). Il nous
faut donc utiliser des interruptions pour, par exemple, écrire à l'écran une phrase ou quitter le
programme (exit).
Nous allons obtenir le programme en assembleur donné en encadré.
ENCADRE: bonjour totu le monde ! en assembleur
Xor eax,eax
xor ebx,ebx
xor ecx,ecx
xor edx,edx
jmp short string
code:
pop ecx
mov bl,1
mov dl,23
mov al,4
int 0x80
dec bl
mov al,1
int 0x80
string :
call code
db 'bonjour, tout le monde!'
FIN ENCADRE
La partie XOR eax, eax par exemple, sert simplement à mettre à zéro le registre eax. Une bonne
habitude est de mettre a zéro les registres que nous voulons utiliser.
Si l'on regarde dans la table des syscalls donnée plus haut, le write est de la forme :
write(1,string,23)
si l'on veut utiliser la fonction write, il faut mettre 4 dans al. Mais au préalable, il faut lui indiquer
quelques arguments.
D'abord , voulons nous travailler avec l'entrée standard (le clavier : 0) ou la sortie standard (l'ecran
:1) ou enfin avec la sortie d'erreur (2) ? nous voulons bien sur visualiser sur l'écran, nous mettrons
donc 1 dans bl.
Il faut ensuite indiquer la taille de la chaîne de caractère que nous voulons écrire, soit pour nous 23
que nous plaçons dans dl.
Il nous reste ensuite à placer dans ecx l'adresse de la chaîne de caractère. Ce qui est fait dans
l'exemple (cf encadré) par une petite astuce: nous faisons d'abord un jmp short string qui va
permettre de sauter a l'étiquette string: qui fait appelle à code .
Ce mécanisme permet de mettre dans la pile , l'adresse de retour soit l'adresse de db 'bonjour,
tout le monde!'.
Dés l'arrivée dans le sous programme code, nous faisons un pop ecx qui permet de placer dans
ecx l'adresse voulue.
Nous pouvons donc maintenant appeler l'interruption int 0x80 qui va donc écrire à l'écran la phrase
voulue.
L'interruption int 0x80 est utilisée aussi pour sortir du programme (exit() ) en plaçant 0 dans bl
(dec bl qui valait 1) et en plaçant 1 dans al.
CONCLUSION:
Nous venons de refaire un petit tour d'horizon de ce qui est nécessaire de savoir pour pouvoir
aborder les articles suivants, surtout sur les buffer overflows. Si vous éprouvez encore des
difficultés à comprendre, n'hésitez pas à me contacter sur le forum : http://www.acissi.net
Franck Ebel | FaSm
Téléchargement