cours/content/secu_logicielle/3_assembleur_approfondissement/index.md

406 lines
12 KiB
Markdown

---
title: "Sécurité logicielle : L'assembleur - approfondissement"
date: 2023-02-01
tags: ["assembleur", "intel"]
categories: ["Sécurité logicielle", "Cours"]
---
## Un premier exemple
Prenons `IP` comme compteur et `A` , `B`, `C` et `D` comme registres.
```asm {linenos=inline}
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` : 8bits
* `movb` : 16 bits
* `movl` : 32 bits
* `movq` : 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) et `RDI` (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>`
```asm
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é**.
```asm
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 de `n` bits vers la gauche de `<dst>`
* `sor <n> <dst>` : applique une rotation de `n` bits vers la droite de `<dst>`
```asm
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 condition
* `ja <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
aussi `jng` (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 et `ZF` = 1. Peut aussi être nommé `loopz`
* `loopne <addr>`: décrémente `%ecx` et effectue un saut vers `<addr>` si `%ecx`
et supérieur à 0 et `ZF` = 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 :
```asm
# affecter le contenu de %eax dans %ebx
movl %eax %ebx
```
Pour les immediats:
```asm
# 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
```asm
# 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>)`
```asm
# 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>`.
```asm
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 :
```asm
faddp # Ajouter st1 à st0
fadd a # Ajouter a à st0
fadd a b # Ajouter a à b
```
* `fsub`, `fsubp`: soustraction flottante
* `fmul`, `fmulp`: multiplication flottante
* `fdiv`, `fdivp`: division flottante
* `fchs` : 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 avec `st0`
```asm
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
registre `xmm`) et une destination (resitre `xmm`).
* `addss`: additions, s'utilise comme `movss`.
* `subss`, `mulss`, `divss`: respectivement soustrasction, multiplication et
division. Ces opération acceptent les même opérandes que `movss`.
### 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`.
```asm
.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
```