Branching and Looping 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:

Branching and Looping en assembleur x86-64

Post by Hydraxx »

Branching and Looping en assembleur x86-64

Le contrôle de flux permet à un programme de choisir quelles instructions exécuter et combien de fois les exécuter.

En assembleur, les structures de haut niveau comme :

Code: Select all

if
else
while
do while
break
continue
n'existent pas directement.

Elles sont traduites en :

Code: Select all

comparaisons
flags
sauts conditionnels
sauts inconditionnels
labels
Pour le reverse engineering, comprendre le contrôle de flux est essentiel : cela permet de reconstruire la logique d'un programme à partir de son désassemblage.

1. Exécution séquentielle

Par défaut, le processeur exécute les instructions les unes après les autres.

Exemple :

Code: Select all

mov eax, 1
add eax, 2
imul eax, 3
L'ordre est simplement :

Code: Select all

1 -> 2 -> 3
Mais un programme réel doit pouvoir prendre des décisions.

C'est le rôle des branchements.

2. Les labels

Un label représente une position dans le code.

Exemple :

Code: Select all

start:
    mov eax, 10
    jmp end

middle:
    mov eax, 20

end:
    ret
Ici :

Code: Select all

start
middle
end
sont des labels.

Ils ne correspondent pas à une instruction exécutée : ce sont des noms associés à des adresses dans le code.

3. JMP — saut inconditionnel

L'instruction :

Code: Select all

jmp label
modifie directement le flot d'exécution.

Exemple :

Code: Select all

mov eax, 1
jmp skip

mov eax, 2

skip:
ret
La ligne :

Code: Select all

mov eax, 2
n'est jamais exécutée.

Le processeur saute directement vers `skip`.

En reverse, `jmp` peut servir à :
  • sortir d'une branche ;
  • revenir au début d'une boucle ;
  • rejoindre une zone commune de code ;
  • implémenter des structures if/else.
4. CMP — comparer deux valeurs

L'instruction :

Code: Select all

cmp destination, source
compare deux valeurs sans conserver le résultat.

Conceptuellement :

Code: Select all

destination - source
est calculé uniquement pour mettre à jour les flags.

Exemple :

Code: Select all

cmp eax, 10
permet ensuite à une instruction conditionnelle de savoir si :

Code: Select all

EAX == 10
EAX != 10
EAX > 10
EAX < 10
...
5. Les flags importants

Les sauts conditionnels utilisent les bits du registre :

Code: Select all

RFLAGS
Parmi les principaux :

Code: Select all

ZF = Zero Flag
SF = Sign Flag
CF = Carry Flag
OF = Overflow Flag
Par exemple :

Code: Select all

cmp eax, ebx
je equal
Le saut `je` se base principalement sur :

Code: Select all

ZF = 1
6. JE / JZ

Les instructions :

Code: Select all

je
jz
sont deux noms pour la même condition.

Elles sautent lorsque :

Code: Select all

ZF = 1
Exemple :

Code: Select all

cmp eax, 10
je equal
Cela correspond approximativement à :

Code: Select all

if (eax == 10)
    goto equal;
7. JNE / JNZ

Les instructions :

Code: Select all

jne
jnz
sautent lorsque :

Code: Select all

ZF = 0
Exemple :

Code: Select all

cmp eax, 10
jne different
équivaut à :

Code: Select all

if (eax != 10)
    goto different;
C'est une combinaison extrêmement fréquente en reverse.

8. Comparaisons signées

Pour les entiers signés, on utilise principalement :

Code: Select all

jg  = greater
jge = greater or equal
jl  = less
jle = less or equal
Exemple :

Code: Select all

cmp eax, ebx
jg greater
signifie :

Code: Select all

si EAX > EBX en signé
    saute vers greater
9. Comparaisons non signées

Pour les entiers non signés, on rencontre :

Code: Select all

ja  = above
jae = above or equal
jb  = below
jbe = below or equal
Exemple :

Code: Select all

cmp eax, ebx
ja greater_unsigned
La différence entre :

Code: Select all

jg
ja
est importante.

`jg` interprète les valeurs comme signées.

`ja` les interprète comme non signées.

10. JC / JNC

Les instructions :

Code: Select all

jc
jnc
testent directement le Carry Flag.

Code: Select all

jc  -> CF = 1
jnc -> CF = 0
On peut les rencontrer après certaines opérations arithmétiques ou binaires.

11. Traduction d'un IF simple

Code C :

Code: Select all

if (a == b)
{
    x = 1;
}
Traduction assembleur simplifiée :

Code: Select all

mov eax, [a]
cmp eax, [b]
jne end_if

mov dword [x], 1

end_if:
Le compilateur inverse souvent la condition.

Au lieu de dire :

Code: Select all

si égal -> exécuter
il fait :

Code: Select all

si différent -> sauter la branche
C'est très fréquent en reverse.

12. Traduction d'un IF / ELSE

Code C :

Code: Select all

if (a < b)
{
    max = b;
}
else
{
    max = a;
}
Assembleur possible :

Code: Select all

mov eax, [a]
cmp eax, [b]
jge else_part

mov eax, [b]
mov [max], eax
jmp end_if

else_part:
mov eax, [a]
mov [max], eax

end_if:
On voit deux éléments essentiels :

Code: Select all

jcc -> choisir la branche
jmp -> éviter d'exécuter l'autre branche
13. IF / ELSE IF / ELSE

Code C :

Code: Select all

if (a == 1)
{
    result = 10;
}
else if (a == 2)
{
    result = 20;
}
else
{
    result = 0;
}
Traduction :

Code: Select all

cmp eax, 1
jne check_two

mov ebx, 10
jmp end

check_two:
cmp eax, 2
jne other

mov ebx, 20
jmp end

other:
xor ebx, ebx

end:
En reverse, une succession de :

Code: Select all

cmp
jcc
cmp
jcc
cmp
jcc
peut correspondre à une série de `if/else if`.

14. Boucle WHILE

Code C :

Code: Select all

while (i < 10)
{
    i++;
}
Assembleur simplifié :

Code: Select all

while_start:

cmp ecx, 10
jge while_end

inc ecx
jmp while_start

while_end:
Structure :

Code: Select all

test
 |
 | vrai
 v
corps
 |
 v
retour au test
15. Reconnaître une boucle en reverse

Un indice très fort est un saut vers une adresse plus basse.

Exemple :

Code: Select all

401000:
    ...
    cmp eax, 10
    jl 401000
Le saut revient vers une instruction précédente.

Cela indique souvent une boucle.

En x64dbg, les lignes de saut permettent généralement de visualiser ce contrôle de flux.

16. BREAK

`break` n'existe pas directement comme instruction assembleur.

Il est généralement traduit par un saut vers la fin de la boucle.

Code C :

Code: Select all

while (1)
{
    if (x == 10)
        break;

    x++;
}
Assembleur simplifié :

Code: Select all

loop_start:

cmp eax, 10
je loop_end

inc eax
jmp loop_start

loop_end:
Donc en reverse :

Code: Select all

saut depuis l'intérieur d'une boucle -> vers après la boucle
peut correspondre à un `break`.

17. CONTINUE

`continue` revient directement à l'itération suivante.

Exemple C :

Code: Select all

while (i < 10)
{
    i++;

    if (i == 5)
        continue;

    x++;
}
Assembleur possible :

Code: Select all

loop_start:

cmp ecx, 10
jge loop_end

inc ecx

cmp ecx, 5
je loop_start

inc eax
jmp loop_start

loop_end:
Le saut directement vers le début ou le test de la boucle peut donc représenter un `continue`.

18. Boucle DO WHILE

Une boucle `do while` exécute son corps au moins une fois.

Code C :

Code: Select all

do
{
    i++;
}
while (i < 10);
Assembleur :

Code: Select all

loop_start:

inc ecx

cmp ecx, 10
jl loop_start
Contrairement à `while`, le test se trouve à la fin.

19. Compter des bits avec une boucle

Exemple logique :

Code: Select all

sum = 0;

for (i = 0; i < 64; i++)
{
    sum += value & 1;
    value >>= 1;
}
Assembleur simplifié :

Code: Select all

xor edx, edx
xor ecx, ecx

loop_start:

cmp ecx, 64
jge end_loop

mov rbx, rax
and ebx, 1
add edx, ebx

shr rax, 1

inc ecx
jmp loop_start

end_loop:
Cette boucle examine les 64 bits d'une valeur.

20. Code source vs code compilé

Il ne faut pas s'attendre à retrouver exactement la structure C originale.

Un compilateur peut :
  • inverser une condition ;
  • déplacer le test ;
  • supprimer un saut ;
  • fusionner plusieurs branches ;
  • réorganiser une boucle ;
  • utiliser `cmov` au lieu d'un branchement ;
  • dérouler une boucle.
Par exemple :

Code: Select all

if (x == 0)
```

peut devenir :

[code]
test eax, eax
jne ...
et pas forcément :

Code: Select all

cmp eax, 0
je ...
21. Loop unrolling

Le loop unrolling consiste à répéter plusieurs fois le corps d'une boucle pour réduire le nombre de branchements.

Boucle classique :

Code: Select all

for (i = 0; i < 4; i++)
    sum += array[i];
Version conceptuellement déroulée :

Code: Select all

sum += array[0];
sum += array[1];
sum += array[2];
sum += array[3];
Avantage :

Code: Select all

moins de comparaisons
moins de sauts
```

Inconvénient :

[code]
plus de code machine
En reverse, une boucle simple peut donc apparaître comme plusieurs blocs similaires répétés.

22. Recherche dans une chaîne

Exemple C :

Code: Select all

while (data[i] != 0)
{
    if (data[i] == target)
        break;

    i++;
}
Assembleur possible :

Code: Select all

xor ecx, ecx

loop:

mov al, [data+rcx]

test al, al
je end

cmp al, bl
je found

inc ecx
jmp loop

found:
...

end:
Tu peux reconnaître :

Code: Select all

test al, al
```

comme un test du caractère nul.

[size=150][b][color=#FF0000]23. Instructions de chaînes x86[/color][/b][/size]

x86 possède plusieurs instructions spécialisées pour manipuler des blocs mémoire ou chaînes.

Les plus importantes :

[code]
movs
stos
lods
scas
cmps
Elles utilisent implicitement certains registres.

En 64 bits :

Code: Select all

RSI = source
RDI = destination
RCX = compteur avec REP
24. MOVSB

`movsb` copie un octet :

Code: Select all

[RSI] -> [RDI]
Puis RSI et RDI sont automatiquement avancés ou reculés selon le Direction Flag.

Exemple :

Code: Select all

movsb
équivaut conceptuellement à :

Code: Select all

[RDI] = [RSI]
RSI++
RDI++
si DF = 0.

25. REP MOVSB

Le préfixe :

Code: Select all

rep
permet de répéter l'instruction RCX fois.

Exemple :

Code: Select all

cld
mov rcx, 100
rep movsb
copie 100 octets de :

Code: Select all

[RSI]
```

vers :

[code]
[RDI]
C'est proche d'un petit `memcpy`.

26. STOSB

`stosb` écrit AL à l'adresse :

Code: Select all

[RDI]
Exemple :

Code: Select all

mov al, 0
mov rcx, 100
rep stosb
remplit une zone de 100 octets avec zéro.

On peut rapprocher cela d'un `memset`.

27. LODSB

`lodsb` charge :

Code: Select all

AL = [RSI]
Puis RSI est automatiquement mis à jour.

Cela permet de lire une suite d'octets depuis une source.

28. SCASB

`scasb` compare :

Code: Select all

AL
```

avec :

[code]
[RDI]
Puis RDI avance ou recule.

Cette instruction est très utile pour rechercher un octet dans une zone mémoire.

Exemple :

Code: Select all

mov al, 0
repne scasb
peut servir à rechercher un caractère nul.

29. CMPSB

`cmpsb` compare :

Code: Select all

[RSI]
```

avec :

[code]
[RDI]
Puis les deux pointeurs sont mis à jour.

Avec :

Code: Select all

repe cmpsb
on peut comparer plusieurs octets tant qu'ils restent égaux.

C'est proche du comportement d'une comparaison de blocs mémoire.

30. REP / REPE / REPNE

Les préfixes principaux sont :

Code: Select all

rep
repe
repz
repne
repnz
`rep` répète RCX fois.

`repe` / `repz` répète tant que :

Code: Select all

ZF = 1
```

et que RCX n'est pas nul.

`repne` / `repnz` répète tant que :

[code]
ZF = 0
```

et que RCX n'est pas nul.

[size=150][b][color=#FF0000]31. CLD et STD[/color][/b][/size]

Le Direction Flag détermine le sens d'avancement des instructions de chaînes.

`cld` :

[code]
DF = 0
```

Les pointeurs avancent vers les adresses supérieures.

`std` :

[code]
DF = 1
```

Les pointeurs avancent vers les adresses inférieures.

En pratique, beaucoup de code utilise :

[code]
cld
avant une séquence `rep movs`, `scas`, etc.

32. Exemple typique en reverse

Tu vois :

Code: Select all

cmp eax, 25
jne failed

mov eax, 1
jmp end

failed:
xor eax, eax

end:
ret
Lecture :

Code: Select all

si EAX == 25
    return 1
sinon
    return 0
Le contrôle de flux permet donc de reconstruire assez facilement la logique source.

33. Reconstruire une boucle

Tu observes :

Code: Select all

loop_start:

cmp ecx, 10
jge end

add eax, ecx
inc ecx
jmp loop_start

end:
Tu peux reconstruire :

Code: Select all

while (ecx < 10)
{
    eax += ecx;
    ecx++;
}
C'est exactement le raisonnement attendu en reverse.

34. Ce qu'il faut retenir pour le reverse

Pour ton usage, retiens surtout :
  • `jmp` effectue un saut inconditionnel ;
  • `cmp` compare deux valeurs en modifiant les flags ;
  • `je/jz` signifie égal / zéro ;
  • `jne/jnz` signifie différent / non zéro ;
  • `jg/jge/jl/jle` servent aux comparaisons signées ;
  • `ja/jae/jb/jbe` servent aux comparaisons non signées ;
  • un saut vers une adresse plus basse indique souvent une boucle ;
  • un saut vers après une boucle peut correspondre à `break` ;
  • un saut vers le début ou le test peut correspondre à `continue` ;
  • une boucle `while` teste généralement avant le corps ;
  • une boucle `do while` teste après le corps ;
  • le compilateur peut réorganiser les structures source ;
  • `rep movsb` ressemble à une copie mémoire ;
  • `rep stosb` ressemble à un remplissage mémoire ;
  • `repne scasb` peut servir à rechercher un octet ;
  • `repe cmpsb` peut servir à comparer des zones mémoire ;
  • `RSI`, `RDI`, `RCX` sont importants avec les instructions de chaînes.
35. Exercices

Exercice 1

Que fait :

Code: Select all

cmp eax, 10
je equal
Exercice 2

Quelle est la différence entre :

Code: Select all

jg
ja
Exercice 3

Reconstitue le pseudo-C :

Code: Select all

cmp eax, ebx
jne skip
mov ecx, 1

skip:
Exercice 4

Analyse :

Code: Select all

xor ecx, ecx

loop:
cmp ecx, 5
jge end

inc ecx
jmp loop

end:
Combien de fois le corps de la boucle est-il parcouru ?

Exercice 5

Quelle structure C ressemble le plus à :

Code: Select all

loop:
inc eax
cmp eax, 10
jl loop
Exercice 6

Explique la différence entre :

Code: Select all

while
do while
au niveau du placement du test.

Exercice 7

Que fait :

Code: Select all

cld
mov rcx, 100
rep movsb
Exercice 8

Que fait :

Code: Select all

mov al, 0
repne scasb
conceptuellement ?

Exercice 9

Analyse :

Code: Select all

cmp eax, 25
jne failed
mov eax, 1
ret

failed:
xor eax, eax
ret
Quelle valeur est retournée si EAX vaut initialement 25 ?

Et sinon ?

Exercice 10

Dans x64dbg, trouve une séquence contenant :

Code: Select all

cmp
jcc
jmp
Dessine mentalement les deux chemins possibles et détermine la condition permettant d'emprunter chaque branche.

Exercice 11

Trouve une boucle dans un programme désassemblé et repère :

Code: Select all

le test
le saut de sortie
le corps
le saut vers le début
Puis réécris-la sous forme de pseudo-C.

Who is online

Users browsing this forum: No registered users and 0 guests