Partie 25 - Annexes
Annexes
Dans cette page, vous trouverez plusieurs informations regroupées ensemble dont on a pu parler lors de ce cours :
- des astuces Ida
- des astuces gdb
- les principales instructions x86
Si vous cherchez une info ou commande bien prĂ©cise, nâhĂ©sitez pas Ă utiliser
Ctrl+Fđ.
Astuces IDA
Astuce IDA : Vous pouvez utiliser le raccourcis
Npour renommer une fonction, un label ou une variable en ayant préalablement cliqué dessus avant de la renommer.
Astuce IDA : Pour modifier le type dâune fonction ou dâune variable, il suffit de cliquer dessus et dâappuyer sur
Y.
Astuce IDA : Le raccourcis permettant dâassigner Ă des constantes des Ă©numĂ©rations est
M.
Astuce IDA : Il est possible de mettre un commentaire sur la mĂȘme ligne que lâinstruction sĂ©lectionnĂ©e dans la fenĂȘtre de dĂ©compilation avec le raccourcis
/.Dans la fenĂȘtre du code dĂ©sassemblĂ©, cela est possible avec
:ou;.
Astuce IDA : Vous pouvez utiliser le raccourcis
Inserpour saisir un commentaire avant lâinstruction sĂ©lectionnĂ©e.
Astuce IDA : En utilisant la touche
EntrĂ©e, vous pouvez ajouter des sauts de lignes, pratique lorsque lâon souhaite espacer le code.
Astuce IDA : Les variables nommées
v1,v2etc. correspondent Ă des variables locales dâune fonction tandis que les variablesa1,a2etc. correspondent aux arguments de la fonction.
Astuce IDA : Vous pouvez utiliser le raccourcis
Gpour aller Ă une adresse en particulier.
Astuce IDA : Vous pouvez utiliser le raccourcis
espacepour basculer du mode âgrapheâ vers le mode âtexteâ et inversement.
Astuce IDA : En mode âgrapheâ, vous pouvez modifier la couleur des blocs de base en cliquant sur lâicĂŽne la plus Ă gauche en haut du bloc.
Astuce IDA : Parfois, au lieu dâafficher une chaĂźne de caractĂšres, IDA affiche un offset en mĂ©moire plutĂŽt que la
stringdirectement. Pour y remĂ©dier, aller dansEditâĄïžPluginsâĄïžHex-Rays DecompilerâĄïžOptionsâĄïžAnalysis options 1et dĂ©cocherPrint only constant string literals.
Astuce IDA : Il est souvent intĂ©ressant dâavoir les deux onglets dĂ©sassembleur / dĂ©compilateur sur la mĂȘme vue. Vous pouvez faire cela en dĂ©plaçant lâun des deux onglets. Vous pouvez ensuite synchroniser les deux vues en faisant un clic droit dans la fenĂȘtre de dĂ©compilation et en cliquant sur
Synchronize with > IDA View.De cette maniĂšre, lorsque vous cliquerez sur un ligne ou que vous changerez de fonction, IDA affichera la ligne adĂ©quate dans la fenĂȘtre de dĂ©sassemblage.
Astuce IDA : Pour dĂ©sactiver (ou rĂ©activer) le cast des variables, câest le raccourcis
Alt Gr + \. Cela permet dâavoir du code plus lisible.Mais attention, parfois les casts donnent des informations importantes, notamment lorsque lâon souhaite reprogrammer un algorithme en C, Python ou autre, il est nĂ©cessaire de faire attention Ă la taille des variables.
Astuce IDA : Une fois que vous avez trouvĂ© lâadresse de base de votre programme, il suffit, dans IDA, dâaller dans
EditâĄïžSegmentsâĄïžRebase programpuis saisir lâadresse de base trouvĂ©e dans gdb aveclibset cliquer surOk.
Astuces gdb
Certaines de ces commandes sont propres Ă
pwndbg.
Liste des formats :
- o : octal
- x : hexadécimal
- u : décimal non signé
- t : binaire
- f : nombre Ă virgule (ou flottant)
- a : adresse
- c : char
- s : chaĂźne de caractĂšres
Tailles définies dans gdb :
| Abréviation | Signification | Taille (en octets) |
|---|---|---|
b | byte | 1 |
h | half word | 2 |
w | word | 4 |
g | giant word | 8 |
Astuce gdb : Si un programme accepte des arguments via
argv, il est possible de les spécifier lors de la commanderun.Exemple :
run arg1 arg2
Astuce gdb : La commande
hb *0xaddr(hardware breakpoint) permet dâinsĂ©rer un point dâarrĂȘt matĂ©riel Ă lâadresse0xaddr.
Astuce gdb : Vous pouvez utiliser
i b(pourinfo breakpoints) afin de lister les points dâarrĂȘts du programme.Cela est trĂšs utile pour sây retrouver. Chaque point dâarrĂȘt ayant un numĂ©ro unique, il sera affichĂ© dans cette commande.
Astuce gdb : Pour supprimer un point dâarrĂȘt vous pouvez utiliser
d N(pourdelete N) afin de supprimer le breakpoint numéroN.
Astuce gdb : Vous pouvez lister les zones mémoire mappées avec la commande
libs.
Astuce gdb : Lâinstruction
startipermet de charger le programme en mĂ©moire et de sâarrĂȘter Ă la premiĂšre instruction de ce dernier, sans lâexĂ©cuter.
Astuce gdb : Vous pouvez quitter gdb avec les commandes
quitouexit. De maniĂšre plus rapide, vous pouvez utiliserCtrl+D.
Astuce gdb : Pour exĂ©cuter lâinstruction courante et sâarrĂȘter Ă la prochaine, il est possible dâutiliser
siouni(pourstep instructionetnext isntruction).La diffĂ©rence entre les deux est que lors de lâappel dâune fonction,
niexĂ©cute la fonction jusquâau retour alors quesientre dans la fonction et sâarrĂȘte Ă la premiĂšre instruction.
Astuce gdb : Le fait de saisir Ă chaque fois
sipour avancer dâune instruction peut ĂȘtre fastidieux đ€. Vous pouvezspammerutiliser la toucheEntrĂ©edans le terminal gdb afin de rĂ©-exĂ©cuter la derniĂšre commande que vous avez lancĂ©e prĂ©cĂ©demment.
Astuce gdb : Vous pouvez utiliser la commande
c(oucontinue) pour poursuivre lâexĂ©cution du processus jusquâĂ arriver Ă un point dâarrĂȘt.
Astuce gdb : Vous pouvez utiliser le raccourcis
fin(oufinish) pour finir lâexĂ©cution dâune fonction jusquâĂ atteindre lâadresse de retour et sây arrĂȘter.
Astuce gdb : La commande
p(ouSi la valeur à afficher est une adresse (ou pointeur), elle ne sera pas déréférencée.
Astuce gdb : Pour afficher un registre, il suffit de le préfixer avec le signe
$. Exemple :print $reg.
Astuce gdb : Vous pouvez utiliser le raccourcis
x( pourexplore) afin dâexaminer le contenu dâune zone mĂ©moire.
Astuce gdb : Vous pouvez spĂ©cifier un nombre dâĂ©lĂ©ments Ă afficher avant les formats afin dâafficher plus ou moins de donnĂ©es en mĂ©moire.
Le nombre dâĂ©lĂ©ments Ă afficher ainsi que la taille ne sont utilisables quâavec
x. Cela ne fonctionnera pas avec
Astuce gdb : Avec
x, vous pouvez Ă©galement donner en argument une expression avec des opĂ©rations (addition, soustraction, multiplication âŠ).Cela peut ĂȘtre pratique pour afficher une donnĂ©e dans un tableau dont on connait lâindex et lâadresse de base. Par exemple, pour afficher la 5Ăšme case dâun tableau dâĂ©lĂ©ments de 64 bits :
x 0x401000+8*5(en supposant que le tableau soit stockĂ© Ă partir de lâadresse0x401000).
Astuce gdb : La commande
searchde pwndbg permet de rechercher des motifs en mémoire.
Astuce gdb : La commande
setpermet dâĂ©crire dans des registres, variables et la mĂ©moire.
Astuce gdb : Vous pouvez utiliser
rel(pourreload) afin de rafraĂźchir la GUI de pwndbg et voir les changements effectifs.
Astuce gdb : Pour modifier une zone mĂ©moire pointĂ©e par un registre, il est possible dâutiliser
set *$reg = value.Pour modifier directement les données pointées par une adresse :
set *0xaddr = value.
Astuce gdb : Si vous ne souhaitez modifier quâun seul octet (au lieu de 4 par dĂ©faut) vous devez le spĂ©cifier. Exemple :
set {byte}0x401020 = 0xf5.
Instructions x86
mov reg_d, value
Opérandes
reg_d: registre de destinationvalue: valeur immédiate (ou concrÚte, constante).
Détails
Cette forme est la plus simple : elle affecte la valeur value au registre de destination reg_d.
Câest une maniĂšre de rĂ©aliser des affectations de valeurs concrĂštes (immĂ©diates).
Exemple
Imaginons que eax vaille 0xaabbccdd puis que lâon exĂ©cute lâinstruction mov eax, 0xdeadbeef. Alors la valeur de eax deviendra 0xdeadbeef.
Ăquivalent en C
1
2
3
4
5
// Initilisation du registre
int x = 0xaabbccdd; // eax
// Equivalent de : mov eax, 0xdeadbeef
x = 0xdeadbeef;
mov reg_d, reg_s
Opérandes
reg_d: registre de destinationreg_s: registre source
Détails
Le contenu du registre source reg_s est copié dans le registre de destination reg_d.
Câest une maniĂšre dâaffecter le contenu dâune variable Ă une autre.
Exemple
1
2
3
4
mov eax, 0xaabbccdd
mov ebx, 0x11223344
mov ebx, eax ; ebx == 0xaabbccdd
Ăquivalent en C
1
2
3
4
5
6
// Initilisation des registres
int a = 0xaabbccdd; // eax
int b = 0x11223344; // ebx
// Equivalent de : mov ebx, eax
b = a; // b = 0xaabbccdd
mov reg_d, [reg_p]
Opérandes
reg_d: registre de destinationreg_p: registre pointant vers une zone mémoire
Détails
Cette forme est un peu plus complexe que les précédentes car elle fait appel à la notion de pointeur.
Ici reg_d est le registre de destination qui recevra une valeur, jusque-là rien de bien nouveau. Par contre, reg_p ne contient pas la valeur qui sera copiée mais un pointeur vers la valeur en question.
Ainsi, câest la valeur pointĂ©e par reg_p qui est copiĂ©e dans reg_d.
Câest une maniĂšre de lire des donnĂ©es depuis la mĂ©moire.
Exemple
Imaginons que je veuille exécuter ces instructions :
1
2
3
4
mov eax, 0x700000F0 ; 0x700000F0 -> 0x1a2b3c4d
mov ebx, 0xcafebabe
mov ebx, [eax]
On suppose Ă©galement que lâadresse 0x700000F0 pointe vers lâentier de 4 octets 0x1a2b3c4d. Lorsque la derniĂšre instruction mov ebx, [eax] sera exĂ©cutĂ©e, alors ebx vaudra 0x1a2b3c4d. Vous voyez la logique ?
LégÚres variantes
Il existe quelques variantes oĂč un offset (positif ou nĂ©gatif) est ajoutĂ© au registre reg_p, par exemple :
1
2
mov edx, [eax + 8]
mov ecx, [esi - 0x2000]
Ăquivalent en C
Cette forme est trĂšs similaire Ă lâutilisation de pointeurs en C :
1
2
3
4
5
6
7
8
9
// Initilisation des registres
int *a = 0x700000f0; // eax
int b = 0xcafebabe; // ebx
// Initilisation de la mémoire
*a = 0x1a2b3c4d;
// Equivalent de : mov ebx, [eax]
b = *a; // b = 0x1a2b3c4d
mov [reg_p], reg_s
Opérandes
reg_p: registre pointant vers une zone mémoirereg_s: registre source
Détails
Normalement, si vous avez bien saisi le principe de lâinstruction mov reg_d, [reg_p] vous devriez deviner le fonctionnement de celle-ci.
En fait il sâagit de lâinverse de la prĂ©cĂ©dente instruction. En effet, ici on copie la valeur du registre reg_s vers la zone mĂ©moire pointĂ©e par reg_p.
Câest une maniĂšre dâĂ©crire des donnĂ©es en mĂ©moire.
Exemple
Reprenons le précédent exemple, nous avons cette fois-ci :
1
2
3
4
mov eax, 0x700000F0 ; 0x700000F0 -> 0x1a2b3c4d
mov ebx, 0xcafebabe
mov [eax], ebx ; 0x700000F0 -> 0xcafebabe
LégÚres variantes
Il existe quelques variantes oĂč un offset (positif ou nĂ©gatif) est ajoutĂ© au registre reg_p. Il est Ă©galement possible de remplacer reg_s par une valeur immĂ©diate. Par exemple :
1
2
mov [ebp + 8], edi
mov [esi - 0x200], 0xdeadbeef
Ăquivalent en C
1
2
3
4
5
6
7
8
9
// Initilisation des registres
int *a = 0x700000f0; // eax
int b = 0xcafebabe; // ebx
// Initilisation de la mémoire
*a = 0x1a2b3c4d; // 0x700000f0 -> 0x1a2b3c4d
// Equivalent de : mov [ebx], eax
*a = b; // 0x700000f0 -> 0xcafebabe
Résumé des différentes formes de mov
Je sais, ça fait beaucoup dâinformations dâun coup, voici ainsi un rĂ©sumĂ© avec un exemple pour chacun des 4 formes possibles. Supposons que dans les 4 cas lâĂ©tat initial est le suivant :
Alors le résultat est :
Les valeurs en đŽ sont celles qui ont changĂ© lors de lâexĂ©cutions de lâinstruction tandis que celles en â« sont les valeurs Ă lâorigine du changement.
lea reg, [...]
Opérandes
reg: registre de destination[...]: valeur qui est souvent une adresse mémoire
Détails
Cette instruction a ainsi une seule forme oĂč la premiĂšre opĂ©rande est toujours un registre, la seconde opĂ©rande est une valeur qui est souvent une adresse vers une zone mĂ©moire.
Ce que fait lea est tout simplement la copie de lâopĂ©rande de droite, sans la dĂ©rĂ©fĂ©rencer, vers le registre de destination.
Voici quelques exemples :
1
2
3
lea eax, [0x400000] ; ici eax = 0x400000
lea edx, [ebp+8] ; ici edx = ebp +8
lea ecx, [ebx+eax] ; ici ecx = ebx+eax
Exemple
Comme
leane dĂ©rĂ©fĂ©rence pas la seconde opĂ©rande, lâinstructionlea eax, [0x400000]copie bien0x400000danseaxet non pas la valeur pointĂ©e par0x400000.
En fait, plus simplement, lea copie la valeur entre les crochets vers le registre de destination. En dâautres termes, lea reg, [...] est Ă©quivalente Ă mov reg, ....
Jâen vois dĂ©jĂ certains froncer les sourcils đ€š.
Mais si cela est équivalent à faire un
mov, pourquoi se casser la tĂȘte avec une instruction en plus ?
En fait, contrairement Ă mov, lâinstruction lea permet de faire de petites opĂ©rations au niveau de lâopĂ©rande de droite. Par exemple, si je souhaite affecter Ă ecx la somme de ebx et eax en utilisant mov, je suis obligĂ© dâutiliser une instruction supplĂ©mentaire telle que add pour faire lâaddition et ensuite stocker le rĂ©sultat dans ecx avec mov.
Tandis quâavec lea, je peux simplement faire : lea ecx, [ebx + eax]. Vous savez quoi ? On peut mĂȘme faire lea ecx, [ebx + eax*2]đ.
Ainsi, lea permet de :
- Stocker le résultat de simples opérations en écrivant une seule instruction
- De manipuler des adresses en y ajoutant, ou non, un offset
Sâil nây avait quâune seule chose Ă retenir de
lea: il sâagit dâunmovqui copie la âvaleur entre crochetsâ vers la destination.
add reg_d, reg_s
Opérandes
reg_d: registre de destinationreg_s: registre source
Détails
âAddâ en anglais signifie âajouterâ.
Cette instruction réalise ainsi deux actions :
- addition de la valeur du registre source avec celui de destination
- stockage du résultat (la somme) dans le registre de destination
Câest de cette maniĂšre que sont rĂ©alisĂ©es les additions.
Lorsque la somme des deux termes dĂ©passe le plus grand entier que peut stocker le registre de destination, le rĂ©sultat est tronquĂ© pour quâil puisse y ĂȘtre stockĂ©
Exemple
Faisons la somme de 0xf0000034 et 0x20001200 :
1
2
3
4
mov eax, 0xf0000034
mov ebx, 0x20001200
add eax, ebx ; eax = 0x10001234 et non pas 0x110001234 car le résultat est tronqué aux 32 bits de poids faible
Ăquivalent en C
1
2
3
4
5
// Initilisation des registres
int a = 0xf0000034;
int b = 0x20001200;
a = a + b;
Autres formes
Il existe plusieurs autres formes :
add reg, valueadd [ptr], valueadd reg, [ptr]
Leur fonctionnement est toujours le mĂȘme : somme des deux termes et stockage dans lâopĂ©rande de destination.
Toutes les instructions, sauf mention contraire (comme
lea), dĂ©rĂ©fĂ©rencent les pointeurs vers des zones mĂ©moire.Dans les prĂ©cĂ©dentes formes, ce nâest donc pas le pointeur
ptrqui est utilisé dans la somme mais la valeur pointée parptrqui est[ptr](qui serait*ptren C).
and ope_d, ope_s
Opérandes
ope_d: opĂ©rande de destination. Peut ĂȘtre :- un registre
- un pointeur
ope_s: opĂ©rande source. Peut ĂȘtre- une valeur immĂ©diate
- un registre
- un pointeur (vers une zone mémoire)
Détails
Lâinstruction and rĂ©alise un âet logiqueâ entre les bits des deux opĂ©randes. Le rĂ©sultat est ensuite sauvegardĂ© dans la premiĂšre opĂ©rande (qui ne peut donc pas ĂȘtre une valeur immĂ©diate).
Exemple
1
2
3
4
mov eax, 0xff00ff00
mov ebx, 0xabcdef12
and eax, ebx ; eax = 0xab00ef00
Ăquivalent en C
1
2
3
4
int a = 0xff00ff00;
int b = 0xabcdef12;
a = a & b;
Autres formes
Il existe dâautres formes en fonction du type dâopĂ©randes mais le principe est toujours le mĂȘme.
sub ope_d, ope_s
Opérandes
ope_d: opĂ©rande de destination. Peut ĂȘtre :- un registre
- un pointeur
ope_s: opĂ©rande source. Valeur soustraite. Peut ĂȘtre- une valeur immĂ©diate
- un registre
- un pointeur
Détails
âSubâ provient de âsubstractâ qui signifie soustraire.
Cette instruction réalise ainsi deux actions :
- soustraction de lâopĂ©rande source avec lâopĂ©rande de destination
ope_d - ope_s. - stockage du rĂ©sultat (la diffĂ©rence) dans lâopĂ©rande de destination
Câest de cette maniĂšre que sont rĂ©alisĂ©es les soustractions.
Contrairement Ă
add, lâordre des opĂ©randes est important danssub. En effet, en inversant les opĂ©randes, on inverse le signe du rĂ©sultat.
Exemple
Faisons la différence de 0xf0000034 avec 0x10000034 :
1
2
3
4
mov eax, 0xf0000034
mov ebx, 0x10000034
sub eax, ebx ; eax = 0xe0000000
Ăquivalent en C
1
2
3
4
int a = 0xf0000034;
int b = 0x10000034;
a = a - b;
Autres formes
Il existe dâautres formes mais le principe est toujours le mĂȘme.
cmp ope_d, ope_s
Opérandes
ope_d: opĂ©rande de destination. Peut ĂȘtre :- un registre
- un pointeur
ope_s: opĂ©rande source. Peut ĂȘtre :- une valeur immĂ©diate
- un registre
- un pointeur
Détails
La comparaison avec cmp est effectuĂ©e dâune maniĂšre qui peut nous paraĂźtre bizarre. En effet, cmp effectue la soustraction suivante sub ope_d, ope_s mais sans stocker le rĂ©sultat. Ainsi le contenu des opĂ©randes restent inchangĂ©es.
Par contre, quelques flags parmi les EFLAGS vont ĂȘtre changĂ©s en fonction des valeurs des opĂ©randes et du rĂ©sultat. Câest Ă partir de ces EFLAGS que lâon saura si les opĂ©randes sont Ă©gales ou sâil y en a une plus grande/petite que lâautre etc.
Il est important que vous ayez en tĂȘte la maniĂšre dont les entiers sont reprĂ©sentĂ©s en informatique, notamment les entiers signĂ©s avec le complĂ©ment Ă deux.
Plus précisément, ce sont les flags ZF, SF, CF et OF qui nous intéressent principalement (et dans une moindre mesure PF). Nous les avions déjà vus briÚvement précédemment, profitons-en pour nous rafraßchir la mémoire et rentrer plus dans les détails.
ZF(Zero Flag) :- 1 si les deux opérandes sont égales. La différence des deux termes vaut donc 0.
- 0 si les deux opérandes sont différentes.
SF(Sign Flag) :- 1 si le bit de poids fort du rĂ©sultat est non nul. Dans le cas dâune opĂ©ration signĂ©e cela implique quâil est nĂ©gatif. Dans le cas oĂč elle est non signĂ©, ce flag nâa pas dâimportance.
- 0 si le bit de poids fort du résultat est nul
- Exemple : Prenons la soustraction signée suivante :
0x5 - 0x20 = -0x1b. Le rĂ©sultat Ă©tant nĂ©gatif, le complĂ©ment Ă deux de0x1best0xe5qui sâĂ©crit sur 8 bits en binaire0b11100101. Le bit de poids fort Ă©tant Ă1,SFlâest Ă©galement. Etant donnĂ© quâil sâagit dâune opĂ©ration signĂ©eSFnous permet de savoir que le rĂ©sultat est nĂ©gatif.
CF(Carry Flag) :- 1 si le résultat possÚde une retenue.
- 0 si le résultat ne possÚde pas de retenue
- Exemple : Par exemple, pour lâinstruction
add al, blsur 8 bits oĂčalvaut0xFFetblvaut0x01, le rĂ©sultat est0xFF + 0x01 = 0x100qui ne tient pas sur les 8 bit deal. Cela gĂ©nĂšre donc une retenue. Lors dâune soustractiona - b, une retenue est gĂ©nĂ©rĂ©e lorsquebest plus grand quea.
OF(Overflow Flag) :- 1 si un dĂ©bordement a lieu avec des valeurs signĂ©es. Par exemple, cela peut avoir lieu lorsquâil y a un rĂ©sultat nĂ©gatif dâopĂ©randes positifs et inversement. Ce bit nâa pas dâimportance lorsque lâon manipule des valeurs non signĂ©es.
- 0 sâil nây a pas eu de dĂ©bordement
- Exemple : Prenons lâaddition signĂ©e suivante :
0x7F + 0x8 = 0x87. Ici, le bit de poids fort de0x87est Ă1: il sâagit donc dâun rĂ©sultat nĂ©gatif (-121). Pourtant, les deux termes sont strictement positifs. Il y a donc eu un dĂ©bordement (overflow).
PF(Parity Flag) :1si le nombre de bits su résultat est pair0sinon
NâhĂ©sitez pas Ă utiliser asmdebugger pour faire quelques tests. Les 4
flagsĂ©tudiĂ©s sont affichĂ©s sur le site lors de lâexĂ©cution des instructions.En effet, si lâutilisation de ces flags vous paraĂźt difficile, sachez que câest normal car cela fait intervenir des notions que lâon utilise pas, en tant quâhumain, tous les jours comme le complĂ©ment Ă deux pour reprĂ©senter des nombres nĂ©gatifs.
Lors dâune comparaison avec cmp, le processeur ne sait pas si les opĂ©randes sont signĂ©es ou non. En fait, il sâen moque Ă ce stade. Câest pourquoi il va modifier, si besoin est, ces 4 flags bien que certains soient plutĂŽt utilisĂ©s lors dâopĂ©rations signĂ©es (SF et OF) ou non signĂ©es (CF).
Exemples
Voici quelques exemples :
| Instruction | ZF | SF | CF | OF |
|---|---|---|---|---|
cmp 1, 5 | Â | â | â | Â |
cmp 5, 1 | Â | Â | Â | Â |
cmp 5, 5 | â | Â | Â | Â |
cmp 4, 255 | Â | Â | â | Â |
cmp 127, 129 | Â | â | â | â |
Je vous conseille de reprĂ©senter les entiers sous forme binaire et de faire attention Ă la reprĂ©sentation du complĂ©ment Ă deux. En effet, 129 sâil nâest pas signĂ© vaut 129 mais sâil est signĂ©, il vaut -127.
Ăquivalent en C
Pour lâinstruction cmp, il nây a pas rĂ©ellement dâĂ©quivalent en C. En fait, cmp nâest jamais (sauf exceptions) utilisĂ©es autrement quâavec des sauts. Ainsi, reprĂ©senter cmp tout seul dans du code C nâa pas de sens. Par contre, dans toutes les conditions du type if, else vous y trouverez un cmp (ou test) dans le code assembleur associĂ©.
test ope_d, ope_s
Opérandes
ope_d: opĂ©rande de destination. Peut ĂȘtre :- un registre
- un pointeur
ope_s: opĂ©rande source. Peut ĂȘtre :- une valeur immĂ©diate
- un registre
Détails
Cette instruction est également utilisée pour réaliser des comparaisons mais son fonctionnement sous-jacent est différent de cmp.
test va exĂ©cuter lâinstruction and ope_d, ope_s sans stocker le rĂ©sultat mais en mettant Ă jour des flags suivants : SF, ZF et PF. test est souvent utilisĂ© pour savoir si un registre est nul ou non.
Exemple
Lâinstruction test eax, eax permet de voir si eax est nul ou non. En effet, lors de lâexĂ©cution de cette instruction, si ZF == 1, câest que eax est nul. Sinon, cela signifie quâil est non nul.
Ăquivalent en C
MĂȘme remarque que pour cmp : il nây a pas rĂ©ellement dâĂ©quivalent direct en C.
jmp dest
Opérandes
dest: destination du saut. Peut ĂȘtre :- une valeur immĂ©diate (exemple : adresse relative ou absolue)
- un registre
- un pointeur
Détails
Unique instruction permettant de rĂ©aliser des sauts inconditionnels afin de âsauterâ vers lâadresse de destination. Cela permet de pouvoir exĂ©cuter des instructions qui ne sont pas toujours situĂ©es linĂ©airement dans le code.
La différence entre un saut et un appel de fonction
callest que lâon ne se prĂ©occupe pas de sauvegarder lâadresse de retour afin de pouvoir y retourner plus tard.
Lorsque lâopĂ©rande dest est une valeur immĂ©diate, il peut sâagir dâune adresse absolue ou relative :
- adresse absolue : lâadresse est âcodĂ©e en durâ dans lâopcode de lâinstruction. Cela permet de sauter plus loin dans le code mais lâinstruction prend plus de place.
- Exemple :
e9 d8 12 00 00 jmp 0x12dd
- Exemple :
- adresse relative : seule la diffĂ©rence entre lâadresse courante de
eipet lâadresse de destination est insĂ©rĂ©e dans lâopcode. Cela permet dâavoir des opcodes plus courts mais de sauter moins loin.- Exemple :
eb 2a jmp short 0x12DC
- Exemple :
Concernant les adresses absolues, elles ne sont pas insĂ©rĂ©es tel quel dans lâopcode. En effet, il est nĂ©cessaire de prendre en compte la taille de lâinstruction de saut (par exemple 5 octets) avant dâinsĂ©rer lâadresse de destination. Câest pourquoi lâopcode de lâexemple contient
e9 d8 12et non pase9 dd 12.
Bien que le mnĂ©monique jmp utilisĂ© soit le mĂȘme, il existe diffĂ©rentes forme oĂč dest nâest pas toujours une adresse. Cela peut, en effet, ĂȘtre un pointeur ou registre.
Le souci, en tant que reverser, est quâil ne sera pas toujours possible de savoir directement vers quelle adresse le processeur va sauter lorsquâun registre (ou pointeur) va ĂȘtre utilisĂ©. En analyse statique, il sera nĂ©cessaire de dĂ©terminer les diffĂ©rentes valeurs que peut prendre le registre afin de trouver les potentielles destinations.
Le fait dâutiliser un registre comme opĂ©rande est trĂšs commun dans la modĂ©lisation des switch en assembleur aprĂšs compilation.
Exemple
1
2
3
4
jmp 0x401020
jmp rax
jmp [ebx]
Ăquivalent en C
Les sauts inconditionnels jmp sont lâĂ©quivalent de goto en C :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int main() {
int i = 0;
start_loop:
if (i < 5) {
printf("i = %d\n", i);
i++;
goto start_loop; // Sauter à l'étiquette start_loop
}
return 0;
}
jcc dest
Opérandes
dest: destination du saut. Peut ĂȘtre :- une valeur immĂ©diate (exemple : adresse relative ou absolue)
Détails
jcc nâest pas un mnĂ©monique en soi. Il sâagit dâun terme gĂ©nĂ©rique pour dĂ©signer le mnĂ©monique de tous les sauts conditionnels. Les points communs de tous ces sauts sont les suivants :
- Ils utilisent certains flags parmi les EFLAGS afin de savoir sâil faut sauter
- Lorsque que le saut nâest pas exĂ©cutĂ©e, câest lâinstruction situĂ©e immĂ©diatement aprĂšs le saut qui est rĂ©alisĂ©e
- Ils sont prĂ©cĂ©dĂ©s dâune instruction
cmpoutest
Si vous retenez ça, vous avez retenu 60% du fonctionnement des sauts conditionnels. Le reste consiste seulement à se rappeler de ce que signifie chaque mnémonique et quels flags sont utilisés.
Voici les principaux sauts que vous pourrez rencontrer :
Selon le désassembleur utilisé, il peut y avoir quelques différences dans le mnémonique comme
jz(jump if zero) qui peut ĂȘtre dĂ©signĂ©je(jump if equal) mais qui reprĂ©sentent exactement la mĂȘme instruction.
| MnĂ©monique(s) | Description | Signe des opĂ©rations | Cas dâutilisation | Condition de saut |
|---|---|---|---|---|
jo | Jump if overflow |  | Détection de débordement | OF == 1 |
jno | Jump if not overflow |  | Détection de débordement | OF == 0 |
js | Jump if sign | Â | Tester le signe | SF == 1 |
jns | Jump if not sign | Â | Tester le signe | SF == 0 |
jz / je | Jump if zero / equal |  | Tester lâ(in)Ă©galitĂ© | ZF == 1 |
jnz / jne | Jump if not zero / not equal |  | Tester lâ(in)Ă©galitĂ© | ZF == 0 |
jb / jnae / jc | Jump if below / not above or equal / carry | Non signé | Tester la supériorité / infériorité | CF == 1 |
jnb / jae / jnc | Jump if not below / above or equal / not carry | Non signé | Tester la supériorité / infériorité | CF == 0 |
jbe / jna | Jump if below or equal / not above | Non signé | Tester la supériorité / infériorité | CF == 1 \|\| ZF == 1 |
jnbe / ja | Jump if not below or equal / above | Non signé | Tester la supériorité / infériorité | CF == 0 && ZF == 0 |
jl / jnge | Jump if less / not greater or equal | Signé | Tester la supériorité / infériorité | SF != OF |
jnl / jge | Jump if not less / greater or equal | Signé | Tester la supériorité / infériorité | SF == OF |
jng / jle | Jump if not greater / less or equal | Signé | Tester la supériorité / infériorité | ZF == 1 \|\| SF != OF |
jg / jnle | Jump if greater / not less or equal | Signé | Tester la supériorité / infériorité | ZF == 0 && SF == OF |
Il est Ă noter quâil nâexiste pas une seule maniĂšre de reprĂ©senter une condition du C vers lâassembleur. Prenons par exemple le code suivant :
1
2
3
4
5
6
7
8
9
10
unsigned int x = ...;
unsigned int y = ...;
if (x > y )
{
// Code A
}
else
{
// Code B
}
On peut trĂšs bien faire :
1
2
3
cmp x, y
ja addr_code_A
code_B
ou :
1
2
3
cmp x, y
jbe addr_code_B
code_A
Il faut donc ĂȘtre attentif lorsque lâon analyse du code assembleur pour savoir ce qui va ĂȘtre exĂ©cutĂ© et sous quelles conditions.
Exemples
1
2
jz 0x555555550102
jns 0x405987
Ăquivalent en C
Selon le signe des variables comparĂ©es et le type de comparaison utilisĂ©, certains sauts vont ĂȘtre utilisĂ©s plutĂŽt que dâautres (les diffĂ©rents mnĂ©moniques dâune mĂȘme instruction ont Ă©tĂ© omis par souci de concision) :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int x = ...;
int y = ...;
if (x < 0) // js ou jns
{
//...
}
if (x == y) //jz ou jnz
{
//...
}
if(x < y) // jl ou jnl
{
//...
}
if(x >= y) // jnl ou jl
{
//...
}
if(x <= y) // jle ou jnle
{
//...
}
Autres formes
Il existe dâautres sauts mais que lâon rencontre moins souvent.
cdq
Opérandes
- Cette instruction nâa pas dâopĂ©randes
Détails
cdq est lâabrĂ©viation de convert dword to qword. Vous lâavez compris, cela devrait donc permettre de convertir un dword (4 octets) en un qword (8 octets), mais comment ?
Tout dâabord, cette instruction ne sâapplique que sur le registre eax (ou ses dĂ©rivĂ©es). Câest pourquoi elle ne dispose pas dâopĂ©randes. De plus, cette instruction garde le signe de lâancienne valeur lors de la conversion vers la nouvelle valeur.
En x86_64 on a des registres de 64 octets, ce qui nâest pas le cas en x86. Ainsi, pour doubler la taille des donnĂ©es contenues dans eax, câest le registre edx (ou ses dĂ©rivĂ©es) qui va ĂȘtre utilisĂ© de cette maniĂšre :
- si le nombre dans
eaxest nĂ©gatif (bit de poids fort Ă©gal Ă1), alorsedxest rempli de1 - si le nombre dans
eaxest positif (bit de poids fort Ă©gal Ă0), alorsedxest rempli de0
Cette maniĂšre de gĂ©nĂ©rer une nouvelle valeur Ă partir dâune valeur signĂ©e est ce que lâon appelle lâextension de signe.
Ainsi on obtient une valeur de taille double en concaténant les deux registres sous la forme : edx:eax.
Cette instruction est trĂšs utilisĂ©e lors des divisions signĂ©es afin dâavoir un rĂ©sultat cohĂ©rent et correct.
Exemples
1
2
3
4
5
mov eax, 0x70001234
cdq ; edx:eax = 0x00000000:0x70001234
mov eax, 0x80001234
cdq ; edx:eax = 0xffffffff:0x80001234
Ăquivalent en C
Il nây pas a pas dâĂ©quivalent directe en C.
Autres formes
Il existe plusieurs dĂ©rivĂ©es mais dont le principe dâextension de signe est le mĂȘme :
cwd(convert word to dword): la valeur convertie est contenue dansdx:axcqo(convert qword to double qword): la valeur convertie est contenue dansrdx:rax(disponible seulement en x86_64)
shr ope_d, n et sar ope_d, n
Opérandes
ope_d: opĂ©rande de destination. Peut ĂȘtre :- un registre
- un pointeur
n: opĂ©rande source. Peut ĂȘtre :- une valeur immĂ©diate
- un registre (seulement le registre
cl)
Détails
Lâinstruction shr (ou shift right) permet de rĂ©aliser un dĂ©calage des bits de ope_d de n bits vers la droite.
Avec lâinstruction
shret toutes les autres instruction deshift(dĂ©calage), il nây a pas de rotation des bits sortants.Il existe dâautres instructions comme
ror/rolqui rĂ©alise un dĂ©calage rotatif des bits. Câest-Ă -dire que des bits qui sortent, par exemple, par la gauche, ârerentrentâ par la droite.
Ainsi, le dĂ©calage de 0b01110011 dâun bit vers la droite est 0b00111001.
En fait, lorsquâil y a un bit sortant, il nâest pas rĂ©ellement perdu dans la nature : il est sauvegardĂ© dans le flag
CFdes EFLAGS.
Il existe lâinstruction sar (ou shift aritmetic right) est basĂ©e sur le mĂȘme principe de dĂ©calage que shr. La seule diffĂ©rence est que sar prend en compte le signe du nombre qui sera dĂ©calĂ©.
Ainsi, si le bit de poids fort de ope_d est 1, il sera réinitialisé à 1 aprÚs décalage. En fait sar agit en deux temps :
- exécuter
shr - si le prĂ©cĂ©dent nombre Ă©tait signĂ©, mettre le bit de poids fort du rĂ©sultat Ă
1
Voir les exemples ci-dessous pour comprendre de quoi il sâagit.
Ces instructions sont trĂšs utilisĂ©es pour rĂ©aliser des divisions par 2 dâun nombre (et dont le reste est dans le flag CF). En effet, le dĂ©calage dâun bit vers la droite revient Ă diviser par 2. Le dĂ©calage de n bits vers la droite revient Ă diviser par 2 puissance n.
Je ne vois pas en quoi dĂ©caler dâun bit vers la droite revient Ă diviser par deux ?
Pourtant câest bien ce qui se passe lorsque lâon note un nombre en dĂ©cimal et que lâon le dĂ©cale dâune unitĂ© vers la droite, cela revient Ă diviser par 10.
Prenons par exemple 213950, en le dĂ©calant dâune unitĂ© vers la droite on obtient 21395, ce qui revient bien Ă diviser par 10.
Avec la notation en binaire, câest la mĂȘme chose : dĂ©caler dâun bit revient Ă diviser par deux.
Ainsi, sar et shr sont trÚs utilisés pour réaliser des divisions de puissances de 2.
Exemple
1
2
3
4
5
6
7
mov eax, 0x80000001 (0b10.....001)
shr eax, 1 ; eax = 0x40000000 (0b01.....000)
; CF == 1
mov eax, 0x80000001
sar eax, 1 ; eax = 0xc0000000 (0b11.....000)
; CF == 1
Ăquivalent en C
1
2
3
4
5
int x = 0x80000001;
x = x >> 1; // x = 0xc0000000
int y = 0xdeadbeef;
y = y >> 13; // y = 0xfffef56d
Autres formes
De la mĂȘme maniĂšre que shr/sar permettent de rĂ©aliser des dĂ©calages vers la droite, shl/sal permettent de rĂ©aliser des dĂ©calages vers la gauche avec le mĂȘme principe.
A lâinstar de la division par puissances de 2 de shr/sar, shl/sal permettent de rĂ©aliser des multiplications par puissances de 2 :
- âĄïž
shr/sar: division par puissances de 2 - âŹ
ïž
shl/sal: multiplication par puissances de 2
Vous pouvez Ă©galement jeter un Ćil aux instructions rcl/rcr/rol/ror. Leur fonctionnement de dĂ©calage est le mĂȘme. La principale diffĂ©rence est quâil y a une rotation des bits sortants.

