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

12 KiB

title date tags categories
Sécurité logicielle : L'assembleur - approfondissement 2023-02-01
assembleur
intel
Sécurité logicielle
Cours

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, eCxet '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>
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 de n bits vers la gauche de <dst>
  • sor <n> <dst> : applique une rotation de n 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 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 :

# 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 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
    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.

.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