Un peu de mathématiques en assembleur x86-64

Salut ce forum est dédié à comprendre les entrailles de l'ordinateur au plus bas niveau bienvenue dans le monde du reverse et de l'assembleur

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 114
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Un peu de mathématiques en assembleur x86-64

Post by Hydraxx »

Un peu de mathématiques en assembleur x86-64

Les opérations arithmétiques sont extrêmement fréquentes en assembleur. Elles apparaissent aussi bien dans du code simple que dans des boucles, des calculs d'offsets, des comparaisons, des index de tableaux ou encore dans le calcul d'adresses.

Pour le reverse engineering, il faut surtout être capable de lire rapidement une suite d'instructions et de déterminer la valeur finale des registres ou de la mémoire.

Dans ce chapitre, nous allons voir les principales opérations arithmétiques :

Code: Select all

neg
add
inc
sub
dec
mul
imul
div
idiv
cmovcc
1. La négation avec NEG

L'instruction :

Code: Select all

neg
remplace une valeur par son opposé.

Exemple :

Code: Select all

mov rax, 10
neg rax
Résultat :

Code: Select all

RAX = -10
Si RAX vaut déjà -10 :

Code: Select all

neg rax
donne :

Code: Select all

RAX = 10
L'instruction `neg` fonctionne en utilisant le complément à deux, qui est la représentation standard des entiers signés en x86/x64.

Elle peut être utilisée sur un registre :

Code: Select all

neg rax
ou directement en mémoire :

Code: Select all

neg dword [x]
neg byte [x]
Lorsque l'on manipule directement de la mémoire, il faut généralement préciser la taille.

2. Les tailles : byte, word, dword, qword

Les tailles classiques sont :

Code: Select all

byte  = 1 octet  = 8 bits
word  = 2 octets = 16 bits
dword = 4 octets = 32 bits
qword = 8 octets = 64 bits
Exemples :

Code: Select all

neg byte  [x]
neg word  [x]
neg dword [x]
neg qword [x]
Cela indique au processeur combien d'octets doivent être lus et modifiés.

En reverse, cette taille est très importante.

Par exemple :

Code: Select all

neg byte [rax]
ne modifie qu'un octet à l'adresse contenue dans RAX.

Alors que :

Code: Select all

neg qword [rax]
modifie 8 octets.

3. NEG et les flags

Comme beaucoup d'instructions arithmétiques, `neg` modifie certains bits du registre `RFLAGS`.

Parmi les plus importants :

Code: Select all

ZF = Zero Flag
SF = Sign Flag
CF = Carry Flag
OF = Overflow Flag
Exemple :

Code: Select all

mov eax, 0
neg eax
Le résultat est toujours 0, donc :

Code: Select all

ZF = 1
Si le résultat est négatif, le Sign Flag peut être positionné.

Ces flags peuvent ensuite être utilisés par une instruction conditionnelle.

4. Addition avec ADD

L'instruction `add` réalise une addition.

Syntaxe :

Code: Select all

add destination, source
Exemple :

Code: Select all

mov rax, 10
add rax, 5
Résultat :

Code: Select all

RAX = 15
On peut additionner :
  • une constante à un registre ;
  • un registre à un autre registre ;
  • une valeur mémoire à un registre ;
  • un registre à une valeur mémoire.
Exemples :

Code: Select all

add rax, 10
add rax, rbx
add rax, [x]
add [x], rax
5. ADD et la mémoire

Supposons :

Code: Select all

x dq 100
Puis :

Code: Select all

mov rax, 20
add rax, [x]
Résultat :

Code: Select all

RAX = 120
À l'inverse :

Code: Select all

mov rax, 20
add [x], rax
modifie directement la valeur en mémoire :

Code: Select all

x = 120
La différence est donc :

Code: Select all

add rax, [x]
→ charge la valeur de x pour modifier RAX.

Alors que :

Code: Select all

add [x], rax
→ modifie directement x en mémoire.

6. INC — incrémenter

L'instruction :

Code: Select all

inc
ajoute simplement 1.

Exemple :

Code: Select all

mov rax, 10
inc rax
Résultat :

Code: Select all

RAX = 11
C'est l'équivalent logique de :

Code: Select all

add rax, 1
Elle est très fréquente dans les compteurs et les boucles.

Exemple :

Code: Select all

inc rcx
cmp rcx, 10
jne boucle
Ici, RCX sert de compteur.

7. Soustraction avec SUB

L'instruction :

Code: Select all

sub destination, source
retire la source de la destination.

Exemple :

Code: Select all

mov rax, 20
sub rax, 5
Résultat :

Code: Select all

RAX = 15
On peut aussi utiliser :

Code: Select all

sub rax, rbx
sub rax, [x]
sub [x], rax
Même logique que pour `add`.

8. SUB et la stack

Tu verras très souvent :

Code: Select all

sub rsp, 20h
Ici, ce n'est pas vraiment un calcul métier.

Cette instruction sert généralement à réserver de l'espace sur la stack.

Puis, à la fin de la fonction :

Code: Select all

add rsp, 20h
restaure la valeur de RSP.

Exemple :

Code: Select all

sub rsp, 20h
...
add rsp, 20h
ret
C'est extrêmement fréquent dans du code x64 Windows.

9. DEC — décrémenter

`dec` soustrait 1.

Exemple :

Code: Select all

mov rcx, 10
dec rcx
Résultat :

Code: Select all

RCX = 9
C'est comparable à :

Code: Select all

sub rcx, 1
On le rencontre notamment dans les compteurs décroissants.

Exemple :

Code: Select all

dec rcx
jnz boucle
10. ADD/SUB et les flags

`add` et `sub` modifient plusieurs flags importants.

Par exemple :

Code: Select all

mov eax, 10
sub eax, 10
Résultat :

Code: Select all

EAX = 0
ZF = 1
Une instruction conditionnelle peut ensuite tester cet état :

Code: Select all

je egal
Autre exemple :

Code: Select all

mov eax, 5
sub eax, 10
Le résultat est négatif dans une interprétation signée.

Le Sign Flag est alors utile pour les conditions signées.

En reverse, tu n'as pas forcément besoin de mémoriser tous les flags immédiatement. Mais retiens bien :

Code: Select all

une opération arithmétique modifie souvent RFLAGS
```

et les instructions suivantes peuvent prendre une décision en fonction de ces flags.

[size=150][b][color=#FF0000]11. Multiplication avec IMUL[/color][/b][/size]

L'instruction :

[code]
imul
effectue une multiplication signée.

C'est celle que tu rencontreras très souvent dans du code compilé.

Exemple :

Code: Select all

mov rax, 10
imul rax, 5
Résultat :

Code: Select all

RAX = 50
Autre forme :

Code: Select all

imul rax, rbx
Si :

Code: Select all

RAX = 10
RBX = 20
alors :

Code: Select all

RAX = 200
On peut également rencontrer une forme à trois opérandes :

Code: Select all

imul rax, rbx, 10
qui signifie conceptuellement :

Code: Select all

RAX = RBX * 10
Cette forme est particulièrement pratique pour les calculs.

12. MUL — multiplication non signée

`mul` réalise une multiplication non signée.

Sa syntaxe est différente de certaines formes de `imul`.

Exemple :

Code: Select all

mov rax, 10
mov rbx, 20
mul rbx
Dans ce cas, le processeur utilise implicitement RAX.

Pour une opération 64 bits :

Code: Select all

RDX:RAX = RAX * RBX
Le résultat peut donc occuper jusqu'à 128 bits.

La partie basse du résultat est placée dans :

Code: Select all

RAX
et la partie haute dans :

Code: Select all

RDX
Pour des petites valeurs, RDX reste souvent à zéro.

13. MUL ou IMUL ?

La différence principale est :

Code: Select all

mul  = multiplication non signée
imul = multiplication signée
Pour le reverse, `imul` est souvent plus simple à lire car il existe des formes comme :

Code: Select all

imul eax, eax, 5
que tu peux lire immédiatement comme :

Code: Select all

EAX = EAX * 5
14. Exemple de multiplication

Prenons :

Code: Select all

mov rax, 5
imul rax, rax
Résultat :

Code: Select all

RAX = 25
Puis :

Code: Select all

mov rbx, 12
imul rbx, 2
Résultat :

Code: Select all

RBX = 24
Ce type de code peut apparaître lors de calculs d'indices, d'offsets ou de tailles.

15. Division avec DIV

L'instruction :

Code: Select all

div
effectue une division non signée.

Pour une division 64 bits, le dividende est placé dans :

Code: Select all

RDX:RAX
Le quotient est placé dans :

Code: Select all

RAX
et le reste dans :

Code: Select all

RDX
Exemple simplifié :

Code: Select all

mov rax, 100
xor rdx, rdx
mov rbx, 10
div rbx
Résultat :

Code: Select all

RAX = 10
RDX = 0
Le :

Code: Select all

xor rdx, rdx
met RDX à zéro avant la division.

16. Pourquoi RDX:RAX ?

Certaines instructions utilisent une paire de registres afin de représenter une valeur plus grande.

Pour `div`, le dividende 64 bits ou 128 bits selon la taille de l'opération peut être réparti sur deux registres.

En 64 bits :

Code: Select all

RDX = partie haute
RAX = partie basse
On peut visualiser :

Code: Select all

RDX : RAX
high   low
Après la division :

Code: Select all

RAX = quotient
RDX = reste
17. IDIV — division signée

`idiv` réalise la division signée.

Exemple conceptuel :

Code: Select all

-100 / 10 = -10
Pour préparer correctement une division signée 64 bits, on peut rencontrer :

Code: Select all

cqo
qui étend le signe de RAX dans RDX.

Exemple :

Code: Select all

mov rax, -100
cqo
mov rbx, 10
idiv rbx
Résultat :

Code: Select all

RAX = -10
RDX = 0
En 32 bits, l'instruction correspondante est souvent :

Code: Select all

cdq
pour étendre le signe de EAX dans EDX.

18. Division par zéro

Une division par zéro n'est pas autorisée.

Exemple :

Code: Select all

mov rbx, 0
div rbx
provoque une exception processeur.

En reverse, si tu vois un test avant une division :

Code: Select all

test rbx, rbx
je erreur
div rbx
tu peux comprendre immédiatement que le programme évite une division par zéro.

19. Les instructions CMOVcc

Les instructions :

Code: Select all

cmovcc
signifient :

Code: Select all

Conditional MOV
```

Elles permettent de copier une valeur uniquement si une condition est vraie.

Exemple :

[code]
cmp rax, rbx
cmovg rcx, rdx
Ici :

Code: Select all

si RAX > RBX
alors RCX = RDX
```

Sinon :

[code]
RCX reste inchangé
```

Contrairement à un saut conditionnel, `cmov` ne change pas RIP vers une autre branche de code.

Il effectue simplement ou non la copie.

[size=150][b][color=#FF0000]20. Quelques CMOV utiles[/color][/b][/size]

On peut rencontrer :

[code]
cmove   / cmovz   -> égal / zéro
cmovne  / cmovnz  -> différent / non zéro
cmovg            -> supérieur signé
cmovge           -> supérieur ou égal signé
cmovl            -> inférieur signé
cmovle           -> inférieur ou égal signé
cmova            -> supérieur non signé
cmovb            -> inférieur non signé
Ces instructions utilisent les flags produits par une instruction précédente comme :

Code: Select all

cmp
test
sub
add
21. Exemple de CMOV

Supposons :

Code: Select all

mov rax, 20
mov rbx, 10
mov rcx, 1
mov rdx, 2

cmp rax, rbx
cmovg rcx, rdx
Puisque :

Code: Select all

20 > 10
la condition est vraie.

Donc :

Code: Select all

RCX = 2
Si RAX avait été inférieur ou égal à RBX :

Code: Select all

RCX serait resté égal à 1
22. Pourquoi utiliser CMOV ?

Une condition classique pourrait être écrite avec un saut :

Code: Select all

cmp rax, rbx
jle no_change

mov rcx, rdx

no_change:
Avec `cmov` :

Code: Select all

cmp rax, rbx
cmovg rcx, rdx
Cela peut éviter une branche conditionnelle.

Les compilateurs modernes utilisent parfois ce type d'instruction pour produire du code plus compact ou éviter certains coûts liés aux branches.

23. Pourquoi déplacer une valeur dans un registre ?

Tu verras souvent du code qui charge d'abord une valeur depuis la mémoire :

Code: Select all

mov rax, [x]
add rax, 10
mov [x], rax
alors qu'on pourrait parfois imaginer une opération directe :

Code: Select all

add qword [x], 10
Pourquoi les compilateurs utilisent-ils souvent des registres ?

Parce que les instructions et calculs sur registres sont généralement simples et efficaces, et parce que certaines opérations imposent l'utilisation de registres spécifiques.

Les registres servent donc souvent d'intermédiaires :

Code: Select all

mémoire -> registre -> calcul -> mémoire
Pour le reverse, ce trajet est fondamental à suivre.

24. Exemple complet de suivi de valeurs

Prenons :

Code: Select all

mov rax, 10
add rax, 5
imul rax, 2
sub rax, 4
Suivons RAX :

Code: Select all

RAX = 10

add rax, 5
RAX = 15

imul rax, 2
RAX = 30

sub rax, 4
RAX = 26
En reverse, il faut être capable de faire exactement ce type de calcul mental.

25. Exemple avec mémoire

Supposons :

Code: Select all

x dq 100
y dq 20
Puis :

Code: Select all

mov rax, [x]
sub rax, [y]
imul rax, 2
mov [x], rax
Étapes :

Code: Select all

RAX = 100

RAX = 100 - 20
RAX = 80

RAX = 80 * 2
RAX = 160

x = 160
Valeur finale :

Code: Select all

x = 160
y = 20
RAX = 160
26. Exemple typique de reverse

Supposons que tu rencontres :

Code: Select all

mov eax, ecx
imul eax, 2
add eax, 5
cmp eax, 25
jne failed
Tu peux traduire mentalement :

Code: Select all

EAX = ECX
EAX = EAX * 2
EAX = EAX + 5
compare EAX avec 25
si différent -> failed
Mathématiquement :

Code: Select all

2 * ECX + 5 = 25
donc :

Code: Select all

ECX = 10
C'est exactement le genre de raisonnement utilisé lorsqu'on reverse une condition ou une validation.

27. Ce qu'il faut retenir pour le reverse

Pour ton usage, retiens surtout :
  • `neg` inverse le signe ;
  • `add` additionne ;
  • `inc` ajoute 1 ;
  • `sub` soustrait ;
  • `dec` retire 1 ;
  • `imul` multiplie des valeurs signées ;
  • `mul` multiplie des valeurs non signées ;
  • `div` divise des valeurs non signées ;
  • `idiv` divise des valeurs signées ;
  • après `div/idiv`, le quotient est généralement dans RAX et le reste dans RDX ;
  • les opérations arithmétiques mettent souvent à jour RFLAGS ;
  • les instructions suivantes peuvent utiliser ces flags ;
  • `cmovcc` effectue une copie uniquement si une condition est vraie ;
  • les calculs d'offset et d'indices utilisent souvent `add`, `sub` et `imul` ;
  • il faut savoir suivre une valeur registre par registre.
28. Exercices

Exercice 1

Quelle est la valeur finale de RAX ?

Code: Select all

mov rax, 10
add rax, 5
sub rax, 3
Exercice 2

Quelle est la valeur finale de RAX ?

Code: Select all

mov rax, 8
imul rax, 4
Exercice 3

Quelle est la valeur finale ?

Code: Select all

mov rax, 100
mov rbx, 25

sub rax, rbx
imul rax, 2
Exercice 4

On a :

Code: Select all

x dq 10
y dq 20
Puis :

Code: Select all

mov rax, [x]
add rax, [y]
mov [x], rax
Donne :

Code: Select all

RAX = ?
x   = ?
y   = ?
Exercice 5

Analyse :

Code: Select all

mov eax, ecx
imul eax, 3
add eax, 2
cmp eax, 20
jne failed
Quelle valeur de ECX permet de ne pas sauter vers `failed` ?

Exercice 6

Que fait ce code ?

Code: Select all

mov rax, 100
xor rdx, rdx
mov rbx, 30
div rbx
Donne :

Code: Select all

RAX = quotient
RDX = reste
Exercice 7

Explique la différence entre :

Code: Select all

mul
imul
div
idiv
Exercice 8

Analyse :

Code: Select all

mov rax, 20
mov rbx, 10
mov rcx, 1
mov rdx, 2

cmp rax, rbx
cmovg rcx, rdx
Quelle est la valeur finale de RCX ?

Exercice 9

Dans x64dbg, trouve une instruction :

Code: Select all

add
sub
imul
Place un breakpoint avant l'instruction puis avance d'une instruction.

Observe la valeur du registre avant et après.

L'objectif est de prendre l'habitude de lire une instruction assembleur comme une transformation directe de l'état du CPU.

Who is online

Users browsing this forum: No registered users and 1 guest