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