12 KiB
title | date | tags | categories | ||||
---|---|---|---|---|---|---|---|
Sécurité logicielle : L'assembleur - approfondissement | 2023-02-01 |
|
|
Un premier exemple
Prenons IP
comme compteur et A
, B
, C
et D
comme registres.
0x00: mov $1, A
0x05: mov $2, B
0x0a: mov $3, C
0x0f: add A, B
0x10: cmp B, C
0x11: jz zero
0x12: mov $41, A
0x17: jmp end
0x18: zero:
0x18: mov $42, A
0x1d: end:
La première colonne représente le compteur d'instruction, nous pouvons vois que chaque instruction prend 5 octets.
Ligne 4, la seconde opérande de l'instruction add
est aussi le résultat
Ligne 5, l'instruction cmp
fait une comparaison, en fait il fait une
soustraction des deux opérandes. Si le résultat de cette sourtraction (et dons
que les opérandes sont égales) alors l'instruction jz
(jump zero) réalisera
un saut vers l'étiquette zero
.
Le langage
Les registres
Il étaient de 8 bits à l'origine et nommés A
, B
, C
et D
, lors du passage
en 16 bits, ils se sont appelés Ax
, Bx
, Cx
, Dx
(pour extended). Lors
du passage à 32 bits ils sont passé à eAx
, eBx
, eCx
et 'eDx
. Enfin ils
sont passé à rAx
, rBx
, rCx
et rDx
pour la version 64 bits.
Lors de ce cours, nous utiliseront principalement les versions 32 bits.
Les suffixes d'instructions
Toute comme pour les registres, il existe des version de certaines instructins en fonction de la taille des opérandes :
movb
: 8bitsmovb
: 16 bitsmovl
: 32 bitsmovq
: 64 bits
Les usages
Il existe plusieurs type de registres en fonction de leur usage
- registres généraux :
rAx
,rBx
, ... - registres d'indexation :
RSI
(source) etRDI
(destination) - registres supplémentaire 64 bits : de
R8
àR15
Nous en verrons d''autre un peu plus tard.
Flags de résultats
C'est un registre de 32 bits, chaque bit correspond à un drapeau spécifique :
Les 16 premier bits repente les flags :
position | flag | fonction |
---|---|---|
0 | CF | Carry flag |
2 | PF | Parity flag |
4 | AF | Adjust flag |
6 | ZF | Zero flag (si le résultat est 0) |
7 | SF | Sign flag (valeur de bit de poids fort) |
8 | TF | Trap flag |
9 | IF | Interrupt enable flag |
10 | DF | Direction flag (sens de lecture d'une chaine de caractère) |
11 | OF | Overflow flag (positionné en cas d'overflow) |
12 13 | IOPL | IO privilege number |
14 | NT | Nested tag flag |
Et les 16 suivants les eflags:
position | flag | fonction |
---|---|---|
16 | RF | Resume flag (Défini la réponse CPU pour la reprise à l'exception pour |
debug) | ||
17 | VM | Virtual 8086 mode |
18 | AC | Alignement check |
19 | VIF | Virtual interrupt flag |
20 | ID | CPUID flag (possibilité d'utiliser le CPUID) |
21-31 | Réservé |
Les instructions
Addition / soustraction / incrémentation / décrémentation
add <src> <dest>
: addition de<src>
et<dst>
, positionnement du résultat dans<dst>
sub <src> <dst>
: soustraction de<src>
et<dst>
, positionnement du résultat dans<dst>
inc <val>
: incrémenter<val>
dec <val>
: décrémenter<val>
neg <val>
: renvoie le complément à deux de<val>
movl $20 %rax # rax = 15
addl $10 %rax # rax = rax + 10 = 25
subl $7 %rax # rax = rax - 7 = 18
incl %rax # rax += 1 = 19
decl %rax # rax -= 1 = 19
Division / Multiplication
mul <src>
: multiplication de%eax
par<src>
,%eax
est choisi en dur, le résultat est écrit sur deux registres%edx
et%eax
.mul
retroune un résultat non signé.imul
: multiplication comme précédement, mais le résultat est cette fois signédiv <dst>
: divise le nombre contenu dans%eax
et%edx
par<dst>
. Le quotien sera positionné dans%eax
et le reste dans%edx
. Le résultat est non signé.idiv <dst>
: division comme précédement mais cette fois le résultat est signé.
movl $0 %edx
movl $8 %eax
movl $2 %ebx
divl %ebx # %eax = %edx.%eax / %ebx remainer : %edx
movl $0 %edx
movl $8 %eax
movl $2 %ebx
mull %ebx # %eax = %ebx * %eax
Shift, rotate
On parle ici de décalage de bits que se soir à gauche (multilication par 2 puissance X) ou à droite (division par 2 puissance X).
shl <n> <dst>
: décalage à gauche de<dst>
de<n>
bits (<dst> << n
).shr <n> <dst>
: décalage à droite de<dst>
de<n>
bits (<dst> >> n
).
Ces deux instructions sont non signées, les versions signées sont sal
et sar
.
sol <n> <dst>
: applique une rotation den
bits vers la gauche de<dst>
sor <n> <dst>
: applique une rotation den
bits vers la droite de<dst>
movl $0xa %eax
rol $0x2 %eax # 00001010 -> 00101000 = 0x28
Opération bit à bit
and <src> <dst>
: réalisation de l'opération<src> & <dst>
, positionnement de résultat dans<dst>
or <src> <dst>
: réalisation de l'opėration<src> | <dst>
, positinnement du résultat dans<dst>
xor <src> <dst>
: réalisation de l'opėration<src> ^ <dst>
, positinnement du résultat dans<dst>
not <dst>
: réalisation de l'opération<~dst>
et poitionnement du résultat dans<dst>
cmp <1> <2>
: réalisation de l'opération<1> - <2>
, le résultat n'est pas affecté mais les drapeaux OF, ZF AF, CF PF son positionné en fonction du résultat.
Instruction de branchement
Certaines de ces instructions on plusieurs notations possibles.
jmp <addr>
: saut vers l'adresse<addr>
sans conditionja <addr>
: saut vers l'adresse<addr>
si la comparaison au dessus donne comme résultat strictement supérieur àjae <addr>
: saut vers l'adresse<addr>
si la comparaison au dessus donne comme résultat supérieur ou égal àjnae <addr>
: saut vers l'adresse<addr>
si la comparaison au dessus donne comme résultat strictement inférieur à (not above or equal) -- peut être aussijng
(not greater)jz <addr>
: saut vers l'adresse<addr>
si la comparaison au dessus donne comme résultat égal à
Instruction diverses
loop <addr>
: décrémente%ecx
et effectue un saut vers<addr>
si%ecx
et supérieur à 0.loope <addr>
: décrémente%ecx
et effectue un saut vers<addr>
si%ecx
et supérieur à 0 etZF
= 1. Peut aussi être nomméloopz
loopne <addr>
: décrémente%ecx
et effectue un saut vers<addr>
si%ecx
et supérieur à 0 etZF
= 0. Peut aussi être nomméloopnz
noop
: ne fait rien...
Gestion de la Mémoire
Nous avons différents types d'opérandes pour la mémoire. que se soit sur les registres :
# affecter le contenu de %eax dans %ebx
movl %eax %ebx
Pour les immediats:
# Affecter 1 à %ebx
movl $1 %ebx
# Affecter 0xa à %ebx
movl $0xa %ebx
# Affecter l'adresse `a` à %ebx
movl $a %ebx
Et pour l'acces direct à la mémoire
# Erreur !!
movl a %ebx
# Référence à l'adresse `a`
movl $a %ebx
Et enfin les accès indirects à la mémoire. Le principe ici est base + index *
type + déplacement. Si le type est omit alors il vaut 1 si le déplacement est
omis il vaut 0. Concrètement l'adresse est organisée comme suit:
<deplacement>(<base>, <index>, <type>)
# Copie le contenu de la case mémoire pointé par le contenu de %ebx dans %eax
movl (%ebx) %eax
# Copie le contenu à l'adresse %ebx + 4 octets dans %eax
movl 4(%ebx) %eax
# Cette fois c'est le contenu à l'adresse %ebx - 4
movl -4(%ebx) %eax
# le contenu à l'adresse %ecx + %ebx
movl (%ebx, %ecx) %eax
# contenu à l'adresse %ebx +t
movl t(%ebx) %eax
# contenu à l'adresse t + %ebx + %ecx * 4
movl t(%ebx, %ecx, 4) %eax
Move, xchg, lea
Ces instructions servent à copier une valeur d'un endroit (registre, mémoire ) à un autre.
movl <src> <dst>
: copie le contenu de<src>
vers<dst>
,<src>
peut être une constante, un registre ou un adresse (label) et<dst>
une adresse ou un registre.xchg <a> <b>
: ;echange le contenu de<a>
et de<b>
leal <src> <dst>
: calcule l'adresse source (<src>
) et place le résultat dans<dst>
. Contrairement àmovl
,leal
ne traite pas la valeur mais bien l'adresse
Il est possible d'utiliser leal
pour effectuer des calculs arithmétiques non
signé. Ainsi on peut bénéficier d'un avantage : les drapeaux ne sont pas
impactés.
cmov<cmp> <src> <dst>
: copie<src>
vers<dst>
en fonction de l'instruction de comparaison précédente et de l'instruction donnée par<cmp>
.cmpl %eax %ebx # %ebx - %eax cmovgel $1 %edx # si %ebx > ebx (res > 0) alors %edx = 1
Diverses instruction
nop
: ne fait rien à part incrémenter le pointeur d'instruction. Elle peut servier à remplir un espace mémoire afin d'éviter de décaller le restrant du code.halt
: met en pause le CPU
Travail sur les flottants
Les flotants ont une longueur de 32bits, les doubles de 64bits, le processeur lui les traite sur 80bits :
- un bit de signe
- 15 bits d'exposants
- 64 bits de mantisse
Pour information, nombre = mantisse = 10^exposant.
Le processeur dispose de 8 registres (st0
à st7
). Leurs accès ne se fait pas
directement mais via push
/ pop
ils sont gérés dans une unité à part , qui dispose aussi des ses propres flags sur 16bits. Les 6 premiers bits sont réservés aux exceptions:
position | flag | fonction |
---|---|---|
0 | IE | Invalid Operation |
1 | DE | Denormalized Operand Exception |
2 | FE | Zero Divide Exception |
3 | OE | Overflow Exception |
4 | UE | Underflow Exception |
5 | PE | Precision Exception |
Voici les suivants:
position | flag | fonction |
---|---|---|
7 | ES | Error Summary Status |
8 9 10 14 | C1-C4 | Condition Code |
11 12 13 | TOP | Top of stack pointer |
15 | B | FPU busy |
Les instructions
Le FPU dispose de ses propres instructions par exemple:
fadd
,faddp
: addition flotante :faddp # Ajouter st1 à st0 fadd a # Ajouter a à st0 fadd a b # Ajouter a à b
fsub
,fsubp
: soustraction flottantefmul
,fmulp
: multiplication flottantefdiv
,fdivp
: division flottantefchs
: changement de signe (st(i)
)fabs
: retourne la valeur absolue (st(i)
)fsqrt
: retourne la racine carré (st(i)
)fsin
,fcos
retourne le sinus, le cosinus (st(i)
)fcomi
: compare l'opérande avecst0
fcomi st2
fcmovb
,fcmove
,fcmovbe
: move si en dessous, egal, en dessous ou égal
SSE (Streaming SIMD Extentions)
C'est un ensemble de 70 instructions ajoutées au processeurs Intel en 1999, 8
registres supplémentaires sont implémentés : xmm0
à xmm7
d'une longueur de
128bits. La version 64 bits ajoute 8 registes de plus (xmm8
à xmm16
).
Instruction
Voici quelques exemples de mnémonique spécifiques à cette partie:
movss
qui permet de déplacement, deux opérandes la source (adresse ou registrexmm
) et une destination (resitrexmm
).addss
: additions, s'utilise commemovss
.subss
,mulss
,divss
: respectivement soustrasction, multiplication et division. Ces opération acceptent les même opérandes quemovss
.
Les appels systèmes en assembleur
Pour passer en mode noyau, nous pouvons utiliser une interruption spécifique :
0x80
, le numéro d'appel système doit être positionné dans %eax
et les
paramètres dasn ebx
, ecx
, edx
, esi
et edi
. La valeur de retour est
positionnée dans eax
.
En version 32bits l'appel se fait via int 0x80
. La version 64 bits dispose
de l'instruction syscall
.
.data
# Notre message et sa taille, elle nous sont nécessaire pour appeler
# notre appel système
msg:
.asciz "Hello World\n"
len = . - msg
.text
.globl main
main:
# placement de nos paramètres dans nos différents registres
movl $len, %edx
movl $msg, %ecx
movl $1, %ebx
# numéro de l'appel système sys_write dans eax
movl $4, %eax
# Et lancement de notre interruption
int $0x80
# Relançons un appel système pour exit, avec 0 comme paramètre
movl $0, %ebx
movl $1, %eax
int $0x80